Bug fixes for spiffy decode corner cases (#57)

* Bug fix for decoding empty maps and arrays with spiffy decode
* Bug fix for entering and exiting the same map multiple times
* Add a lot of tests for spiffy decoding of nested maps and arrays

Co-authored-by: Laurence Lundblade <lgl@securitytheory.com>
diff --git a/inc/qcbor/qcbor_spiffy_decode.h b/inc/qcbor/qcbor_spiffy_decode.h
index df0067b..0ea6a71 100644
--- a/inc/qcbor/qcbor_spiffy_decode.h
+++ b/inc/qcbor/qcbor_spiffy_decode.h
@@ -729,7 +729,7 @@
  The items in the map that was entered do not have to have been
  consumed for this to succeed.
 
- This sets thepre-order traversal cursor to the item after
+ This sets the pre-order traversal cursor to the item after
  the map that was exited.
 */
 static void QCBORDecode_ExitMap(QCBORDecodeContext *pCtx);
@@ -762,6 +762,8 @@
  nesting, this is of little consequence, but may be of consequence for
  large deeply nested CBOR structures on slow CPUs.
 
+ The position of the pre-order traversal cursor is not changed.
+
  See @ref Decode-Errors for discussion on how error handling works.
 
  See also QCBORDecode_GetItemsInMap() for error discussion.
@@ -804,6 +806,8 @@
  QCBORDecode_EnterMapinMapN(), QCBORDecode_EnterArrayInMapN() and such
  to descend into and process maps and arrays.
 
+ The position of the pre-order traversal cursor is not changed.
+
  See @ref Decode-Errors for discussion on how error handling works.
 
  The following errors are set:
@@ -1505,7 +1509,7 @@
 
  When the wrapped CBOR is entered with this function, the pre-order
  traversal and such are bounded to the wrapped
- CBOR. QCBORDecode_ExitBstrWrapped() must be called resume processing
+ CBOR. QCBORDecode_ExitBstrWrapped() must be called to resume processing
  CBOR outside the wrapped CBOR.
 
  If @c pBstr is not @c NULL the pointer and length of the wrapped
@@ -1518,7 +1522,6 @@
 
  See also QCBORDecode_ExitBstrWrapped(), QCBORDecode_EnterMap() and
  QCBORDecode_EnterArray().
-
  */
 void QCBORDecode_EnterBstrWrapped(QCBORDecodeContext *pCtx,
                                   uint8_t             uTagRequirement,
@@ -1545,7 +1548,7 @@
  The items in the wrapped CBOR that was entered do not have to have been
  consumed for this to succeed.
 
- The this sets thepre-order traversal cursor to the item after
+ The this sets the pre-order traversal cursor to the item after
  the byte string that was exited.
 */
 void QCBORDecode_ExitBstrWrapped(QCBORDecodeContext *pCtx);
diff --git a/src/qcbor_decode.c b/src/qcbor_decode.c
index afc3651..7b41ff3 100644
--- a/src/qcbor_decode.c
+++ b/src/qcbor_decode.c
@@ -227,7 +227,8 @@
       return false;
    }
    // Works for both definite and indefinite length maps/arrays
-   if(pNesting->pCurrentBounded->u.ma.uCountCursor != 0) {
+   if(pNesting->pCurrentBounded->u.ma.uCountCursor != 0 &&
+      pNesting->pCurrentBounded->u.ma.uCountCursor != QCBOR_COUNT_INDICATES_ZERO_LENGTH) {
       // Count is not zero, still unconsumed item
       return false;
    }
@@ -432,6 +433,13 @@
 
 
 static inline void
+DecodeNesting_ResetMapOrArrayCount(QCBORDecodeNesting *pNesting)
+{
+   pNesting->pCurrentBounded->u.ma.uCountCursor = pNesting->pCurrentBounded->u.ma.uCountTotal;
+}
+
+
+static inline void
 DecodeNesting_Init(QCBORDecodeNesting *pNesting)
 {
    /* Assumes that *pNesting has been zero'd before this call. */
@@ -445,7 +453,7 @@
 {
    *pSave = *pNesting;
    pNesting->pCurrent = pNesting->pCurrentBounded;
-   pNesting->pCurrent->u.ma.uCountCursor = pNesting->pCurrent->u.ma.uCountTotal;
+   DecodeNesting_ResetMapOrArrayCount(pNesting);
 }
 
 
@@ -2964,13 +2972,25 @@
 }
 
 
+/**
+ * @brief Search for a map/array by label and enter it
+ *
+ * @param[in] pMe  The decode context.
+ * @param[in] pSearch The map/array to search for.
+ *
+ * @c pSearch is expected to contain one item of type map or array
+ * with the label specified. The current bounded map will be searched for
+ * this and if found  will be entered.
+ *
+ * If the label is not found, or the item found is not a map or array,
+ * the error state is set.
+ */
 static void SearchAndEnter(QCBORDecodeContext *pMe, QCBORItem pSearch[])
 {
    // The first item in pSearch is the one that is to be
    // entered. It should be the only one filled in. Any other
    // will be ignored unless it causes an error.
    if(pMe->uLastError != QCBOR_SUCCESS) {
-      // Already in error state; do nothing.
       return;
    }
 
@@ -2985,23 +3005,28 @@
       return;
    }
 
-   /* Need to get the current pre-order nesting level and cursor to be
-      at the map/array about to be entered.
-
-    Also need the current map nesting level and start cursor to
-    be at the right place.
-
-    The UsefulInBuf offset could be anywhere, so no assumption is
-    made about it.
-
-    No assumption is made about the pre-order nesting level either.
-
-    However the bounded mode nesting level is assumed to be one above
-    the map level that is being entered.
+   /*
+    * QCBORDecode_EnterBoundedMapOrArray() used here, requires the
+    * next item for the pre-order traversal cursor to be the map/array
+    * found by MapSearch(). The next few lines of code force the
+    * cursor to that.
+    *
+    * There is no need to retain the old cursor because
+    * QCBORDecode_EnterBoundedMapOrArray() will set it to the
+    * beginning of the map/array being entered.
+    *
+    * The cursor is forced by: 1) setting the input buffer position to
+    * the item offset found by MapSearch(), 2) setting the map/array
+    * counter to the total in the map/array, 3) setting the nesting
+    * level. Setting the map/array counter to the total is not
+    * strictly correct, but this is OK because this cursor only needs
+    * to be used to get one item and MapSearch() has already found it
+    * confirming it exists.
     */
-   /* Seek to the data item that is the map or array */
    UsefulInputBuf_Seek(&(pMe->InBuf), uOffset);
 
+   DecodeNesting_ResetMapOrArrayCount(&(pMe->nesting));
+
    DecodeNesting_SetCurrentToBoundedLevel(&(pMe->nesting));
 
    QCBORDecode_EnterBoundedMapOrArray(pMe, pSearch->uDataType, NULL);
diff --git a/test/qcbor_decode_tests.c b/test/qcbor_decode_tests.c
index 70fd3b1..6ee22fd 100644
--- a/test/qcbor_decode_tests.c
+++ b/test/qcbor_decode_tests.c
@@ -4647,7 +4647,7 @@
       "another int": 98,
       "text 2": "lies, damn lies and statistics"
    }
-  }
+ }
  */
 
 int32_t SpiffyDecodeBasicMap(UsefulBufC input)
