diff --git a/doc/source/api/streaming_decoding.rst b/doc/source/api/streaming_decoding.rst index d7cea387..7a95b47c 100644 --- a/doc/source/api/streaming_decoding.rst +++ b/doc/source/api/streaming_decoding.rst @@ -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 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/examples/streaming_parser.c b/examples/streaming_parser.c index 0c7a0b59..0edd3e1d 100644 --- a/examples/streaming_parser.c +++ b/examples/streaming_parser.c @@ -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); diff --git a/src/cbor.c b/src/cbor.c index af99de1b..49926e33 100644 --- a/src/cbor.c +++ b/src/cbor.c @@ -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; diff --git a/src/cbor/data.h b/src/cbor/data.h index a12e92f2..2522fec3 100644 --- a/src/cbor/data.h +++ b/src/cbor/data.h @@ -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; };