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