@@ -4743,6 +4743,174 @@
    return 0;
 }
 
+/*
+ {
+   -75008: h'05083399',
+   88: [
+     ],
+   100100: {
+     "sub1": {
+       10: [
+         0
+       ],
+       -75009: h'A46823990001',
+       100100: {
+         "json": "{ \"ueid\", \"xyz\"}",
+         "subsub": {
+           100002: h'141813191001'
+         }
+       }
+     }
+   }
+ }
+ */
+
+static const uint8_t spNestedCBOR[] = {
+0xa3, 0x3a, 0x00, 0x01, 0x24, 0xff, 0x44, 0x05, 0x08, 0x33, 0x99, 0x18, 0x58, 0x80, 0x1a, 0x00,
+0x01, 0x87, 0x04, 0xa1, 0x64, 0x73, 0x75, 0x62, 0x31, 0xa3, 0x0a, 0x81, 0x00, 0x3a, 0x00, 0x01,
+0x25, 0x00, 0x46, 0xa4, 0x68, 0x23, 0x99, 0x00, 0x01, 0x1a, 0x00, 0x01, 0x87, 0x04, 0xa2, 0x64,
+0x6a, 0x73, 0x6f, 0x6e, 0x70, 0x7b, 0x20, 0x22, 0x75, 0x65, 0x69, 0x64, 0x22, 0x2c, 0x20, 0x22,
+0x78, 0x79, 0x7a, 0x22, 0x7d, 0x66, 0x73, 0x75, 0x62, 0x73, 0x75, 0x62, 0xa1, 0x1a, 0x00, 0x01,
+0x86, 0xa2, 0x46, 0x14, 0x18, 0x13, 0x19, 0x10, 0x01
+};
+
+/*  Get item in multi-level nesting in spNestedCBOR */
+static int32_t DecodeNestedGetSubSub(QCBORDecodeContext *pDCtx)
+{
+   UsefulBufC String;
+
+   uint8_t test_oemid_bytes[] = {0x14, 0x18, 0x13, 0x19, 0x10, 0x01};
+   const struct q_useful_buf_c test_oemid = UsefulBuf_FROM_BYTE_ARRAY_LITERAL(test_oemid_bytes);
+
+   QCBORDecode_EnterMapFromMapN(pDCtx, 100100);
+   QCBORDecode_EnterMap(pDCtx, NULL);
+   QCBORDecode_EnterMapFromMapN(pDCtx, 100100);
+   QCBORDecode_EnterMapFromMapSZ(pDCtx, "subsub");
+   QCBORDecode_GetByteStringInMapN(pDCtx, 100002, &String);
+   if(QCBORDecode_GetError(pDCtx)) {
+      return 4001;
+   }
+   if(UsefulBuf_Compare(String, test_oemid)) {
+      return 4002;
+   }
+   QCBORDecode_ExitMap(pDCtx);
+   QCBORDecode_ExitMap(pDCtx);
+   QCBORDecode_ExitMap(pDCtx);
+   QCBORDecode_ExitMap(pDCtx);
+
+   return 0;
+}
+
+/*  Iterations on the zero-length array in spNestedCBOR */
+static int32_t DecodeNestedGetEmpty(QCBORDecodeContext *pDCtx)
+{
+   QCBORItem Item;
+   QCBORError         uErr;
+
+   QCBORDecode_EnterArrayFromMapN(pDCtx, 88);
+   for(int x = 0; x < 20; x++) {
+      uErr = QCBORDecode_GetNext(pDCtx, &Item);
+      if(uErr != QCBOR_ERR_NO_MORE_ITEMS) {
+         return 4100;
+
+      }
+   }
+   QCBORDecode_ExitArray(pDCtx);
+   if(QCBORDecode_GetError(pDCtx)) {
+      return 4101;
+   }
+
+   return 0;
+}
+
+/* Various iterations on the array that contains a zero in spNestedCBOR */
+static int32_t DecodeNestedGetZero(QCBORDecodeContext *pDCtx)
+{
+   QCBORError         uErr;
+
+   QCBORDecode_EnterMapFromMapN(pDCtx, 100100);
+   QCBORDecode_EnterMapFromMapSZ(pDCtx, "sub1");
+   QCBORDecode_EnterArrayFromMapN(pDCtx, 10);
+   int64_t nInt = 99;
+   QCBORDecode_GetInt64(pDCtx, &nInt);
+   if(nInt != 0) {
+      return 4200;
+   }
+   for(int x = 0; x < 20; x++) {
+      QCBORItem Item;
+      uErr = QCBORDecode_GetNext(pDCtx, &Item);
+      if(uErr != QCBOR_ERR_NO_MORE_ITEMS) {
+         return 4201;
+
+      }
+   }
+   QCBORDecode_ExitArray(pDCtx);
+   if(QCBORDecode_GetAndResetError(pDCtx)) {
+      return 4202;
+   }
+   QCBORDecode_EnterArrayFromMapN(pDCtx, 10);
+   UsefulBufC dD;
+   QCBORDecode_GetByteString(pDCtx, &dD);
+   if(QCBORDecode_GetAndResetError(pDCtx) != QCBOR_ERR_UNEXPECTED_TYPE) {
+      return 4203;
+   }
+   for(int x = 0; x < 20; x++) {
+      QCBORDecode_GetByteString(pDCtx, &dD);
+      uErr = QCBORDecode_GetAndResetError(pDCtx);
+      if(uErr != QCBOR_ERR_NO_MORE_ITEMS) {
+         return 4204;
+      }
+   }
+   QCBORDecode_ExitArray(pDCtx);
+   QCBORDecode_ExitMap(pDCtx);
+   QCBORDecode_ExitMap(pDCtx);
+
+   return 0;
+}
+
+/* Repeatedly enter and exit maps and arrays, go off the end of maps
+ and arrays and such. */
+static int32_t DecodeNestedIterate()
+{
+   QCBORDecodeContext DCtx;
+   int32_t            nReturn;
+   QCBORError         uErr;
+
+   QCBORDecode_Init(&DCtx, UsefulBuf_FROM_BYTE_ARRAY_LITERAL(spNestedCBOR), 0);
+   QCBORDecode_EnterMap(&DCtx, NULL);
+
+   for(int j = 0; j < 5; j++) {
+      for(int i = 0; i < 20; i++) {
+         nReturn = DecodeNestedGetSubSub(&DCtx);
+         if(nReturn) {
+            return nReturn;
+         }
+      }
+
+      for(int i = 0; i < 20; i++) {
+         nReturn = DecodeNestedGetEmpty(&DCtx);
+         if(nReturn ) {
+            return nReturn;
+         }
+      }
+
+      for(int i = 0; i < 20; i++) {
+         nReturn = DecodeNestedGetZero(&DCtx);
+         if(nReturn ) {
+            return nReturn;
+         }
+      }
+   }
+
+   QCBORDecode_ExitMap(&DCtx);
+   uErr = QCBORDecode_Finish(&DCtx);
+   if(uErr) {
+      return (int32_t)uErr + 4100;
+   }
+
+   return 0;
+}
+
 
 /*
  [23,
@@ -5125,7 +5293,9 @@
       return 2033;
    }
 
-   return 0;
+   nReturn = DecodeNestedIterate();
+
+   return nReturn;
 }