Add user-defined/set errors; Fix minor VGetNext error handling bug

QCBORDecode_SetError() is added so QCBOR users can set an error and have it propagated up.

The data and label type of the item returned by VGetNext is correctly set to NONE when the error occurs right away, but was not set to NONE when the error occurred previously. This is fixed. This is a minor bug and only affected error handling use that relied solely on checking that the data and label were NONE.

Added more testing of error processing.


* Add SetError(); VGetNext error bug fix

* Documentation fixes

---------

Co-authored-by: Laurence Lundblade <lgl@securitytheory.com>
diff --git a/inc/qcbor/qcbor_common.h b/inc/qcbor/qcbor_common.h
index 0575fd5..edabef9 100644
--- a/inc/qcbor/qcbor_common.h
+++ b/inc/qcbor/qcbor_common.h
@@ -521,7 +521,15 @@
     * whole tag contents when it is not the correct tag content, this
     * error can be returned. None of the built-in tag decoders do this
     * (to save object code). */
-   QCBOR_ERR_RECOVERABLE_BAD_TAG_CONTENT = 78
+   QCBOR_ERR_RECOVERABLE_BAD_TAG_CONTENT = 78,
+
+   /** 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 568739d..f54a478 100644
--- a/inc/qcbor/qcbor_decode.h
+++ b/inc/qcbor/qcbor_decode.h
@@ -121,8 +121,11 @@
  * 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
@@ -892,9 +895,10 @@
  *
  * 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:
  *
@@ -1204,7 +1208,7 @@
 /**
  * @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
@@ -1214,7 +1218,7 @@
 /**
  * @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
@@ -1234,6 +1238,30 @@
 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);
+
+
 
 
 /**
@@ -1522,6 +1550,14 @@
    }
 }
 
+
+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 72a81c6..2188211 100644
--- a/src/qcbor_decode.c
+++ b/src/qcbor_decode.c
@@ -2660,6 +2660,8 @@
 QCBORDecode_VPeekNext(QCBORDecodeContext *pMe, QCBORItem *pDecodedItem)
 {
    if(pMe->uLastError != QCBOR_SUCCESS) {
+      pDecodedItem->uDataType  = QCBOR_TYPE_NONE;
+      pDecodedItem->uLabelType = QCBOR_TYPE_NONE;
       return;
    }
 
@@ -2674,6 +2676,8 @@
 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 77514de..16ab10f 100644
--- a/test/qcbor_decode_tests.c
+++ b/test/qcbor_decode_tests.c
@@ -8829,3 +8829,85 @@
 
    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 11fdc94..0cedf43 100644
--- a/test/qcbor_decode_tests.h
+++ b/test/qcbor_decode_tests.h
@@ -318,4 +318,9 @@
 */
 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 9e747a6..988fd36 100644
--- a/test/run_tests.c
+++ b/test/run_tests.c
@@ -66,7 +66,8 @@
 
 
 static test_entry s_tests[] = {
-    TEST_ENTRY(OpenCloseBytesTest),
+   TEST_ENTRY(ErrorHandlingTests),
+   TEST_ENTRY(OpenCloseBytesTest),
     TEST_ENTRY(EnterBstrTest),
     TEST_ENTRY(IntegerConvertTest),
     TEST_ENTRY(EnterMapTest),