1LibCppBor: A Modern C++ CBOR Parser and Generator 2============================================== 3 4LibCppBor provides a natural and easy-to-use syntax for constructing and 5parsing CBOR messages. It does not (yet) support all features of 6CBOR, nor (yet) support validation against CDDL schemata, though both 7are planned. CBOR features that aren't supported include: 8 9* Indefinite length values 10* Semantic tagging 11* Floating point 12 13LibCppBor requires C++-17. 14 15## CBOR representation 16 17LibCppBor represents CBOR data items as instances of the `Item` class or, 18more precisely, as instances of subclasses of `Item`, since `Item` is a 19pure interface. The subclasses of `Item` correspond almost one-to-one 20with CBOR major types, and are named to match the CDDL names to which 21they correspond. They are: 22 23* `Uint` corresponds to major type 0, and can hold unsigned integers 24 up through (2^64 - 1). 25* `Nint` corresponds to major type 1. It can only hold values from -1 26 to -(2^63 - 1), since it's internal representation is an int64_t. 27 This can be fixed, but it seems unlikely that applications will need 28 the omitted range from -(2^63) to (2^64 - 1), since it's 29 inconvenient to represent them in many programming languages. 30* `Int` is an abstract base of `Uint` and `Nint` that facilitates 31 working with all signed integers representable with int64_t. 32* `Bstr` corresponds to major type 2, a byte string. 33* `Tstr` corresponds to major type 3, a text string. 34* `Array` corresponds to major type 4, an Array. It holds a 35 variable-length array of `Item`s. 36* `Map` corresponds to major type 5, a Map. It holds a 37 variable-length array of pairs of `Item`s. 38* `Simple` corresponds to major type 7. It's an abstract class since 39 items require more specific type. 40* `Bool` is the only currently-implemented subclass of `Simple`. 41 42Note that major type 6, semantic tag, is not yet implemented. 43 44In practice, users of LibCppBor will rarely use most of these classes 45when generating CBOR encodings. This is because LibCppBor provides 46straightforward conversions from the obvious normal C++ types. 47Specifically, the following conversions are provided in appropriate 48contexts: 49 50* Signed and unsigned integers convert to `Uint` or `Nint`, as 51 appropriate. 52* `std::string`, `std::string_view`, `const char*` and 53 `std::pair<char iterator, char iterator>` convert to `Tstr`. 54* `std::vector<uint8_t>`, `std::pair<uint8_t iterator, uint8_t 55 iterator>` and `std::pair<uint8_t*, size_t>` convert to `Bstr`. 56* `bool` converts to `Bool`. 57 58## CBOR generation 59 60### Complete tree generation 61 62The set of `encode` methods in `Item` provide the interface for 63producing encoded CBOR. The basic process for "complete tree" 64generation (as opposed to "incremental" generation, which is discussed 65below) is to construct an `Item` which models the data to be encoded, 66and then call one of the `encode` methods, whichever is convenient for 67the encoding destination. A trivial example: 68 69``` 70cppbor::Uint val(0); 71std::vector<uint8_t> encoding = val.encode(); 72``` 73 74 It's relatively rare that single values are encoded as above. More often, the 75 "root" data item will be an `Array` or `Map` which contains a more complex structure.For example 76 : 77 78``` using cppbor::Map; 79using cppbor::Array; 80 81std::vector<uint8_t> vec = // ... 82 Map val("key1", Array(Map("key_a", 99 "key_b", vec), "foo"), "key2", true); 83std::vector<uint8_t> encoding = val.encode(); 84``` 85 86This creates a map with two entries, with `Tstr` keys "Outer1" and 87"Outer2", respectively. The "Outer1" entry has as its value an 88`Array` containing a `Map` and a `Tstr`. The "Outer2" entry has a 89`Bool` value. 90 91This example demonstrates how automatic conversion of C++ types to 92LibCppBor `Item` subclass instances is done. Where the caller provides a 93C++ or C string, a `Tstr` entry is added. Where the caller provides 94an integer literal or variable, a `Uint` or `Nint` is added, depending 95on whether the value is positive or negative. 96 97As an alternative, a more fluent-style API is provided for building up 98structures. For example: 99 100``` 101using cppbor::Map; 102using cppbor::Array; 103 104std::vector<uint8_t> vec = // ... 105 Map val(); 106val.add("key1", Array().add(Map().add("key_a", 99).add("key_b", vec)).add("foo")).add("key2", true); 107std::vector<uint8_t> encoding = val.encode(); 108``` 109 110 An advantage of this interface over the constructor - 111 based creation approach above is that it need not be done all at once. 112 The `add` methods return a reference to the object added to to allow calls to be chained, 113 but chaining is not necessary; calls can be made 114 sequentially, as the data to add is available. 115 116#### `encode` methods 117 118There are several variations of `Item::encode`, all of which 119accomplish the same task but output the encoded data in different 120ways, and with somewhat different performance characteristics. The 121provided options are: 122 123* `bool encode(uint8\_t** pos, const uint8\_t* end)` encodes into the 124 buffer referenced by the range [`*pos`, end). `*pos` is moved. If 125 the encoding runs out of buffer space before finishing, the method 126 returns false. This is the most efficient way to encode, into an 127 already-allocated buffer. 128* `void encode(EncodeCallback encodeCallback)` calls `encodeCallback` 129 for each encoded byte. It's the responsibility of the implementor 130 of the callback to behave safely in the event that the output buffer 131 (if applicable) is exhausted. This is less efficient than the prior 132 method because it imposes an additional function call for each byte. 133* `template </*...*/> void encode(OutputIterator i)` 134 encodes into the provided iterator. SFINAE ensures that the 135 template doesn't match for non-iterators. The implementation 136 actually uses the callback-based method, plus has whatever overhead 137 the iterator adds. 138* `std::vector<uint8_t> encode()` creates a new std::vector, reserves 139 sufficient capacity to hold the encoding, and inserts the encoded 140 bytes with a std::pushback_iterator and the previous method. 141* `std::string toString()` does the same as the previous method, but 142 returns a string instead of a vector. 143 144### Incremental generation 145 146Incremental generation requires deeper understanding of CBOR, because 147the library can't do as much to ensure that the output is valid. The 148basic tool for intcremental generation is the `encodeHeader` 149function. There are two variations, one which writes into a buffer, 150and one which uses a callback. Both simply write out the bytes of a 151header. To construct the same map as in the above examples, 152incrementally, one might write: 153 154``` 155using namespace cppbor; // For example brevity 156 157std::vector encoding; 158auto iter = std::back_inserter(result); 159encodeHeader(MAP, 2 /* # of map entries */, iter); 160std::string s = "key1"; 161encodeHeader(TSTR, s.size(), iter); 162std::copy(s.begin(), s.end(), iter); 163encodeHeader(ARRAY, 2 /* # of array entries */, iter); 164Map().add("key_a", 99).add("key_b", vec).encode(iter) 165s = "foo"; 166encodeHeader(TSTR, foo.size(), iter); 167std::copy(s.begin(), s.end(), iter); 168s = "key2"; 169encodeHeader(TSTR, foo.size(), iter); 170std::copy(s.begin(), s.end(), iter); 171encodeHeader(SIMPLE, TRUE, iter); 172``` 173 174As the above example demonstrates, the styles can be mixed -- Note the 175creation and encoding of the inner Map using the fluent style. 176 177## Parsing 178 179LibCppBor also supports parsing of encoded CBOR data, with the same 180feature set as encoding. There are two basic approaches to parsing, 181"full" and "stream" 182 183### Full parsing 184 185Full parsing means completely parsing a (possibly-compound) data 186item from a byte buffer. The `parse` functions that do not take a 187`ParseClient` pointer do this. They return a `ParseResult` which is a 188tuple of three values: 189 190* std::unique_ptr<Item> that points to the parsed item, or is nullptr 191 if there was a parse error. 192* const uint8_t* that points to the byte after the end of the decoded 193 item, or to the first unparseable byte in the event of an error. 194* std::string that is empty on success or contains an error message if 195 a parse error occurred. 196 197Assuming a successful parse, you can then use `Item::type()` to 198discover the type of the parsed item (e.g. MAP), and then use the 199appropriate `Item::as*()` method (e.g. `Item::asMap()`) to get a 200pointer to an interface which allows you to retrieve specific values. 201 202### Stream parsing 203 204Stream parsing is more complex, but more flexible. To use 205StreamParsing, you must create your own subclass of `ParseClient` and 206call one of the `parse` functions that accepts it. See the 207`ParseClient` methods docstrings for details. 208 209One unusual feature of stream parsing is that the `ParseClient` 210callback methods not only provide the parsed Item, but also pointers 211to the portion of the buffer that encode that Item. This is useful 212if, for example, you want to find an element inside of a structure, 213and then copy the encoding of that sub-structure, without bothering to 214parse the rest. 215 216The full parser is implemented with the stream parser. 217 218### Disclaimer 219This is not an officially supported Google product 220