Decode conformance for preferred serialization, CDE and dCBOR (#216)
Three conformance modes are added for decoding
- preferred serialization
- CDE
- dCBOR
This checks for sort ordering and duplicate labels when a map is decoded in CDE and dCBOR modes.
It does not support arrays and maps as map labels. They will error out, unless you use maps-as-arrays mode.
Conformance includes checking for shortest form of integers and floats and for dCBOR unification of floats and integers.
* start work on dCBOR decoding enforcement
* checkpoint
* Floating point conformane; ifdef for disabling
* Add more tests
* More dCBOR tests and conformance checks
* More test cases
* Bug fixes and more tests
* Check point stuff
* Map dup and sort order checking kind of working
* more work...
* Finish off UsefulInputBuf_Compare()
* Fix #ifdef fanout
* Fix warnings and #ifdef fan out
* sort & dup checking working and tested
* Fix test ifdef fan out
* Minor fix of one test case
* Another fan out fix
* backout map label checking; doc; test
* Stragglers
---------
Co-authored-by: Laurence Lundblade <lgl@securitytheory.com>
diff --git a/src/qcbor_decode.c b/src/qcbor_decode.c
index eabd3e4..05363f0 100644
--- a/src/qcbor_decode.c
+++ b/src/qcbor_decode.c
@@ -773,6 +773,7 @@
* @brief Decode the CBOR head, the type and argument.
*
* @param[in] pUInBuf The input buffer to read from.
+ * @param[in] bRequirePreferred Require preferred serialization for argument.
* @param[out] pnMajorType The decoded major type.
* @param[out] puArgument The decoded argument.
* @param[out] pnAdditionalInfo The decoded Lower 5 bits of initial byte.
@@ -781,11 +782,10 @@
* @retval QCBOR_ERR_HIT_END Unexpected end of input
*
* This decodes the CBOR "head" that every CBOR data item has. See
- * longer explaination of the head in documentation for
- * QCBOREncode_EncodeHead().
+ * longer description in QCBOREncode_EncodeHead().
*
- * This does the network->host byte order conversion. The conversion
- * here also results in the conversion for floats in addition to that
+ * This does the network to host byte order conversion. The conversion
+ * here also provides the conversion for floats in addition to that
* for lengths, tags and integer values.
*
* The int type is preferred to uint8_t for some variables as this
@@ -794,21 +794,21 @@
*/
static QCBORError
QCBOR_Private_DecodeHead(UsefulInputBuf *pUInBuf,
+#ifndef QCBOR_DISABLE_DECODE_CONFORMANCE
+ bool bRequirePreferred,
+#endif
int *pnMajorType,
uint64_t *puArgument,
int *pnAdditionalInfo)
{
QCBORError uReturn;
+ uint64_t uArgument;
- /* Get the initial byte that every CBOR data item has and break it
- * down. */
+ /* Get and break down initial byte that every CBOR data item has */
const int nInitialByte = (int)UsefulInputBuf_GetByte(pUInBuf);
const int nTmpMajorType = nInitialByte >> 5;
const int nAdditionalInfo = nInitialByte & 0x1f;
- /* Where the argument accumulates */
- uint64_t uArgument;
-
if(nAdditionalInfo >= LEN_IS_ONE_BYTE && nAdditionalInfo <= LEN_IS_EIGHT_BYTES) {
/* Need to get 1,2,4 or 8 additional argument bytes. Map
* LEN_IS_ONE_BYTE..LEN_IS_EIGHT_BYTES to actual length.
@@ -818,14 +818,46 @@
/* Loop getting all the bytes in the argument */
uArgument = 0;
for(int i = aIterate[nAdditionalInfo - LEN_IS_ONE_BYTE]; i; i--) {
- /* This shift and add gives the endian conversion. */
+ /* This shift-and-add gives the endian conversion. */
uArgument = (uArgument << 8) + UsefulInputBuf_GetByte(pUInBuf);
}
+
+#ifndef QCBOR_DISABLE_DECODE_CONFORMANCE
+ /* If requested, check that argument is in preferred form */
+ if(bRequirePreferred) {
+ uint64_t uMinArgument;
+
+ if(nAdditionalInfo == LEN_IS_ONE_BYTE) {
+ if(uArgument < 24) {
+ uReturn = QCBOR_ERR_PREFERRED_CONFORMANCE;
+ goto Done;
+ }
+ } else {
+ if(nTmpMajorType != CBOR_MAJOR_TYPE_SIMPLE) {
+ /* Check only if not a floating-point number */
+ int nArgLen = aIterate[nAdditionalInfo - LEN_IS_ONE_BYTE - 1];
+ uMinArgument = UINT64_MAX >> ((int)sizeof(uint64_t) - nArgLen) * 8;
+ if(uArgument <= uMinArgument) {
+ uReturn = QCBOR_ERR_PREFERRED_CONFORMANCE;
+ goto Done;
+ }
+ }
+ }
+ }
+#endif /* ! QCBOR_DISABLE_DECODE_CONFORMANCE */
+
} else if(nAdditionalInfo >= ADDINFO_RESERVED1 && nAdditionalInfo <= ADDINFO_RESERVED3) {
/* The reserved and thus-far unused additional info values */
uReturn = QCBOR_ERR_UNSUPPORTED;
goto Done;
} else {
+#ifndef QCBOR_DISABLE_DECODE_CONFORMANCE
+ if(bRequirePreferred && nAdditionalInfo == LEN_IS_INDEFINITE) {
+ uReturn = QCBOR_ERR_PREFERRED_CONFORMANCE;
+ goto Done;
+ }
+#endif /* ! QCBOR_DISABLE_DECODE_CONFORMANCE */
+
/* Less than 24, additional info is argument or 31, an
* indefinite-length. No more bytes to get.
*/
@@ -1137,6 +1169,208 @@
}
+#if !defined(QCBOR_DISABLE_DECODE_CONFORMANCE) && !defined(QCBOR_DISABLE_PREFERRED_FLOAT)
+
+static QCBORError
+QCBORDecode_Private_HalfConformance(const double d, const uint8_t uDecodeMode)
+{
+ struct IEEE754_ToInt ToInt;
+
+ /* Only need to check for conversion to integer because
+ * half-precision is always preferred serialization. Don't
+ * need special checker for half-precision because whole
+ * numbers always convert perfectly from half to double.
+ *
+ * This catches half-precision with NaN payload too.
+ *
+ * The only thing allowed here is a double/half-precision that
+ * can't be converted to anything but a double.
+ */
+ if(uDecodeMode >= QCBOR_DECODE_MODE_DCBOR) {
+ ToInt = IEEE754_DoubleToInt(d);
+ if(ToInt.type != QCBOR_TYPE_DOUBLE) {
+ return QCBOR_ERR_DCBOR_CONFORMANCE;
+ }
+ }
+
+ return QCBOR_SUCCESS;
+}
+
+
+static QCBORError
+QCBORDecode_Private_SingleConformance(const float f, const uint8_t uDecodeMode)
+{
+ struct IEEE754_ToInt ToInt;
+ IEEE754_union ToSmaller;
+
+ if(uDecodeMode >= QCBOR_DECODE_MODE_DCBOR) {
+ /* See if it could have been encoded as an integer */
+ ToInt = IEEE754_SingleToInt(f);
+ if(ToInt.type == IEEE754_ToInt_IS_INT || ToInt.type == IEEE754_ToInt_IS_UINT) {
+ return QCBOR_ERR_DCBOR_CONFORMANCE;
+ }
+
+ /* Make sure there is no NaN payload */
+ if(IEEE754_SingleHasNaNPayload(f)) {
+ return QCBOR_ERR_DCBOR_CONFORMANCE;
+ }
+ }
+
+ /* See if it could have been encoded shorter */
+ if(uDecodeMode >= QCBOR_DECODE_MODE_PREFERRED) {
+ ToSmaller = IEEE754_SingleToHalf(f, true);
+ if(ToSmaller.uSize != sizeof(float)) {
+ return QCBOR_ERR_PREFERRED_CONFORMANCE;
+ }
+ }
+
+ return QCBOR_SUCCESS;
+}
+
+
+static QCBORError
+QCBORDecode_Private_DoubleConformance(const double d, uint8_t uDecodeMode)
+{
+ struct IEEE754_ToInt ToInt;
+ IEEE754_union ToSmaller;
+
+ if(uDecodeMode >= QCBOR_DECODE_MODE_DCBOR) {
+ /* See if it could have been encoded as an integer */
+ ToInt = IEEE754_DoubleToInt(d);
+ if(ToInt.type == IEEE754_ToInt_IS_INT || ToInt.type == IEEE754_ToInt_IS_UINT) {
+ return QCBOR_ERR_DCBOR_CONFORMANCE;
+ }
+ /* Make sure there is no NaN payload */
+ if(IEEE754_DoubleHasNaNPayload(d)) {
+ return QCBOR_ERR_DCBOR_CONFORMANCE;
+ }
+ }
+
+ /* See if it could have been encoded shorter */
+ if(uDecodeMode >= QCBOR_DECODE_MODE_PREFERRED) {
+ ToSmaller = IEEE754_DoubleToSmaller(d, true, true);
+ if(ToSmaller.uSize != sizeof(double)) {
+ return QCBOR_ERR_PREFERRED_CONFORMANCE;
+ }
+ }
+
+ return QCBOR_SUCCESS;
+}
+#else /* ! QCBOR_DISABLE_PREFERRED_FLOAT && ! QCBOR_DISABLE_PREFERRED_FLOAT */
+#ifndef QCBOR_DISABLE_FLOAT_HW_USE
+
+static QCBORError
+QCBORDecode_Private_SingleConformance(const float f, uint8_t uDecodeMode)
+{
+ (void)f;
+ if(uDecodeMode >= QCBOR_DECODE_MODE_PREFERRED) {
+ return QCBOR_ERR_CANT_CHECK_FLOAT_CONFORMANCE;
+ } else {
+ return QCBOR_SUCCESS;
+ }
+}
+
+static QCBORError
+QCBORDecode_Private_DoubleConformance(const double d, uint8_t uDecodeMode)
+{
+ (void)d;
+ if(uDecodeMode >= QCBOR_DECODE_MODE_PREFERRED) {
+ return QCBOR_ERR_CANT_CHECK_FLOAT_CONFORMANCE;
+ } else {
+ return QCBOR_SUCCESS;
+ }
+}
+#endif /* ! QCBOR_DISABLE_FLOAT_HW_USE */
+#endif /* ! QCBOR_DISABLE_DECODE_CONFORMANCE && ! QCBOR_DISABLE_PREFERRED_FLOAT */
+
+
+
+/*
+ * Decode a float
+ */
+static QCBORError
+QCBOR_Private_DecodeFloat(const uint8_t uDecodeMode,
+ const int nAdditionalInfo,
+ const uint64_t uArgument,
+ QCBORItem *pDecodedItem)
+{
+ QCBORError uReturn = QCBOR_SUCCESS;
+ float single;
+
+ (void)single; /* Avoid unused error from various #ifndefs */
+
+ switch(nAdditionalInfo) {
+ case HALF_PREC_FLOAT: /* 25 */
+#ifndef QCBOR_DISABLE_PREFERRED_FLOAT
+ /* Half-precision is returned as a double. The cast to
+ * uint16_t is safe because the encoded value was 16 bits. It
+ * was widened to 64 bits to be passed in here.
+ */
+ pDecodedItem->val.dfnum = IEEE754_HalfToDouble((uint16_t)uArgument);
+ pDecodedItem->uDataType = QCBOR_TYPE_DOUBLE;
+
+ uReturn = QCBORDecode_Private_HalfConformance(pDecodedItem->val.dfnum, uDecodeMode);
+ if(uReturn != QCBOR_SUCCESS) {
+ break;
+ }
+#endif /* ! QCBOR_DISABLE_PREFERRED_FLOAT */
+ uReturn = FLOAT_ERR_CODE_NO_HALF_PREC(QCBOR_SUCCESS);
+ break;
+
+ case SINGLE_PREC_FLOAT: /* 26 */
+#ifndef USEFULBUF_DISABLE_ALL_FLOAT
+ /* Single precision is normally returned as a double since
+ * double is widely supported, there is no loss of precision,
+ * it makes it easy for the caller in most cases and it can
+ * be converted back to single with no loss of precision
+ *
+ * The cast to uint32_t is safe because the encoded value was
+ * 32 bits. It was widened to 64 bits to be passed in here.
+ */
+ single = UsefulBufUtil_CopyUint32ToFloat((uint32_t)uArgument);
+ uReturn = QCBORDecode_Private_SingleConformance(single, uDecodeMode);
+ if(uReturn != QCBOR_SUCCESS) {
+ break;
+ }
+
+#ifndef QCBOR_DISABLE_FLOAT_HW_USE
+ /* In the normal case, use HW to convert float to
+ * double. */
+ pDecodedItem->val.dfnum = (double)single;
+ pDecodedItem->uDataType = QCBOR_TYPE_DOUBLE;
+#else /* QCBOR_DISABLE_FLOAT_HW_USE */
+ /* Use of float HW is disabled, return as a float. */
+ pDecodedItem->val.fnum = single;
+ pDecodedItem->uDataType = QCBOR_TYPE_FLOAT;
+
+ /* IEEE754_FloatToDouble() could be used here to return as
+ * a double, but it adds object code and most likely
+ * anyone disabling FLOAT HW use doesn't care about floats
+ * and wants to save object code.
+ */
+#endif /* ! QCBOR_DISABLE_FLOAT_HW_USE */
+#endif /* ! USEFULBUF_DISABLE_ALL_FLOAT */
+ uReturn = FLOAT_ERR_CODE_NO_FLOAT(QCBOR_SUCCESS);
+ break;
+
+ case DOUBLE_PREC_FLOAT: /* 27 */
+#ifndef USEFULBUF_DISABLE_ALL_FLOAT
+ pDecodedItem->val.dfnum = UsefulBufUtil_CopyUint64ToDouble(uArgument);
+ pDecodedItem->uDataType = QCBOR_TYPE_DOUBLE;
+
+ uReturn = QCBORDecode_Private_DoubleConformance(pDecodedItem->val.dfnum, uDecodeMode);
+ if(uReturn != QCBOR_SUCCESS) {
+ break;
+ }
+#endif /* ! USEFULBUF_DISABLE_ALL_FLOAT */
+ uReturn = FLOAT_ERR_CODE_NO_FLOAT(QCBOR_SUCCESS);
+ break;
+ }
+
+ return uReturn;
+}
+
+
/* Make sure #define value line up as DecodeSimple counts on this. */
#if QCBOR_TYPE_FALSE != CBOR_SIMPLEV_FALSE
#error QCBOR_TYPE_FALSE macro value wrong
@@ -1166,7 +1400,6 @@
#error QCBOR_TYPE_FLOAT macro value wrong
#endif
-
/**
* @brief Decode major type 7 -- true, false, floating-point, break...
*
@@ -1182,7 +1415,8 @@
* type in input.
*/
static QCBORError
-QCBOR_Private_DecodeType7(const int nAdditionalInfo,
+QCBOR_Private_DecodeType7(const uint8_t uDecodeMode,
+ const int nAdditionalInfo,
const uint64_t uArgument,
QCBORItem *pDecodedItem)
{
@@ -1201,55 +1435,9 @@
*/
case HALF_PREC_FLOAT: /* 25 */
-#ifndef QCBOR_DISABLE_PREFERRED_FLOAT
- /* Half-precision is returned as a double. The cast to
- * uint16_t is safe because the encoded value was 16 bits. It
- * was widened to 64 bits to be passed in here.
- */
- pDecodedItem->val.dfnum = IEEE754_HalfToDouble((uint16_t)uArgument);
- pDecodedItem->uDataType = QCBOR_TYPE_DOUBLE;
-#endif /* QCBOR_DISABLE_PREFERRED_FLOAT */
- uReturn = FLOAT_ERR_CODE_NO_HALF_PREC(QCBOR_SUCCESS);
- break;
case SINGLE_PREC_FLOAT: /* 26 */
-#ifndef USEFULBUF_DISABLE_ALL_FLOAT
- /* Single precision is normally returned as a double since
- * double is widely supported, there is no loss of precision,
- * it makes it easy for the caller in most cases and it can
- * be converted back to single with no loss of precision
- *
- * The cast to uint32_t is safe because the encoded value was
- * 32 bits. It was widened to 64 bits to be passed in here.
- */
- {
- const float f = UsefulBufUtil_CopyUint32ToFloat((uint32_t)uArgument);
-#ifndef QCBOR_DISABLE_FLOAT_HW_USE
- /* In the normal case, use HW to convert float to
- * double. */
- pDecodedItem->val.dfnum = (double)f;
- pDecodedItem->uDataType = QCBOR_TYPE_DOUBLE;
-#else /* QCBOR_DISABLE_FLOAT_HW_USE */
- /* Use of float HW is disabled, return as a float. */
- pDecodedItem->val.fnum = f;
- pDecodedItem->uDataType = QCBOR_TYPE_FLOAT;
-
- /* IEEE754_FloatToDouble() could be used here to return as
- * a double, but it adds object code and most likely
- * anyone disabling FLOAT HW use doesn't care about floats
- * and wants to save object code.
- */
-#endif /* QCBOR_DISABLE_FLOAT_HW_USE */
- }
-#endif /* USEFULBUF_DISABLE_ALL_FLOAT */
- uReturn = FLOAT_ERR_CODE_NO_FLOAT(QCBOR_SUCCESS);
- break;
-
case DOUBLE_PREC_FLOAT: /* 27 */
-#ifndef USEFULBUF_DISABLE_ALL_FLOAT
- pDecodedItem->val.dfnum = UsefulBufUtil_CopyUint64ToDouble(uArgument);
- pDecodedItem->uDataType = QCBOR_TYPE_DOUBLE;
-#endif /* USEFULBUF_DISABLE_ALL_FLOAT */
- uReturn = FLOAT_ERR_CODE_NO_FLOAT(QCBOR_SUCCESS);
+ uReturn = QCBOR_Private_DecodeFloat(uDecodeMode, nAdditionalInfo, uArgument, pDecodedItem);
break;
case CBOR_SIMPLEV_FALSE: /* 20 */
@@ -1257,12 +1445,19 @@
case CBOR_SIMPLEV_NULL: /* 22 */
case CBOR_SIMPLEV_UNDEF: /* 23 */
case CBOR_SIMPLE_BREAK: /* 31 */
+#ifndef QCBOR_DISABLE_DECODE_CONFORMANCE
+ if(uDecodeMode >= QCBOR_ENCODE_MODE_DCBOR &&
+ nAdditionalInfo == CBOR_SIMPLEV_UNDEF) {
+ uReturn = QCBOR_ERR_DCBOR_CONFORMANCE;
+ goto Done;
+ }
+#endif /* ! QCBOR_DISABLE_DECODE_CONFORMANCE */
break; /* nothing to do */
case CBOR_SIMPLEV_ONEBYTE: /* 24 */
if(uArgument <= CBOR_SIMPLE_BREAK) {
/* This takes out f8 00 ... f8 1f which should be encoded
- * as e0 … f7
+ * as e0 … f7 -- preferred serialization check for simple values.
*/
uReturn = QCBOR_ERR_BAD_TYPE_7;
goto Done;
@@ -1270,8 +1465,16 @@
/* FALLTHROUGH */
default: /* 0-19 */
- pDecodedItem->uDataType = QCBOR_TYPE_UKNOWN_SIMPLE;
- /* DecodeHead() will make uArgument equal to
+#ifndef QCBOR_DISABLE_DECODE_CONFORMANCE
+ if(uDecodeMode >= QCBOR_ENCODE_MODE_DCBOR &&
+ (uArgument < CBOR_SIMPLEV_FALSE || uArgument > CBOR_SIMPLEV_NULL)) {
+ uReturn = QCBOR_ERR_DCBOR_CONFORMANCE;
+ goto Done;
+ }
+#endif /* !QCBOR_DISABLE_DECODE_CONFORMANCE */
+
+ pDecodedItem->uDataType = QCBOR_TYPE_UKNOWN_SIMPLE;
+ /* QCBOR_Private_DecodeHead() will make uArgument equal to
* nAdditionalInfo when nAdditionalInfo is < 24. This cast is
* safe because the 2, 4 and 8 byte lengths of uNumber are in
* the double/float cases above
@@ -1328,7 +1531,14 @@
/* Decode the "head" that every CBOR item has into the major type,
* argument and the additional info.
*/
- uReturn = QCBOR_Private_DecodeHead(&(pMe->InBuf), &nMajorType, &uArgument, &nAdditionalInfo);
+ uReturn = QCBOR_Private_DecodeHead(&(pMe->InBuf),
+#ifndef QCBOR_DISABLE_DECODE_CONFORMANCE
+ // TODO: make this prettier; will optimizer take out stuff without ifdef?
+ pMe->uDecodeMode >= QCBOR_DECODE_MODE_PREFERRED,
+#endif /* !QCBOR_DISABLE_DECODE_CONFORMANCE */
+ &nMajorType,
+ &uArgument,
+ &nAdditionalInfo);
if(uReturn != QCBOR_SUCCESS) {
return uReturn;
}
@@ -1361,7 +1571,7 @@
case CBOR_MAJOR_TYPE_SIMPLE:
/* Major type 7: float, double, true, false, null... */
- return QCBOR_Private_DecodeType7(nAdditionalInfo, uArgument, pDecodedItem);
+ return QCBOR_Private_DecodeType7(pMe->uDecodeMode, nAdditionalInfo, uArgument, pDecodedItem);
break;
default:
@@ -1442,7 +1652,6 @@
goto Done;
}
-
/* This is where out-of-place break is detected for the whole
* decoding stack. Break is an error for everything that calls
* QCBORDecode_Private_GetNextFullString(), so the check is
@@ -1762,13 +1971,13 @@
* This also implements maps-as-array mode where a map is treated like
* an array to allow caller to do their own label processing.
*/
-
static QCBORError
QCBORDecode_Private_GetNextMapEntry(QCBORDecodeContext *pMe,
- QCBORItem *pDecodedItem)
+ QCBORItem *pDecodedItem,
+ uint32_t *puLabelEndOffset)
{
QCBORItem LabelItem;
- QCBORError uErr;
+ QCBORError uErr, uErr2;
uErr = QCBORDecode_Private_GetNextTagNumber(pMe, pDecodedItem);
if(QCBORDecode_IsUnrecoverableError(uErr)) {
@@ -1786,12 +1995,22 @@
/* Decoding a map entry, so the item decoded above was the label */
LabelItem = *pDecodedItem;
+
+ if(puLabelEndOffset != NULL) {
+ /* Cast is OK because lengths are all 32-bit in QCBOR */
+ *puLabelEndOffset = (uint32_t)UsefulInputBuf_Tell(&(pMe->InBuf));
+ }
/* Get the value of the map item */
- uErr = QCBORDecode_Private_GetNextTagNumber(pMe, pDecodedItem);
- if(QCBORDecode_IsUnrecoverableError(uErr)) {
+ uErr2 = QCBORDecode_Private_GetNextTagNumber(pMe, pDecodedItem);
+ if(QCBORDecode_IsUnrecoverableError(uErr2)) {
+ uErr = uErr2;
goto Done;
}
+ if(uErr2 != QCBOR_SUCCESS) {
+ /* The recoverable error for the value overrides the recoverable error for the label, if there was an error for the label */
+ uErr = uErr2;
+ }
/* Combine the label item and value item into one */
pDecodedItem->uLabelAlloc = LabelItem.uDataAlloc;
@@ -1825,8 +2044,15 @@
#endif /* ! QCBOR_DISABLE_NON_INTEGER_LABELS */
default:
- uErr = QCBOR_ERR_MAP_LABEL_TYPE;
- goto Done;
+ /* It is possible to skip over labels that are non-aggregate
+ * types like floats, but not to skip over labels that are
+ * arrays or maps. We might eventually handle more label
+ * types like floats as they are not too hard and we now
+ * have QCBOR_DISABLE_NON_INTEGER_LABELS */
+ if(!pMe->bAllowAllLabels || QCBORItem_IsMapOrArray(LabelItem)) {
+ uErr = QCBOR_ERR_MAP_LABEL_TYPE;
+ goto Done;
+ }
}
Done:
@@ -2024,7 +2250,8 @@
static QCBORError
QCBORDecode_Private_GetNextMapOrArray(QCBORDecodeContext *pMe,
bool *pbBreak,
- QCBORItem *pDecodedItem)
+ QCBORItem *pDecodedItem,
+ uint32_t *puLabelEndOffset)
{
QCBORError uReturn;
/* ==== First: figure out if at the end of a traversal ==== */
@@ -2052,7 +2279,7 @@
}
/* ==== Next: not at the end, so get another item ==== */
- uReturn = QCBORDecode_Private_GetNextMapEntry(pMe, pDecodedItem);
+ uReturn = QCBORDecode_Private_GetNextMapEntry(pMe, pDecodedItem, puLabelEndOffset);
if(QCBORDecode_IsUnrecoverableError(uReturn)) {
/* Error is so bad that traversal is not possible. */
goto Done;
@@ -2624,7 +2851,7 @@
{
QCBORError uReturn;
- uReturn = QCBORDecode_Private_GetNextMapOrArray(pMe, NULL, pDecodedItem);
+ uReturn = QCBORDecode_Private_GetNextMapOrArray(pMe, NULL, pDecodedItem, NULL);
if(uReturn != QCBOR_SUCCESS) {
goto Done;
}
@@ -2700,6 +2927,223 @@
}
+/**
+ * @brief Consume an entire map or array including its contents.
+ *
+ * @param[in] pMe The decoder context.
+ * @param[in] pItemToConsume The array/map whose contents are to be
+ * consumed.
+ * @param[out] puNextNestLevel The next nesting level after the item was
+ * fully consumed.
+ *
+ * This may be called when @c pItemToConsume is not an array or
+ * map. In that case, this is just a pass through for @c puNextNestLevel
+ * since there is nothing to do.
+ */
+static QCBORError
+QCBORDecode_Private_ConsumeItem(QCBORDecodeContext *pMe,
+ const QCBORItem *pItemToConsume,
+ bool *pbBreak,
+ uint8_t *puNextNestLevel)
+{
+ QCBORError uReturn;
+ QCBORItem Item;
+
+ /* If it is a map or array, this will tell if it is empty. */
+ const bool bIsEmpty = (pItemToConsume->uNextNestLevel <= pItemToConsume->uNestingLevel);
+
+ if(QCBORItem_IsMapOrArray(*pItemToConsume) && !bIsEmpty) {
+ /* There is only real work to do for non-empty maps and arrays */
+
+ /* This works for definite- and indefinite-length maps and
+ * arrays by using the nesting level
+ */
+ do {
+ uReturn = QCBORDecode_Private_GetNextMapOrArray(pMe, pbBreak, &Item, NULL);
+ if(QCBORDecode_IsUnrecoverableError(uReturn) ||
+ uReturn == QCBOR_ERR_NO_MORE_ITEMS) {
+ goto Done;
+ }
+ } while(Item.uNextNestLevel >= pItemToConsume->uNextNestLevel);
+
+ *puNextNestLevel = Item.uNextNestLevel;
+
+ uReturn = QCBOR_SUCCESS;
+
+ } else {
+ /* pItemToConsume is not a map or array. Just pass the nesting
+ * level through. */
+ *puNextNestLevel = pItemToConsume->uNextNestLevel;
+
+ uReturn = QCBOR_SUCCESS;
+ }
+
+Done:
+ return uReturn;
+}
+
+
+/*
+ *
+ * This consumes the next item. It returns the starting position of
+ * the label and the length of the label. It also returns the nest
+ * level of the item consumed.
+ */
+static QCBORError
+QCBORDecode_Private_GetLabelAndConsume(QCBORDecodeContext *pMe,
+ uint8_t *puNestLevel,
+ size_t *puLabelStart,
+ size_t *puLabelLen)
+{
+ QCBORError uErr;
+ QCBORItem Item;
+ uint8_t uLevel;
+ uint32_t uLabelOffset;
+
+ /* Get the label and consume it should it be complex */
+ *puLabelStart = UsefulInputBuf_Tell(&(pMe->InBuf));
+
+ uErr = QCBORDecode_Private_GetNextMapOrArray(pMe, NULL, &Item, &uLabelOffset);
+ if(uErr != QCBOR_SUCCESS) {
+ goto Done;
+ }
+ *puLabelLen = uLabelOffset - *puLabelStart;
+ *puNestLevel = Item.uNestingLevel;
+ uErr = QCBORDecode_Private_ConsumeItem(pMe, &Item, NULL, &uLevel);
+
+Done:
+ return uErr;
+}
+
+
+/* Loop over items in a map until the end of the map looking for
+ * duplicates. This starts at the current position in the map, not at
+ * the beginning of the map.
+ *
+ * This saves and restores the traversal cursor and nest tracking so
+ * they are the same on exit as they were on entry.
+ */
+static QCBORError
+QCBORDecode_Private_CheckDups(QCBORDecodeContext *pMe,
+ const uint8_t uNestLevel,
+ const size_t uCompareLabelStart,
+ const size_t uCompareLabelLen)
+{
+ QCBORError uErr;
+ size_t uLabelStart;
+ size_t uLabelLen;
+ uint8_t uLevel;
+ int nCompare;
+
+ const QCBORDecodeNesting SaveNesting = pMe->nesting;
+ const UsefulInputBuf Save = pMe->InBuf;
+
+ do {
+ uErr = QCBORDecode_Private_GetLabelAndConsume(pMe, &uLevel, &uLabelStart, &uLabelLen);
+ if(uErr != QCBOR_SUCCESS) {
+ if(uErr == QCBOR_ERR_NO_MORE_ITEMS) {
+ uErr = QCBOR_SUCCESS; /* Successful end */
+ }
+ break;
+ }
+
+ if(uLevel != uNestLevel) {
+ break; /* Successful end of loop */
+ }
+
+ /* This check for dups works for labels that are preferred
+ * serialization and are not maps. If the labels are not in
+ * preferred serialization, then the check has to be more
+ * complicated and is type-specific because it uses the decoded
+ * value, not the encoded CBOR. It is further complicated for
+ * maps because the order of items in a map that is a label
+ * doesn't matter when checking that is is the duplicate of
+ * another map that is a label. QCBOR so far only turns on this
+ * dup checking as part of CDE checking which requires preferred
+ * serialization. See 5.6 in RFC 8949.
+ */
+ nCompare = UsefulInputBuf_Compare(&(pMe->InBuf),
+ uCompareLabelStart, uCompareLabelLen,
+ uLabelStart, uLabelLen);
+ if(nCompare == 0) {
+ uErr = QCBOR_ERR_DUPLICATE_LABEL;
+ break;
+ }
+ } while (1);
+
+ pMe->nesting = SaveNesting;
+ pMe->InBuf = Save;
+
+ return uErr;
+}
+
+
+/* This does sort order and duplicate detection on a map. The and all
+ * it's members must be in preferred serialization so the comparisons
+ * work correctly.
+ */
+static QCBORError
+QCBORDecode_Private_CheckMap(QCBORDecodeContext *pMe, const QCBORItem *pMapToCheck)
+{
+ QCBORError uErr;
+ uint8_t uNestLevel;
+ size_t offset2, offset1, length2, length1;
+
+ const QCBORDecodeNesting SaveNesting = pMe->nesting;
+ const UsefulInputBuf Save = pMe->InBuf;
+ pMe->bAllowAllLabels = 1;
+
+ /* This loop runs over all the items in the map once, comparing
+ * each adjacent pair for correct ordering. It also calls CheckDup
+ * on each one which also runs over the remaining items in the map
+ * checking for duplicates. So duplicate checking runs in n^2.
+ */
+
+ offset2 = SIZE_MAX;
+ length2 = SIZE_MAX; // To avoid uninitialized warning
+ while(1) {
+ uErr = QCBORDecode_Private_GetLabelAndConsume(pMe, &uNestLevel, &offset1, &length1);
+ if(uErr != QCBOR_SUCCESS) {
+ if(uErr == QCBOR_ERR_NO_MORE_ITEMS) {
+ uErr = QCBOR_SUCCESS; /* Successful exit from loop */
+ }
+ break;
+ }
+
+ if(uNestLevel < pMapToCheck->uNextNestLevel) {
+ break; /* Successful exit from loop */
+ }
+
+ if(offset2 != SIZE_MAX) {
+ /* Check that the labels are ordered. Check is not done the
+ * first time through the loop when offset2 is unset. Since
+ * this does comparison of the items in encoded form they
+ * must be preferred serialization encoded. See RFC 8949
+ * 4.2.1.
+ */
+ if(UsefulInputBuf_Compare(&(pMe->InBuf), offset2, length2, offset1, length1) > 0) {
+ uErr = QCBOR_ERR_UNSORTED;
+ break;
+ }
+ }
+
+ uErr = QCBORDecode_Private_CheckDups(pMe, pMapToCheck->uNextNestLevel, offset1, length1);
+ if(uErr != QCBOR_SUCCESS) {
+ break;
+ }
+
+ offset2 = offset1;
+ length2 = length1;
+ }
+
+ pMe->bAllowAllLabels = 0;
+ pMe->nesting = SaveNesting;
+ pMe->InBuf = Save;
+
+ return uErr;
+}
+
+
/*
* Public function, see header qcbor/qcbor_decode.h file
*/
@@ -2707,7 +3151,13 @@
QCBORDecode_GetNext(QCBORDecodeContext *pMe, QCBORItem *pDecodedItem)
{
QCBORError uErr;
- uErr = QCBORDecode_Private_GetNextTagContent(pMe, pDecodedItem);
+ uErr = QCBORDecode_Private_GetNextTagContent(pMe, pDecodedItem);
+#ifndef QCBOR_DISABLE_CONFORMANCE
+ if(uErr == QCBOR_SUCCESS && pMe->uDecodeMode >= QCBOR_ENCODE_MODE_CDE && pDecodedItem->uDataType == QCBOR_TYPE_MAP) {
+ /* Traverse map checking sort order and for duplicates */
+ uErr = QCBORDecode_Private_CheckMap(pMe, pDecodedItem);
+ }
+#endif /* ! QCBOR_DISABLE_CONFORMANCE */
if(uErr != QCBOR_SUCCESS) {
pDecodedItem->uDataType = QCBOR_TYPE_NONE;
pDecodedItem->uLabelType = QCBOR_TYPE_NONE;
@@ -3143,63 +3593,6 @@
-
-/**
- * @brief Consume an entire map or array including its contents.
- *
- * @param[in] pMe The decoder context.
- * @param[in] pItemToConsume The array/map whose contents are to be
- * consumed.
- * @param[out] puNextNestLevel The next nesting level after the item was
- * fully consumed.
- *
- * This may be called when @c pItemToConsume is not an array or
- * map. In that case, this is just a pass through for @c puNextNestLevel
- * since there is nothing to do.
- */
-static QCBORError
-QCBORDecode_Private_ConsumeItem(QCBORDecodeContext *pMe,
- const QCBORItem *pItemToConsume,
- bool *pbBreak,
- uint8_t *puNextNestLevel)
-{
- QCBORError uReturn;
- QCBORItem Item;
-
- /* If it is a map or array, this will tell if it is empty. */
- const bool bIsEmpty = (pItemToConsume->uNextNestLevel <= pItemToConsume->uNestingLevel);
-
- if(QCBORItem_IsMapOrArray(*pItemToConsume) && !bIsEmpty) {
- /* There is only real work to do for non-empty maps and arrays */
-
- /* This works for definite- and indefinite-length maps and
- * arrays by using the nesting level
- */
- do {
- uReturn = QCBORDecode_Private_GetNextMapOrArray(pMe, pbBreak, &Item);
- if(QCBORDecode_IsUnrecoverableError(uReturn) ||
- uReturn == QCBOR_ERR_NO_MORE_ITEMS) {
- goto Done;
- }
- } while(Item.uNextNestLevel >= pItemToConsume->uNextNestLevel);
-
- *puNextNestLevel = Item.uNextNestLevel;
-
- uReturn = QCBOR_SUCCESS;
-
- } else {
- /* pItemToConsume is not a map or array. Just pass the nesting
- * level through. */
- *puNextNestLevel = pItemToConsume->uNextNestLevel;
-
- uReturn = QCBOR_SUCCESS;
- }
-
-Done:
- return uReturn;
-}
-
-
/*
* Public function, see header qcbor/qcbor_decode.h file
*/
@@ -3648,8 +4041,8 @@
bInMap = DecodeNesting_IsCurrentTypeMap(&(pMe->nesting));
/* Could call GetNext here, but don't need to because this
- * is only interested in arrays and maps. */
- uErr = QCBORDecode_Private_GetNextMapOrArray(pMe, NULL, pItem);
+ * is only interested in arrays and maps. TODO: switch to GetNext()? */
+ uErr = QCBORDecode_Private_GetNextMapOrArray(pMe, NULL, pItem, NULL);
if(uErr != QCBOR_SUCCESS) {
pMe->uLastError = (uint8_t)uErr;
return;