CDE and dCBOR encode-side support (#193)
A mode that errors out if non-preferred serialization is attempted.
Layered on top of that is CDE mode that always sorts maps.
Layered on top of the dCBOR mode, that disallows a few things and unifies encoding of the float and integer number spaces. There is no change to decoding, though some are planned.
NaN Payloads can no longer be output by default. You must explicitly allow them. This is a non-compatibility with QCBOR 1.x
65-bit negative integers also cannot be output without explicitly allowing them.
* CDE and dCBOR support
* Tests for single precision and exponent boundaries
* test complete and passing; code clean up; bug fix
* Add comments for encoding modes
* tidy up and documentation
* Check point progress on CDE, preferred and dCBOR
* documentation updates
* Minor test fixes; minor code improvements
* full test fan out passes
* Formatting and documentation nits
---------
Co-authored-by: Laurence Lundblade <lgl@securitytheory.com>
diff --git a/src/qcbor_encode.c b/src/qcbor_encode.c
index 767ce58..37b1c89 100644
--- a/src/qcbor_encode.c
+++ b/src/qcbor_encode.c
@@ -35,6 +35,10 @@
#include "qcbor/qcbor_encode.h"
#include "ieee754.h"
+#ifndef QCBOR_DISABLE_PREFERRED_FLOAT
+#include <math.h> /* Only for NAN definition */
+#endif /* ! QCBOR_DISABLE_ENCODE_USAGE_GUARDS */
+
/**
* @file qcbor_encode.c
@@ -248,6 +252,11 @@
*/
+/* Forward declaration for reference in QCBOREncode_Init() */
+static void
+QCBOREncode_Private_CloseMapUnsorted(QCBOREncodeContext *pMe);
+
+
/*
* Public function for initialization. See qcbor/qcbor_encode.h
*/
@@ -257,6 +266,7 @@
memset(pMe, 0, sizeof(QCBOREncodeContext));
UsefulOutBuf_Init(&(pMe->OutBuf), Storage);
Nesting_Init(&(pMe->nesting));
+ pMe->pfnCloseMap = QCBOREncode_Private_CloseMapUnsorted;
}
@@ -505,7 +515,7 @@
* @param pMe Encoder context.
* @param uMajorType Major type to insert.
* @param uArgument The argument (an integer value or a length).
- * @param uMinLen The minimum number of bytes for encoding the CBOR argument.
+ * @param uMinLen Minimum number of bytes for encoding the CBOR argument.
*
* This formats the CBOR "head" and appends it to the output.
*/
@@ -667,9 +677,22 @@
/*
* Public functions for adding negative integers. See qcbor/qcbor_encode.h
*/
-void QCBOREncode_AddNegativeUInt64(QCBOREncodeContext *pMe, const uint64_t uValue)
+void
+QCBOREncode_AddNegativeUInt64(QCBOREncodeContext *pMe, const uint64_t uValue)
{
- // TODO: Error out in dCBOR mode
+#ifndef QCBOR_DISABLE_ENCODE_USAGE_GUARDS
+ if(pMe->uMode >= QCBOR_ENCODE_MODE_DCBOR) {
+ /* Never allowed in dCBOR */
+ pMe->uError = QCBOR_ERR_NOT_PREFERRED;
+ return;
+ }
+
+ if(!(pMe->uAllow & QCBOR_ENCODE_ALLOW_65_BIG_NEG)) {
+ pMe->uError = QCBOR_ERR_NOT_ALLOWED;
+ return;
+ }
+#endif /* QCBOR_DISABLE_ENCODE_USAGE_GUARDS */
+
QCBOREncode_Private_AppendCBORHead(pMe, CBOR_MAJOR_TYPE_NEGATIVE_INT, uValue, 0);
QCBOREncode_Private_IncrementMapOrArrayCount(pMe);
@@ -809,6 +832,17 @@
void
QCBOREncode_AddDoubleNoPreferred(QCBOREncodeContext *pMe, const double dNum)
{
+#ifndef QCBOR_DISABLE_ENCODE_USAGE_GUARDS
+ if(pMe->uMode >= QCBOR_ENCODE_MODE_PREFERRED) {
+ pMe->uError = QCBOR_ERR_NOT_PREFERRED;
+ return;
+ }
+ if(IEEE754_IsNotStandardDoubleNaN(dNum) && !(pMe->uAllow & QCBOR_ENCODE_ALLOW_NAN_PAYLOAD)) {
+ pMe->uError = QCBOR_ERR_NOT_ALLOWED;
+ return;
+ }
+#endif /* ! QCBOR_DISABLE_ENCODE_USAGE_GUARDS */
+
QCBOREncode_Private_AddType7(pMe,
sizeof(uint64_t),
UsefulBufUtil_CopyDoubleToUint64(dNum));
@@ -819,42 +853,50 @@
* Public functions for adding a double. See qcbor/qcbor_encode.h
*/
void
-QCBOREncode_AddDouble(QCBOREncodeContext *pMe, const double dNum)
+QCBOREncode_AddDouble(QCBOREncodeContext *pMe, double dNum)
{
#ifndef QCBOR_DISABLE_PREFERRED_FLOAT
- const IEEE754_union uNum = IEEE754_DoubleToSmaller(dNum, true);
+ IEEE754_union FloatResult;
+ bool bNoNaNPayload;
+ struct IEEE754_ToInt IntResult;
- QCBOREncode_Private_AddType7(pMe, (uint8_t)uNum.uSize, uNum.uValue);
+#ifndef QCBOR_DISABLE_ENCODE_USAGE_GUARDS
+ if(IEEE754_IsNotStandardDoubleNaN(dNum) && !(pMe->uAllow & QCBOR_ENCODE_ALLOW_NAN_PAYLOAD)) {
+ pMe->uError = QCBOR_ERR_NOT_ALLOWED;
+ return;
+ }
+#endif /* ! QCBOR_DISABLE_ENCODE_USAGE_GUARDS */
+
+ if(pMe->uMode == QCBOR_ENCODE_MODE_DCBOR) {
+ IntResult = IEEE754_DoubleToInt(dNum);
+ switch(IntResult.type) {
+ case IEEE754_ToInt_IS_INT:
+ QCBOREncode_AddInt64(pMe, IntResult.integer.is_signed);
+ return;
+ case IEEE754_ToInt_IS_UINT:
+ QCBOREncode_AddUInt64(pMe, IntResult.integer.un_signed);
+ return;
+ case IEEE754_ToInt_NaN:
+ dNum = NAN;
+ bNoNaNPayload = true;
+ break;
+ case IEEE754_ToInt_NO_CONVERSION:
+ bNoNaNPayload = true;
+ }
+ } else {
+ bNoNaNPayload = false;
+ }
+
+ FloatResult = IEEE754_DoubleToSmaller(dNum, true, bNoNaNPayload);
+
+ QCBOREncode_Private_AddType7(pMe, (uint8_t)FloatResult.uSize, FloatResult.uValue);
+
#else /* QCBOR_DISABLE_PREFERRED_FLOAT */
QCBOREncode_AddDoubleNoPreferred(pMe, dNum);
#endif /* QCBOR_DISABLE_PREFERRED_FLOAT */
}
-/*
- * Public functions for adding a double. See qcbor/qcbor_encode.h
- */
-void QCBOREncode_AddDoubleDeterministic(QCBOREncodeContext *me, double dNum)
-{
- if(dNum <= (double)UINT64_MAX && dNum >= 0) {
- uint64_t uNum = (uint64_t)dNum;
- if((double)uNum == dNum) {
- QCBOREncode_AddUInt64(me, uNum);
- return;
- }
- /* Fall through */
- } else if(dNum >= (double)INT64_MIN && dNum < 0) {
- int64_t nNum = (int64_t)dNum;
- if((double)nNum == dNum) {
- QCBOREncode_AddInt64(me, nNum);
- return;
- }
- /* Fall through */
- }
- //const IEEE754_union uNum = IEEE754_DoubleToSmallest(dNum);
-
- //QCBOREncode_AddType7(me, uNum.uSize, uNum.uValue);
-}
/*
@@ -863,6 +905,16 @@
void
QCBOREncode_AddFloatNoPreferred(QCBOREncodeContext *pMe, const float fNum)
{
+#ifndef QCBOR_DISABLE_ENCODE_USAGE_GUARDS
+ if(pMe->uMode >= QCBOR_ENCODE_MODE_PREFERRED) {
+ pMe->uError = QCBOR_ERR_NOT_PREFERRED;
+ return;
+ }
+ if(IEEE754_IsNotStandardSingleNaN(fNum) && !(pMe->uAllow & QCBOR_ENCODE_ALLOW_NAN_PAYLOAD)) {
+ pMe->uError = QCBOR_ERR_NOT_ALLOWED;
+ return;
+ }
+#endif /* ! QCBOR_DISABLE_ENCODE_USAGE_GUARDS */
QCBOREncode_Private_AddType7(pMe,
sizeof(uint32_t),
UsefulBufUtil_CopyFloatToUint32(fNum));
@@ -873,12 +925,45 @@
* Public functions for adding a float. See qcbor/qcbor_encode.h
*/
void
-QCBOREncode_AddFloat(QCBOREncodeContext *pMe, const float fNum)
+QCBOREncode_AddFloat(QCBOREncodeContext *pMe, float fNum)
{
#ifndef QCBOR_DISABLE_PREFERRED_FLOAT
- const IEEE754_union uNum = IEEE754_SingleToHalf(fNum);
+ IEEE754_union FloatResult;
+ bool bNoNaNPayload;
+ struct IEEE754_ToInt IntResult;
- QCBOREncode_Private_AddType7(pMe, (uint8_t)uNum.uSize, uNum.uValue);
+#ifndef QCBOR_DISABLE_ENCODE_USAGE_GUARDS
+ if(IEEE754_IsNotStandardSingleNaN(fNum) && !(pMe->uAllow & QCBOR_ENCODE_ALLOW_NAN_PAYLOAD)) {
+ pMe->uError = QCBOR_ERR_NOT_ALLOWED;
+ return;
+ }
+#endif /* ! QCBOR_DISABLE_ENCODE_USAGE_GUARDS */
+
+
+ if(pMe->uMode == QCBOR_ENCODE_MODE_DCBOR) {
+ IntResult = IEEE754_SingleToInt(fNum);
+ switch(IntResult.type) {
+ case IEEE754_ToInt_IS_INT:
+ QCBOREncode_AddInt64(pMe, IntResult.integer.is_signed);
+ return;
+ case IEEE754_ToInt_IS_UINT:
+ QCBOREncode_AddUInt64(pMe, IntResult.integer.un_signed);
+ return;
+ case IEEE754_ToInt_NaN:
+ fNum = NAN;
+ bNoNaNPayload = true;
+ break;
+ case IEEE754_ToInt_NO_CONVERSION:
+ bNoNaNPayload = true;
+ }
+ } else {
+ bNoNaNPayload = false;
+ }
+
+ FloatResult = IEEE754_SingleToHalf(fNum, bNoNaNPayload);
+
+ QCBOREncode_Private_AddType7(pMe, (uint8_t)FloatResult.uSize, FloatResult.uValue);
+
#else /* QCBOR_DISABLE_PREFERRED_FLOAT */
QCBOREncode_AddFloatNoPreferred(pMe, fNum);
#endif /* QCBOR_DISABLE_PREFERRED_FLOAT */
@@ -1007,6 +1092,12 @@
QCBOREncode_Private_OpenMapOrArrayIndefiniteLength(QCBOREncodeContext *pMe,
const uint8_t uMajorType)
{
+#ifndef QCBOR_DISABLE_ENCODE_USAGE_GUARDS
+ if(pMe->uMode >= QCBOR_ENCODE_MODE_PREFERRED) {
+ pMe->uError = QCBOR_ERR_NOT_PREFERRED;
+ return;
+ }
+#endif /* ! QCBOR_DISABLE_ENCODE_USAGE_GUARDS */
/* Insert the indefinite length marker (0x9f for arrays, 0xbf for maps) */
QCBOREncode_Private_AppendCBORHead(pMe, uMajorType, 0, 0);
@@ -1019,12 +1110,10 @@
/**
- * @brief Semi-private method to close a map, array or bstr wrapped CBOR
+ * @brief Semi-private method to close a map, array or bstr wrapped CBOR.
*
* @param[in] pMe The context to add to.
* @param[in] uMajorType The major CBOR type to close.
- *
- * Call QCBOREncode_CloseArray() or QCBOREncode_CloseMap() instead of this.
*/
void
QCBOREncode_Private_CloseMapOrArray(QCBOREncodeContext *pMe,
@@ -1034,6 +1123,20 @@
}
+/**
+ * @brief Private method to close a map without sorting.
+ *
+ * @param[in] pMe The encode context with map to close.
+ *
+ * See QCBOREncode_SerializationCDE() implemention for explantion for why
+ * this exists in this form.
+ */
+static void
+QCBOREncode_Private_CloseMapUnsorted(QCBOREncodeContext *pMe)
+{
+ QCBOREncode_Private_CloseMapOrArray(pMe, CBOR_MAJOR_TYPE_MAP);
+}
+
/**
* @brief Decode a CBOR item head.