Skip to content

Commit c05f01c

Browse files
zcbor_encode: Add new fragmented string encoding API
to match the redesigned decoding API. Signed-off-by: Øyvind Rønningstad <[email protected]>
1 parent a34f80b commit c05f01c

File tree

5 files changed

+375
-2
lines changed

5 files changed

+375
-2
lines changed

MIGRATION_GUIDE.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,16 @@
1313

1414
* A fix was made to the naming of bstr elements with a .size specifier, which might mean that these elements change name in your code when you regenerate.
1515

16+
* The fragmented payload API has been completely redesigned to accomodate adding the encoding counterpart.
17+
The docs have been updated and there's a new section in the README to explain the functionality.
18+
19+
* You must now define ZCBOR_FRAGMENTS to access the API
20+
* `zcbor_*str_decode_fragment()` has been renamed to `zcbor_*str_fragments_start_decode()`
21+
* After calling `zcbor_*str_fragments_start_decode()`, you must now retrieve the first fragment manually with `zcbor_str_fragment_decode()`, instead of via an argument.
22+
* `zcbor_next_fragment()` and `zcbor_bstr_next_fragment()` have merged and is now called `zcbor_str_fragment_decode()`.
23+
It does not take a `prev_fragment` argument, instead, this state is kept internally in the state struct.
24+
* `zcbor_bstr_start_decode_fragment()` has been renamed to `zcbor_cbor_bstr_fragments_start_decode()` and does not return a fragment.
25+
To retrieve fragments when decoding a CBOR-encoded bstr, use `zcbor_str_fragment_decode()`
1626

1727
# zcbor v. 0.8.0
1828

