diff --git a/inc/qcbor/qcbor_common.h b/inc/qcbor/qcbor_common.h index cd85d970..8c6c1259 100644 --- a/inc/qcbor/qcbor_common.h +++ b/inc/qcbor/qcbor_common.h @@ -534,8 +534,15 @@ typedef enum { /* Trying to encode something that is discouraged (e.g., 65-bit * negative integer) without allowing it by calling * QCBOREncode_Allow() */ - QCBOR_ERR_NOT_ALLOWED = 80 + QCBOR_ERR_NOT_ALLOWED = 80, + /** A range of error codes that can be made use of by the + * caller. QCBOR internally does nothing with these except notice + * that they are not QCBOR_SUCCESS. See QCBORDecode_SetError(). */ + QCBOR_ERR_FIRST_USER_DEFINED = 128, + + /** See \ref QCBOR_ERR_FIRST_USER_DEFINED */ + QCBOR_ERR_LAST_USER_DEFINED = 255 /* This is stored in uint8_t; never add values > 255 */ } QCBORError; diff --git a/inc/qcbor/qcbor_decode.h b/inc/qcbor/qcbor_decode.h index f40200a4..69dbbc15 100644 --- a/inc/qcbor/qcbor_decode.h +++ b/inc/qcbor/qcbor_decode.h @@ -121,8 +121,11 @@ extern "C" { * call QCBORDecode_GetError() to know the earlier items were * successfully decoded before examining their value or type. * - * The internal decode error state is reset only by re initializing the - * decoder or calling QCBORDecode_GetErrorAndReset(). + * The internal decode error state can be reset by reinitializing the + * decoder or calling QCBORDecode_GetErrorAndReset(). Code calling + * QCBOR may take advantage of the internal error state to halt + * futher decoding and propagate errors it detects using + * QCBORDecode_SetError(). * * It is only useful to reset the error state by calling * QCBORDecode_GetErrorAndReset() on recoverable errors. Examples of @@ -900,9 +903,10 @@ QCBORDecode_SetUpAllocator(QCBORDecodeContext *pCtx, * * See [Decode Error Overview](#Decode-Errors-Overview). * - * If a decoding error occurs, \c uDataType and \c uLabelType will be set - * to @ref QCBOR_TYPE_NONE. If there is no need to know the specific - * error, it is sufficient to check for @ref QCBOR_TYPE_NONE. + * If a decoding error occurs or previously occured, \c uDataType and + * \c uLabelType will be set to @ref QCBOR_TYPE_NONE. If there is no + * need to know the specific error, it is sufficient to check for @ref + * QCBOR_TYPE_NONE. * * Errors fall in several categories: * @@ -1212,7 +1216,7 @@ QCBORDecode_GetAndResetError(QCBORDecodeContext *pCtx); /** * @brief Whether an error indicates non-well-formed CBOR. * - * @param[in] uErr The decoder context. + * @param[in] uErr The QCBOR error code. * @return @c true if the error code indicates non-well-formed CBOR. */ static bool @@ -1222,7 +1226,7 @@ QCBORDecode_IsNotWellFormedError(QCBORError uErr); /** * @brief Whether a decoding error is recoverable. * - * @param[in] uErr The decoder context. + * @param[in] uErr The QCBOR error code. * @return @c true if the error code indicates and uncrecoverable error. * * When an error is unrecoverable, no further decoding of the input is @@ -1242,6 +1246,30 @@ static bool QCBORDecode_IsUnrecoverableError(QCBORError uErr); +/** + * @brief Manually set error condition, or set user-defined error. + * + * @param[in] pCtx The decoder context. + * @param[in] uError The error code to set. + * + * Once set, none of the QCBORDecode methods will do anything and the + * error code set will stay until cleared with + * QCBORDecode_GetAndResetError(). A user-defined error can be set + * deep in some decoding layers to short-circuit further decoding + * and propagate up. + * + * When the error condition is set, QCBORDecode_VGetNext() will always + * return an item with data and label type as \ref QCBOR_TYPE_NONE. + * + * The main intent of this is to set a user-defined error code in the + * range of \ref QCBOR_ERR_FIRST_USER_DEFINED to + * \ref QCBOR_ERR_LAST_USER_DEFINED, but it is OK to set QCBOR-defined + * error codes too. + */ +static void +QCBORDecode_SetError(QCBORDecodeContext *pCtx, QCBORError uError); + + /** @@ -1530,6 +1558,14 @@ QCBORDecode_IsUnrecoverableError(const QCBORError uErr) } } + +static inline void +QCBORDecode_SetError(QCBORDecodeContext *pMe, QCBORError uError) +{ + pMe->uLastError = (uint8_t)uError; +} + + /* A few cross checks on size constants and special value lengths */ #if QCBOR_MAP_OFFSET_CACHE_INVALID < QCBOR_MAX_DECODE_INPUT_SIZE #error QCBOR_MAP_OFFSET_CACHE_INVALID is too large diff --git a/src/qcbor_decode.c b/src/qcbor_decode.c index 19054a94..bb8d4d4a 100644 --- a/src/qcbor_decode.c +++ b/src/qcbor_decode.c @@ -2658,6 +2658,8 @@ void QCBORDecode_VPeekNext(QCBORDecodeContext *pMe, QCBORItem *pDecodedItem) { if(pMe->uLastError != QCBOR_SUCCESS) { + pDecodedItem->uDataType = QCBOR_TYPE_NONE; + pDecodedItem->uLabelType = QCBOR_TYPE_NONE; return; } @@ -2672,6 +2674,8 @@ void QCBORDecode_VGetNext(QCBORDecodeContext *pMe, QCBORItem *pDecodedItem) { if(pMe->uLastError != QCBOR_SUCCESS) { + pDecodedItem->uDataType = QCBOR_TYPE_NONE; + pDecodedItem->uLabelType = QCBOR_TYPE_NONE; return; } diff --git a/test/qcbor_decode_tests.c b/test/qcbor_decode_tests.c index 76e7c3ff..dc5d0702 100644 --- a/test/qcbor_decode_tests.c +++ b/test/qcbor_decode_tests.c @@ -8830,3 +8830,85 @@ int32_t BoolTest(void) return 0; } + + +int32_t +ErrorHandlingTests(void) +{ + QCBORDecodeContext DCtx; + QCBORItem Item; + QCBORError uError; + int64_t integer; + + /* Test QCBORDecode_SetError() */ + QCBORDecode_Init(&DCtx, + UsefulBuf_FROM_BYTE_ARRAY_LITERAL(pValidMapEncoded), + QCBOR_DECODE_MODE_NORMAL); + + QCBORDecode_SetError(&DCtx, QCBOR_ERR_FIRST_USER_DEFINED); + + QCBORDecode_VGetNext(&DCtx, &Item); + + uError = QCBORDecode_GetError(&DCtx); + + if(uError != QCBOR_ERR_FIRST_USER_DEFINED) { + return -1; + } + + if(Item.uLabelType != QCBOR_TYPE_NONE || + Item.uDataType != QCBOR_TYPE_NONE) { + return -2; + } + + + /* Test data type returned from previous error */ + QCBORDecode_Init(&DCtx, + UsefulBuf_FROM_BYTE_ARRAY_LITERAL(pValidMapEncoded), + QCBOR_DECODE_MODE_NORMAL); + QCBORDecode_GetInt64(&DCtx, &integer); + uError = QCBORDecode_GetError(&DCtx); + if(uError != QCBOR_ERR_UNEXPECTED_TYPE) { + return -3; + } + + QCBORDecode_VGetNext(&DCtx, &Item); + if(Item.uLabelType != QCBOR_TYPE_NONE || + Item.uDataType != QCBOR_TYPE_NONE) { + return -2; + } + uError = QCBORDecode_GetError(&DCtx); + if(uError != QCBOR_ERR_UNEXPECTED_TYPE) { + return -3; + } + + + /* Test error classification functions */ + + if(!QCBORDecode_IsUnrecoverableError(QCBOR_ERR_INDEFINITE_STRING_CHUNK)) { + return -10; + } + if(QCBORDecode_IsUnrecoverableError(QCBOR_SUCCESS)) { + return -11; + } + if(!QCBORDecode_IsUnrecoverableError(QCBOR_ERR_INDEFINITE_STRING_CHUNK)) { + return -12; + } + if(QCBORDecode_IsUnrecoverableError(QCBOR_ERR_DUPLICATE_LABEL)) { + return -13; + } + + if(!QCBORDecode_IsNotWellFormedError(QCBOR_ERR_BAD_TYPE_7)) { + return -20; + } + if(!QCBORDecode_IsNotWellFormedError(QCBOR_ERR_BAD_BREAK)) { + return -21; + } + if(QCBORDecode_IsNotWellFormedError(QCBOR_SUCCESS)) { + return -22; + } + if(QCBORDecode_IsNotWellFormedError(QCBOR_ERR_ARRAY_DECODE_TOO_LONG)) { + return -23; + } + + return 0; +} diff --git a/test/qcbor_decode_tests.h b/test/qcbor_decode_tests.h index 11fdc94b..0cedf43d 100644 --- a/test/qcbor_decode_tests.h +++ b/test/qcbor_decode_tests.h @@ -318,4 +318,9 @@ Test GitHub issue #134: decode an indefinite-length string with a zero-length fi */ int32_t CBORTestIssue134(void); + + +int32_t ErrorHandlingTests(void); + + #endif /* defined(__QCBOR__qcbort_decode_tests__) */ diff --git a/test/run_tests.c b/test/run_tests.c index bfc9dd9d..c61fcefd 100644 --- a/test/run_tests.c +++ b/test/run_tests.c @@ -67,6 +67,7 @@ static test_entry2 s_tests2[] = { static test_entry s_tests[] = { + TEST_ENTRY(ErrorHandlingTests), TEST_ENTRY(OpenCloseBytesTest), TEST_ENTRY(EnterBstrTest), TEST_ENTRY(IntegerConvertTest),