README.md
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