Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions doc/source/api/streaming_decoding.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,42 @@ When building custom sets of callbacks, feel free to start from
.. doxygenvariable:: cbor_empty_callbacks


Handling failures in callbacks
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Callbacks receive no return value, so there is no built-in channel to signal
failure back to :func:`cbor_stream_decode`. Any error that occurs inside a
callback — including allocation failures, validation errors, or application-level
rejections — must be tracked by the callback itself, typically via a flag in the
``context`` struct passed to :func:`cbor_stream_decode`:

.. code-block:: c

struct my_context {
bool failed;
/* ... */
};

void my_string_callback(void *context, cbor_data data, uint64_t length) {
struct my_context *ctx = context;
if (length > MAX_ALLOWED) {
ctx->failed = true;
return;
}
char *copy = malloc(length);
if (copy == NULL) {
ctx->failed = true;
return;
}
/* ... */
}

After each call to :func:`cbor_stream_decode`, check the flag before
continuing. Note that :func:`cbor_load` handles allocation failures internally —
the ``CBOR_ERR_MEMERROR`` result code is set when a builder callback fails to
allocate memory.


Callback types definition
~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Expand Down
12 changes: 9 additions & 3 deletions examples/streaming_parser.c
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,17 @@ int main(int argc, char* argv[]) {
while (bytes_read < length) {
decode_result = cbor_stream_decode(buffer + bytes_read, length - bytes_read,
&callbacks, NULL);
if (decode_result.status == CBOR_DECODER_ERROR) {
fprintf(stderr, "Error at byte %zu\n", bytes_read);
if (decode_result.status == CBOR_DECODER_FINISHED) {
bytes_read += decode_result.read;
} else {
// The input was fully loaded into memory, so NEDATA means truncated data.
fprintf(stderr,
decode_result.status == CBOR_DECODER_NEDATA
? "Truncated data at byte %zu\n"
: "Error at byte %zu\n",
bytes_read);
break;
}
bytes_read += decode_result.read;
}

free(buffer);
Expand Down
2 changes: 1 addition & 1 deletion src/cbor.c
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ cbor_item_t* cbor_load(cbor_data source, size_t source_size,
break;
}
case CBOR_DECODER_NEDATA:
/* Data length doesn't match MTB expectation */
/* Not enough data to complete the current item */
{
result->error.code = CBOR_ERR_NOTENOUGHDATA;
goto error;
Expand Down
20 changes: 10 additions & 10 deletions src/cbor/data.h
Original file line number Diff line number Diff line change
Expand Up @@ -240,19 +240,19 @@ struct cbor_decoder_result {
/** The decoding status */
enum cbor_decoder_status status;

/** Number of bytes in the input buffer needed to resume parsing
/** Number of bytes needed in the input buffer to make progress
*
* Set to 0 unless the result status is #CBOR_DECODER_NEDATA. If it is, then:
* - If at least one byte was passed, #required will be set to the minimum
* number of bytes needed to invoke a decoded callback on the current
* prefix.
* Set to 0 unless the result status is #CBOR_DECODER_NEDATA. When it is:
* - #required is the total number of bytes that must be present at the
* start of the buffer before calling #cbor_stream_decode again will
* invoke a callback. The caller should accumulate bytes until at least
* #required bytes are available, then retry from the same position.
*
* For example: Attempting to decode a 1B buffer containing `0x19` will
* set #required to 3 as `0x19` signals a 2B integer item, so we need at
* least 3B to continue (the `0x19` MTB byte and two bytes of data needed
* to invoke #cbor_callbacks.uint16).
* For example: A 1B buffer containing `0x19` (a 2B uint16 item) will
* set #required to 3 — the MTB byte plus two bytes of data.
*
* - If there was no data at all, #read will always be set to 1
* - If the buffer was empty (source_size == 0), #required is set to 1
* since at least the initial MTB byte is needed.
*/
size_t required;
};
Expand Down