README.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,31 @@ ZCBOR_STATE_D(decode_state, n, payload, payload_len, elem_count, n_flags);
8888
ZCBOR_STATE_E(encode_state, n, payload, payload_len, 0);
8989
```
9090
91+
Fragmented payloads
92+
-------------------
93+
94+
zcbor can encode and decode payloads in sections, i.e. the payload can be split into separate buffers/arrays.
95+
This can be useful e.g. if you send or receive your payload in multiple packets.
96+
When the current payload section is done, call `zcbor_update_state()` to introduce the next section.
97+
Note that zcbor does not allow section boundaries to split a zcbor header/value pair.
98+
This means that the following elements cannot be split between sections:
99+
100+
- Numbers and simple values (integers, floats, bools, undefined, nil)
101+
- Tags
102+
- Headers of lists, maps, tstrs, and bstrs
103+
104+
If your payload is split in an unsupported way, you can get around it by making a small section out of the remaining bytes of one section spliced with the start of the next.
105+
Another option is to leave a little room at the start of each section buffer, and copy the remaining end of one section into the start of the next buffer.
106+
8 bytes should be enough for this.
107+
108+
Lists and maps can span multiple sections, as long as the individual elements are not split as to break the above rule.
109+
110+
String payloads can be split across multiple payload sections, if `ZCBOR_FRAGMENTS` is enabled, and the `*str_fragments_*()` APIs are used. Note that in the zcbor docs, the term "string fragment" is used for fragmented strings, while the term "payload section" is used for fragmented CBOR payloads, as passed to `zcbor_update_state()`. These do not always line up perfectly, particularly at the start and end of fragmented strings.
111+
112+
CBOR-encoded bstrs can be nested, and there can also be a non-CBOR-encoded innermost string.
113+
The current innermost string (CBOR-encoded or otherwise) is called the "current string".
114+
`zcbor_update_state()` modifies all backups so that outer nested CBOR-encoded strings have updated information about the new section.
115+
91116
Configuration
92117
-------------
93118

include/zcbor_encode.h

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,46 @@ bool zcbor_bstr_start_encode(zcbor_state_t *state);
231231
*/
232232
bool zcbor_bstr_end_encode(zcbor_state_t *state, struct zcbor_string *result);
233233

234+
235+
#ifdef ZCBOR_FRAGMENTS
236+
237+
/** Start encoding a fragmented string. I.e. a string spread over non-consecutive payload sections.
238+
*
239+
* After calling this, you can write a fragment with @ref zcbor_str_fragment_encode,
240+
* then update the payload with @ref zcbor_update_state.
241+
* Repeat until the string is fully decoded, then call @ref zcbor_bstr_fragments_end_encode.
242+
*/
243+
bool zcbor_bstr_fragments_start_encode(zcbor_state_t *state, size_t total_len);
244+
bool zcbor_tstr_fragments_start_encode(zcbor_state_t *state, size_t total_len);
245+
246+
/** Start encoding a fragmented CBOR-encoded bytestring.
247+
*
248+
* I.e. a string spread over non-consecutive payload sections.
249+
*
250+
* This is an alternative to zcbor_*str_fragments_start_encode() to be used if the payload
251+
* contains CBOR data that will be encoded directly with other zcbor_*() functions.
252+
*
253+
* A state backup is created to keep track of the element count and original payload_end.
254+
* After calling this, you can encode elements using other zcbor functions,
255+
* then update the payload with @ref zcbor_update_state.
256+
* Repeat until the string is fully decoded, then call @ref zcbor_bstr_fragments_end_encode.
257+
* When the current payload section contains the end of the string,
258+
* payload_end is set to the end of the string, so there is no risk of encoding past the end.
259+
*/
260+
bool zcbor_cbor_bstr_fragments_start_encode(zcbor_state_t *state, size_t total_len);
261+
262+
/** Retrieve a string fragment.
263+
*
264+
* Consume bytes from the payload until either the end of the payload or the end of the string.
265+
* Do not use this function with @ref zcbor_cbor_bstr_fragments_start_encode.
266+
*/
267+
bool zcbor_str_fragment_encode(zcbor_state_t *state, struct zcbor_string *fragment, size_t *enc_len);
268+
269+
/** Finish encoding a fragmented string. */
270+
bool zcbor_str_fragments_end_encode(zcbor_state_t *state);
271+
272+
#endif /* ZCBOR_FRAGMENTS */
273+
234274
#ifdef __cplusplus
235275
}
236276
#endif

src/zcbor_encode.c

Lines changed: 115 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ static bool encode_header_byte(zcbor_state_t *state,
4848

4949
zcbor_assert_state(additional < 32, "Unsupported additional value: %d\r\n", additional);
5050

51+
#ifdef ZCBOR_FRAGMENTS
52+
ZCBOR_ERR_IF(state->inside_frag_str, ZCBOR_ERR_INSIDE_STRING);
53+
#endif
54+
5155
*(state->payload_mut) = (uint8_t)((major_type << 5) | (additional & 0x1F));
5256
zcbor_trace(state, "value_encode");
5357
state->payload_mut++;
@@ -234,6 +238,7 @@ bool zcbor_bstr_start_encode(zcbor_state_t *state)
234238

235239
/* Encode a dummy header */
236240
if (!value_encode(state, ZCBOR_MAJOR_TYPE_BSTR, &max_len, sizeof(max_len))) {
241+
zcbor_process_backup(state, ZCBOR_FLAG_CONSUME, 0xFFFFFFFF);
237242
ZCBOR_FAIL();
238243
}
239244
return true;
@@ -259,7 +264,7 @@ bool zcbor_bstr_end_encode(zcbor_state_t *state, struct zcbor_string *result)
259264
result->value = state->payload + zcbor_header_len(zcbor_remaining_str_len(state));
260265
result->len = (size_t)payload - (size_t)result->value;
261266

262-
/* Reencode header of list now that we know the number of elements. */
267+
/* Reencode header of list now that we know the length. */
263268
if (!zcbor_bstr_encode(state, result)) {
264269
ZCBOR_FAIL();
265270
}
@@ -279,7 +284,7 @@ static bool str_encode(zcbor_state_t *state,
279284
}
280285
if (state->payload_mut != input->value) {
281286
/* Use memmove since string might be encoded into the same space
282-
* because of bstrx_cbor_start_encode/bstrx_cbor_end_encode. */
287+
* because of zcbor_bstr_start_encode/zcbor_bstr_end_encode. */
283288
memmove(state->payload_mut, input->value, input->len);
284289
}
285290
state->payload += input->len;
@@ -327,6 +332,106 @@ bool zcbor_tstr_put_term(zcbor_state_t *state, char const *str, size_t maxlen)
327332
}
328333

329334

335+
#ifdef ZCBOR_FRAGMENTS
336+
337+
static bool start_encode_fragments(zcbor_state_t *state,
338+
zcbor_major_type_t major_type, size_t len, bool cbor_bstr)
339+
{
340+
ZCBOR_CHECK_PAYLOAD();
341+
342+
if (state->inside_cbor_bstr) {
343+
if ((state->str_total_len_cbor - zcbor_current_string_offset(state) - zcbor_header_len(len)) < len) {
344+
ZCBOR_ERR(ZCBOR_ERR_INNER_STRING_TOO_LARGE);
345+
}
346+
}
347+
348+
if (cbor_bstr) {
349+
if (!zcbor_new_backup(state, 0)) {
350+
ZCBOR_FAIL();
351+
}
352+
}
353+
354+
if (!value_encode(state, major_type, &len, sizeof(len))) {
355+
if (cbor_bstr) {
356+
zcbor_process_backup(state, ZCBOR_FLAG_CONSUME | ZCBOR_FLAG_RESTORE, 0xFFFFFFFF);
357+
}
358+
ZCBOR_FAIL();
359+
}
360+
361+
ptrdiff_t new_offset = state->constant_state->curr_payload_section - state->payload;
362+
363+
if (cbor_bstr) {
364+
state->frag_offset_cbor = new_offset;
365+
state->str_total_len_cbor = len;
366+
state->inside_cbor_bstr = true;
367+
} else {
368+
state->frag_offset = new_offset;
369+
state->str_total_len = len;
370+
state->inside_frag_str = true;
371+
}
372+
373+
return true;
374+
}
375+
376+
377+
bool zcbor_bstr_fragments_start_encode(zcbor_state_t *state, size_t len)
378+
{
379+
return start_encode_fragments(state, ZCBOR_MAJOR_TYPE_BSTR, len, false);
380+
}
381+
382+
383+
bool zcbor_tstr_fragments_start_encode(zcbor_state_t *state, size_t len)
384+
{
385+
return start_encode_fragments(state, ZCBOR_MAJOR_TYPE_TSTR, len, false);
386+
}
387+
388+
389+
bool zcbor_cbor_bstr_fragments_start_encode(zcbor_state_t *state, size_t len)
390+
{
391+
return start_encode_fragments(state, ZCBOR_MAJOR_TYPE_BSTR, len, true);
392+
}
393+
394+
395+
bool zcbor_str_fragment_encode(zcbor_state_t *state, struct zcbor_string *fragment, size_t *enc_len)
396+
{
397+
ZCBOR_CHECK_PAYLOAD();
398+
399+
ZCBOR_ERR_IF(!state->inside_frag_str, ZCBOR_ERR_NOT_IN_FRAGMENT);
400+
401+
size_t len = MIN(MIN((size_t)state->payload_end - (size_t)state->payload, fragment->len),
402+
state->str_total_len - zcbor_current_string_offset(state));
403+
404+
memcpy(state->payload_mut, fragment->value, len);
405+
state->payload += len;
406+
407+
if (enc_len != NULL) {
408+
*enc_len = len;
409+
}
410+
411+
return true;
412+
}
413+
414+
415+
bool zcbor_str_fragments_end_encode(zcbor_state_t *state)
416+
{
417+
ZCBOR_ERR_IF(!state->inside_frag_str && !state->inside_cbor_bstr, ZCBOR_ERR_NOT_IN_FRAGMENT);
418+
ZCBOR_ERR_IF(zcbor_current_string_remainder(state) != 0, ZCBOR_ERR_NOT_AT_END);
419+
420+
if (state->inside_frag_str) {
421+
state->inside_frag_str = false;
422+
} else {
423+
if (!zcbor_process_backup(state, ZCBOR_FLAG_RESTORE | ZCBOR_FLAG_CONSUME | ZCBOR_FLAG_KEEP_PAYLOAD, 0xFFFFFFFF)) {
424+
ZCBOR_FAIL();
425+
}
426+
state->elem_count++;
427+
}
428+
429+
return true;
430+
}
431+
432+
#endif /* ZCBOR_FRAGMENTS */
433+
434+
330435
static bool list_map_start_encode(zcbor_state_t *state, size_t max_num,
331436
zcbor_major_type_t major_type)
332437
{
@@ -376,6 +481,14 @@ static bool list_map_end_encode(zcbor_state_t *state, size_t max_num,
376481
size_t max_header_len = zcbor_header_len_ptr(&max_num, 4) - 1;
377482
size_t header_len = zcbor_header_len_ptr(&list_count, 4) - 1;
378483

484+
if (max_num == list_count) {
485+
if (!zcbor_process_backup(state, ZCBOR_FLAG_RESTORE | ZCBOR_FLAG_CONSUME | ZCBOR_FLAG_KEEP_PAYLOAD, 0xFFFFFFFF)) {
486+
ZCBOR_FAIL();
487+
}
488+
state->elem_count++;
489+
return true;
490+
}
491+
379492
if (!zcbor_process_backup(state, ZCBOR_FLAG_RESTORE | ZCBOR_FLAG_CONSUME, 0xFFFFFFFF)) {
380493
ZCBOR_FAIL();
381494
}

0 commit comments

Comments
 (0)