Merge branch 'dev' into CDE
diff --git a/inc/qcbor/qcbor_common.h b/inc/qcbor/qcbor_common.h
index 07de6c7..9ae2123 100644
--- a/inc/qcbor/qcbor_common.h
+++ b/inc/qcbor/qcbor_common.h
@@ -1,6 +1,6 @@
/* ==========================================================================
* Copyright (c) 2016-2018, The Linux Foundation.
- * Copyright (c) 2018-2023, Laurence Lundblade.
+ * Copyright (c) 2018-2024, Laurence Lundblade.
* Copyright (c) 2021, Arm Limited.
* All rights reserved.
*
@@ -521,7 +521,14 @@
* 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,
+
+ /** Attempt to output non-preferred, non-CDE or non-dCBOR when not
+ * allowed by mode. See QCBOREncode_SerializationPreferred(),
+ * QCBOREncode_SerializationCDE(),
+ * QCBOREncode_SerializationdCBOR().
+ */
+ QCBOR_ERR_NOT_PREFERRED = 79
/* This is stored in uint8_t; never add values > 255 */
} QCBORError;
diff --git a/inc/qcbor/qcbor_encode.h b/inc/qcbor/qcbor_encode.h
index 8e3d66a..27a4396 100644
--- a/inc/qcbor/qcbor_encode.h
+++ b/inc/qcbor/qcbor_encode.h
@@ -435,7 +435,7 @@
* is too small, encoding will go into an error state and not write
* anything further.
*
- * If allocating on the stack the convenience macro
+ * If allocating on the stack, the convenience macro
* UsefulBuf_MAKE_STACK_UB() can be used, but its use is not required.
*
* Since there is no reallocation or such, the output buffer must be
@@ -469,6 +469,89 @@
/**
+ * @brief Select preferred serialization mode.
+ *
+ * @param[in] pCtx The encoding context for mode set.
+ *
+ * Setting this mode will cause QCBOR to return an error if an attempt
+ * is made to use one of the methods that produce non-preferred
+ * serialization. It doesn't change anything else as QCBOR produces
+ * preferred serialization by default.
+ *
+ * The non-preferred methods are: QCBOREncode_AddFloatNoPreferred(),
+ * QCBOREncode_AddDoubleNoPreferred(),
+ * QCBOREncode_OpenArrayIndefiniteLength(),
+ * QCBOREncode_CloseArrayIndefiniteLength(),
+ * QCBOREncode_OpenMapIndefiniteLength(),
+ * QCBOREncode_CloseMapIndefiniteLength(), plus those derived from the
+ * above listed.
+ *
+ * This mode is just a user guard to prevent accidentally calling
+ * something that produces non-preferred serialization. It doesn't do
+ * anything but causes errors to occur on attempts to call the above
+ * listed functions.
+ */
+static void
+QCBOREncode_SerializationPreferred(QCBOREncodeContext *pCtx);
+
+
+/**
+ * @brief Select CBOR deterministic encoding mode.
+ *
+ * @param[in] pCtx The encoding context for mode set.
+
+ * This causes QCBOR to produce CBOR Deterministic Encoding. With
+ * CDE, two distant unrelated CBOR encoders will produce exactly the
+ * same encoded CBOR for a given input. See serialization discussion
+ * TODO:
+ *
+ * In addition to doing what QCBOREncode_SerializationPreferred()
+ * does, this causes maps to be sorted. The map is sorted
+ * automatically when QCBOREncode_CloseMap() is called.
+ * QCBOREncode_CloseMap() becomes equivalent to
+ * QCBOREncode_CloseAndSortMap().
+ *
+ * Note that linking this function causese about 30% more code from
+ * the QCBOR library to be linked. Also, QCBOREncode_CloseMap() runs
+ * slower, but this is probably only of consequence in very
+ * constrained environments.
+ */
+static void
+QCBOREncode_SerializationCDE(QCBOREncodeContext *pCtx);
+
+
+/**
+ * @brief Select "dCBOR" encoding mode.
+ *
+ * @param[in] pCtx The encoding context for mode set.
+ *
+ * This cases QCBOR to produce "dCBOR" as defined
+ * in draft-mcnally-deterministic-cbor.
+ *
+ * This is a superset of CDE and this function does everything
+ * QCBOREncode_SerializationCDE() does, plus the following.
+ *
+ * The main feature of dCBOR that there is only one way to serialize a
+ * particular numeric value. This changes the behavior of functions
+ * that add floating point numbers. If the floating-point number is
+ * whole, it will be added as an integer, not a floating-point number.
+ *
+ * dCBOR also disallows NaN payloads. QCBOR will allow NaN payloads if
+ * you pass a NaN to one of the floating-point encoding functions.
+ * This mode forces all NaNs to the half-precision queit NaN. TODO:
+ * should this error if an attempt is made to send a NaN payload?
+
+ * dCBOR also disallows 65-bit negative integers. QCBOR doesn't
+ * produce these by default.
+ *
+ * dCBOR disallows use of the simple type "undef" produced by
+ * QCBOREncode_AddUndef().
+ */
+static void
+QCBOREncode_SerializationdCBOR(QCBOREncodeContext *pCtx);
+
+
+/**
* @brief Add a signed 64-bit integer to the encoded output.
*
* @param[in] pCtx The encoding context to add the integer to.
@@ -2053,7 +2136,8 @@
* This is more expensive than most things in the encoder. It uses
* bubble sort which runs in n-squared time where n is the number of
* map items. Sorting large maps on slow CPUs might be slow. This is
- * also increases the object code size of the encoder by about 30%.
+ * also increases the object code size of the encoder by about 30%
+ * (500-1000 bytes).
*
* Bubble sort was selected so as to not need an extra buffer to track
* map item offsets. Bubble sort works well even though map items are
@@ -2413,6 +2497,36 @@
int64_t nExponent);
+static inline void
+QCBOREncode_SerializationCDE(QCBOREncodeContext *pMe)
+{
+ /* The use of a function pointer here is a little trick to reduce
+ * code linked for the common use cases that don't sort. If this
+ * function is never linked, then QCBOREncode_CloseAndSortMap() is
+ * never linked and the amount of code pulled in is small. If the
+ * mode switch between sorting and not sorting were an if
+ * statement, then QCBOREncode_CloseAndSortMap() would always be
+ * linked even when not used. */
+ pMe->pfnCloseMap = QCBOREncode_CloseAndSortMap;
+ pMe->uMode = QCBOR_ENCODE_MODE_CDE;
+}
+
+
+static inline void
+QCBOREncode_SerializationdCBOR(QCBOREncodeContext *pMe)
+{
+ pMe->pfnCloseMap = QCBOREncode_CloseAndSortMap;
+ pMe->uMode = QCBOR_ENCODE_MODE_DCBOR;
+}
+
+
+static inline void
+QCBOREncode_SerializationPreferred(QCBOREncodeContext *pMe)
+{
+ pMe->uMode = QCBOR_ENCODE_MODE_PREFERRED;
+}
+
+
static inline void
QCBOREncode_AddInt64ToMap(QCBOREncodeContext *pMe,
@@ -3719,6 +3833,11 @@
static inline void
QCBOREncode_AddUndef(QCBOREncodeContext *pMe)
{
+ // TODO: Conditional on usage guard
+ if(pMe->uMode >= QCBOR_ENCODE_MODE_DCBOR) {
+ pMe->uError = QCBOR_ERR_NOT_PREFERRED;
+ return;
+ }
QCBOREncode_Private_AddSimple(pMe, CBOR_SIMPLEV_UNDEF);
}
@@ -3757,6 +3876,7 @@
QCBOREncode_OpenArray(pMe);
}
+
static inline void
QCBOREncode_CloseArray(QCBOREncodeContext *pMe)
{
@@ -3787,7 +3907,7 @@
static inline void
QCBOREncode_CloseMap(QCBOREncodeContext *pMe)
{
- QCBOREncode_Private_CloseMapOrArray(pMe, CBOR_MAJOR_TYPE_MAP);
+ (pMe->pfnCloseMap)(pMe);
}
static inline void
diff --git a/inc/qcbor/qcbor_private.h b/inc/qcbor/qcbor_private.h
index c1ec8d1..449e72d 100644
--- a/inc/qcbor/qcbor_private.h
+++ b/inc/qcbor/qcbor_private.h
@@ -1,6 +1,6 @@
/* ==========================================================================
* Copyright (c) 2016-2018, The Linux Foundation.
- * Copyright (c) 2018-2023, Laurence Lundblade.
+ * Copyright (c) 2018-2024, Laurence Lundblade.
* Copyright (c) 2021, Arm Limited.
* All rights reserved.
*
@@ -215,14 +215,24 @@
* functions to form a public "object" that does the job of encdoing.
*
* Size approximation (varies with CPU/compiler):
- * 64-bit machine: 27 + 1 (+ 4 padding) + 136 = 32 + 136 = 168 bytes
+ * 64-bit machine: 27 + 1 (+ 4 padding) + 136 = 32 + 136 = 168 bytes
* 32-bit machine: 15 + 1 + 132 = 148 bytes
*/
+typedef struct _QCBOREncodeContext QCBORPrivateEncodeContext;
+/* These are in order of increasing strictness, and that is relied upon in implementation */
+#define QCBOR_ENCODE_MODE_ANY 0
+#define QCBOR_ENCODE_MODE_PREFERRED 1
+#define QCBOR_ENCODE_MODE_CDE 2
+#define QCBOR_ENCODE_MODE_DCBOR 3
+
struct _QCBOREncodeContext {
/* PRIVATE DATA STRUCTURE */
UsefulOutBuf OutBuf; /* Pointer to output buffer, its length and
* position in it. */
uint8_t uError; /* Error state, always from QCBORError enum */
+ uint8_t uMode; /* QCBOR_ENCODE_MODE_PREFERRED or related */
+ void (*pfnCloseMap)(QCBORPrivateEncodeContext *); /* Use of function
+ * pointer explained in QCBOREncode_SerializationCDE() */
QCBORTrackNesting nesting; /* Keep track of array and map nesting */
};
diff --git a/src/ieee754.c b/src/ieee754.c
index 2d98159..33c3708 100644
--- a/src/ieee754.c
+++ b/src/ieee754.c
@@ -1,5 +1,5 @@
/* ==========================================================================
- * ieee754.c -- floating-point conversion between half, double & single-precision
+ * ieee754.c -- floating-point conversion for half, double & single-precision
*
* Copyright (c) 2018-2024, Laurence Lundblade. All rights reserved.
* Copyright (c) 2021, Arm Limited. All rights reserved.
@@ -24,7 +24,7 @@
/*
- * This code has long lines and is easier to read because of
+ * This has long lines and is easier to read because of
* them. Some coding guidelines prefer 80 column lines (can they not
* afford big displays?).
*
@@ -184,7 +184,7 @@
/**
- * @brief Assemble sign, significand and exponent into single precision float.
+ * @brief Assemble sign, significand and exponent into double precision float.
*
* @param[in] uDoubleSign 0 if positive, 1 if negative
* @pararm[in] uDoubleSignificand Bits of the significand
@@ -208,6 +208,7 @@
}
+/* Public function; see ieee754.h */
double
IEEE754_HalfToDouble(uint16_t uHalfPrecision)
{
@@ -315,7 +316,7 @@
/* Public function; see ieee754.h */
IEEE754_union
-IEEE754_SingleToHalf(float f)
+IEEE754_SingleToHalf(const float f, const int bNoNaNPayload)
{
IEEE754_union result;
uint32_t uDroppedBits;
@@ -357,28 +358,36 @@
result.uSize = IEEE754_UNION_IS_HALF;
result.uValue = IEEE754_AssembleHalf(uSingleSign, 0, HALF_EXPONENT_INF_OR_NAN);
} else {
- /* The NaN can only be converted if no payload bits are lost
- * per RFC 8949 section 4.1 that defines Preferred
- * Serializaton. Note that Deterministically Encode CBOR in
- * section 4.2 allows for some variation of this rule, but at
- * the moment this implementation is of Preferred
- * Serialization, not CDE. As of December 2023, we are also
- * expecting an update to CDE. This code may need to be
- * updated for CDE.
- */
- uDroppedBits = uSingleSignificand & (SINGLE_SIGNIFICAND_MASK >> HALF_NUM_SIGNIFICAND_BITS);
- if(uDroppedBits == 0) {
- /* --- IS CONVERTABLE NAN --- */
- uHalfSignificand = uSingleSignificand >> (SINGLE_NUM_SIGNIFICAND_BITS - HALF_NUM_SIGNIFICAND_BITS);
+ if(bNoNaNPayload) {
+ /* --- REQUIRE CANNONICAL NAN --- */
result.uSize = IEEE754_UNION_IS_HALF;
result.uValue = IEEE754_AssembleHalf(uSingleSign,
- uHalfSignificand,
+ HALF_QUIET_NAN_BIT,
HALF_EXPONENT_INF_OR_NAN);
-
} else {
- /* --- IS UNCONVERTABLE NAN --- */
- result.uSize = IEEE754_UNION_IS_SINGLE;
- result.uValue = uSingle;
+ /* The NaN can only be converted if no payload bits are lost
+ * per RFC 8949 section 4.1 that defines Preferred
+ * Serializaton. Note that Deterministically Encode CBOR in
+ * section 4.2 allows for some variation of this rule, but at
+ * the moment this implementation is of Preferred
+ * Serialization, not CDE. As of December 2023, we are also
+ * expecting an update to CDE. This code may need to be
+ * updated for CDE.
+ */
+ uDroppedBits = uSingleSignificand & (SINGLE_SIGNIFICAND_MASK >> HALF_NUM_SIGNIFICAND_BITS);
+ if(uDroppedBits == 0) {
+ /* --- IS CONVERTABLE NAN --- */
+ uHalfSignificand = uSingleSignificand >> (SINGLE_NUM_SIGNIFICAND_BITS - HALF_NUM_SIGNIFICAND_BITS);
+ result.uSize = IEEE754_UNION_IS_HALF;
+ result.uValue = IEEE754_AssembleHalf(uSingleSign,
+ uHalfSignificand,
+ HALF_EXPONENT_INF_OR_NAN);
+
+ } else {
+ /* --- IS UNCONVERTABLE NAN --- */
+ result.uSize = IEEE754_UNION_IS_SINGLE;
+ result.uValue = uSingle;
+ }
}
}
} else {
@@ -495,7 +504,7 @@
* This handles all subnormals and NaN payloads.
*/
static IEEE754_union
-IEEE754_DoubleToSingle(double d)
+IEEE754_DoubleToSingle(const double d)
{
IEEE754_union Result;
int64_t nExponentDifference;
@@ -514,7 +523,6 @@
const uint64_t uDoubleSign = (uDouble & DOUBLE_SIGN_MASK) >> DOUBLE_SIGN_SHIFT;
const uint64_t uDoubleSignificand = uDouble & DOUBLE_SIGNIFICAND_MASK;
-
if(nDoubleUnbiasedExponent == DOUBLE_EXPONENT_ZERO) {
if(uDoubleSignificand == 0) {
/* --- IS ZERO --- */
@@ -619,7 +627,9 @@
/* Public function; see ieee754.h */
IEEE754_union
-IEEE754_DoubleToSmaller(double d, int bAllowHalfPrecision)
+IEEE754_DoubleToSmaller(const double d,
+ const int bAllowHalfPrecision,
+ const int bNoNanPayload)
{
IEEE754_union result;
@@ -629,13 +639,190 @@
/* Cast to uint32_t is OK, because value was just successfully
* converted to single. */
float uSingle = CopyUint32ToSingle((uint32_t)result.uValue);
- result = IEEE754_SingleToHalf(uSingle);
+ result = IEEE754_SingleToHalf(uSingle, bNoNanPayload);
}
return result;
}
+static int
+IEEE754_Private_CountNonZeroBits(int nMax, uint64_t uTarget)
+{
+ int nNonZeroBitsCount;
+ uint64_t uMask;
+
+ for(nNonZeroBitsCount = nMax; nNonZeroBitsCount > 0; nNonZeroBitsCount--) {
+ uMask = (0x01UL << nMax) >> nNonZeroBitsCount;
+ if(uMask & uTarget) {
+ break;
+ }
+ }
+ return nNonZeroBitsCount;
+}
+
+
+/* Public function; see ieee754.h */
+struct IEEE754_ToInt
+IEEE754_DoubleToInt(const double d)
+{
+ int64_t nNonZeroBitsCount;
+ struct IEEE754_ToInt Result;
+ uint64_t uInteger;
+
+ /* Pull the three parts out of the double-precision float. Most
+ * work is done with uint64_t which helps avoid integer promotions
+ * and static analyzer complaints.
+ */
+ const uint64_t uDouble = CopyDoubleToUint64(d);
+ const uint64_t uDoubleBiasedExponent = (uDouble & DOUBLE_EXPONENT_MASK) >> DOUBLE_EXPONENT_SHIFT;
+ /* Cast safe because of mask above; exponents < DOUBLE_EXPONENT_MAX */
+ const int64_t nDoubleUnbiasedExponent = (int64_t)uDoubleBiasedExponent - DOUBLE_EXPONENT_BIAS;
+ const uint64_t uDoubleSignificand = uDouble & DOUBLE_SIGNIFICAND_MASK;
+
+ if(nDoubleUnbiasedExponent == DOUBLE_EXPONENT_ZERO) {
+ if(uDoubleSignificand == 0) {
+ /* --- POSITIVE AND NEGATIVE ZERO --- */
+ Result.integer.un_signed = 0;
+ Result.type = IEEE754_ToInt_IS_UINT;
+ } else {
+ /* --- SUBNORMAL --- */
+ Result.type = IEEE754_ToInt_NO_CONVERSION;
+ }
+ } else if(nDoubleUnbiasedExponent == DOUBLE_EXPONENT_INF_OR_NAN) {
+ /* --- NAN or INFINITY --- */
+ if(uDoubleSignificand != 0) {
+ Result.type = IEEE754_To_int_NaN; /* dCBOR doesn't care about payload */
+ } else {
+ Result.type = IEEE754_ToInt_NO_CONVERSION;
+ }
+ } else if(nDoubleUnbiasedExponent < 0 ||
+ (nDoubleUnbiasedExponent >= ((uDouble & DOUBLE_SIGN_MASK) ? 63 : 64))) {
+ /* --- Exponent out of range --- */
+ Result.type = IEEE754_ToInt_NO_CONVERSION;
+ } else {
+ /* Count down from 52 to the number of bits that are not zero in
+ * the significand. This counts from the least significant bit
+ * until a non-zero bit is found to know if it is a whole
+ * number.
+ *
+ * Conversion only fails when the input is too large or is not a
+ * whole number, never because of lack of precision because
+ * 64-bit integers always have more precision than the 52-bits
+ * of a double.
+ */
+ nNonZeroBitsCount = IEEE754_Private_CountNonZeroBits(DOUBLE_NUM_SIGNIFICAND_BITS, uDoubleSignificand);
+
+ if(nNonZeroBitsCount && nNonZeroBitsCount > nDoubleUnbiasedExponent) {
+ /* --- Not a whole number --- */
+ Result.type = IEEE754_ToInt_NO_CONVERSION;
+ } else {
+ /* --- CONVERTABLE WHOLE NUMBER --- */
+ /* Add in the one that is implied in normal floats */
+ uInteger = uDoubleSignificand + (1ULL << DOUBLE_NUM_SIGNIFICAND_BITS);
+ /* Factor in the exponent */
+ if(nDoubleUnbiasedExponent < DOUBLE_NUM_SIGNIFICAND_BITS) {
+ /* Numbers less than 2^52 with up to 52 significant bits */
+ uInteger >>= DOUBLE_NUM_SIGNIFICAND_BITS - nDoubleUnbiasedExponent;
+ } else {
+ /* Numbers greater than 2^52 with at most 52 significant bits */
+ uInteger <<= nDoubleUnbiasedExponent - DOUBLE_NUM_SIGNIFICAND_BITS;
+ }
+ if(uDouble & DOUBLE_SIGN_MASK) {
+ /* Cast safe because exponent range check above */
+ Result.integer.is_signed = -((int64_t)uInteger);
+ Result.type = IEEE754_ToInt_IS_INT;
+ } else {
+ Result.integer.un_signed = uInteger;
+ Result.type = IEEE754_ToInt_IS_UINT;
+ }
+ }
+ }
+
+ return Result;
+}
+
+
+/* Public function; see ieee754.h */
+struct IEEE754_ToInt
+IEEE754_SingleToInt(const float f)
+{
+ int32_t nNonZeroBitsCount;
+ struct IEEE754_ToInt Result;
+ uint64_t uInteger;
+
+ /* Pull the three parts out of the single-precision float. Most
+ * work is done with uint32_t which helps avoid integer promotions
+ * and static analyzer complaints.
+ */
+ const uint32_t uSingle = CopyFloatToUint32(f);
+ const uint32_t uSingleBiasedExponent = (uSingle & SINGLE_EXPONENT_MASK) >> SINGLE_EXPONENT_SHIFT;
+ /* Cast safe because of mask above; exponents < SINGLE_EXPONENT_MAX */
+ const int32_t nSingleUnbiasedExponent = (int32_t)uSingleBiasedExponent - SINGLE_EXPONENT_BIAS;
+ const uint32_t uSingleleSignificand = uSingle & SINGLE_SIGNIFICAND_MASK;
+
+ if(nSingleUnbiasedExponent == SINGLE_EXPONENT_ZERO) {
+ if(uSingleleSignificand == 0 && !(uSingle & SINGLE_SIGN_MASK)) {
+ /* --- POSITIVE AND NEGATIVE ZERO --- */
+ Result.integer.un_signed = 0;
+ Result.type = IEEE754_ToInt_IS_UINT;
+ } else {
+ /* --- Subnormal --- */
+ Result.type = IEEE754_ToInt_NO_CONVERSION;
+ }
+ } else if(nSingleUnbiasedExponent == SINGLE_EXPONENT_INF_OR_NAN) {
+ /* --- NAN or INFINITY --- */
+ if(uSingleleSignificand != 0) {
+ Result.type = IEEE754_To_int_NaN; /* dCBOR doesn't care about payload */
+ } else {
+ Result.type = IEEE754_ToInt_NO_CONVERSION;
+ }
+ } else if(nSingleUnbiasedExponent < 0 ||
+ (nSingleUnbiasedExponent >= ((uSingle & SINGLE_SIGN_MASK) ? 63 : 64))) {
+ /* --- Exponent out of range --- */
+ Result.type = IEEE754_ToInt_NO_CONVERSION;
+ } else {
+ /* Count down from 23 to the number of bits that are not zero in
+ * the significand. This counts from the least significant bit
+ * until a non-zero bit is found.
+ *
+ * Conversion only fails when the input is too large or is not a
+ * whole number, never because of lack of precision because
+ * 64-bit integers always have more precision than the 52-bits
+ * of a double.
+ */
+ nNonZeroBitsCount = IEEE754_Private_CountNonZeroBits(SINGLE_NUM_SIGNIFICAND_BITS, uSingleleSignificand);
+
+
+ if(nNonZeroBitsCount && nNonZeroBitsCount > nSingleUnbiasedExponent) {
+ /* --- Not a whole number --- */
+ Result.type = IEEE754_ToInt_NO_CONVERSION;
+ } else {
+ /* --- CONVERTABLE WHOLE NUMBER --- */
+ /* Add in the one that is implied in normal floats */
+ uInteger = uSingleleSignificand + (1ULL << SINGLE_NUM_SIGNIFICAND_BITS);
+ /* Factor in the exponent */
+ if(nSingleUnbiasedExponent < SINGLE_NUM_SIGNIFICAND_BITS) {
+ /* Numbers less than 2^23 with up to 23 significant bits */
+ uInteger >>= SINGLE_NUM_SIGNIFICAND_BITS - nSingleUnbiasedExponent;
+ } else {
+ /* Numbers greater than 2^23 with at most 23 significant bits*/
+ uInteger <<= nSingleUnbiasedExponent - SINGLE_NUM_SIGNIFICAND_BITS;
+ }
+ if(uSingle & SINGLE_SIGN_MASK) {
+ Result.integer.is_signed = -((int64_t)uInteger);
+ Result.type = IEEE754_ToInt_IS_INT;
+ } else {
+ Result.integer.un_signed = uInteger;
+ Result.type = IEEE754_ToInt_IS_UINT;
+ }
+ }
+ }
+
+ return Result;
+}
+
+
#else /* QCBOR_DISABLE_PREFERRED_FLOAT */
int ieee754_dummy_place_holder;
diff --git a/src/ieee754.h b/src/ieee754.h
index 863019b..fbd90aa 100644
--- a/src/ieee754.h
+++ b/src/ieee754.h
@@ -25,6 +25,9 @@
* smaller representation (e.g., double to single) that does not lose
* precision for CBOR preferred serialization.
*
+ * This also implements conversion of floats to whole numbers as
+ * is required for dCBOR.
+ *
* This implementation works entirely with shifts and masks and does
* not require any floating-point HW or library.
*
@@ -87,6 +90,22 @@
} IEEE754_union;
+/** Holds result of an attempt to convert a floating-point
+ * number to an int64_t or uint64_t.
+ */
+struct IEEE754_ToInt {
+ enum {IEEE754_ToInt_IS_INT,
+ IEEE754_ToInt_IS_UINT,
+ IEEE754_ToInt_NO_CONVERSION,
+ IEEE754_To_int_NaN
+ } type;
+ union {
+ uint64_t un_signed;
+ int64_t is_signed;
+ } integer;
+};
+
+
/**
* @brief Convert a double to either single or half-precision.
*
@@ -102,7 +121,7 @@
* This handles all subnormals and NaN payloads.
*/
IEEE754_union
-IEEE754_DoubleToSmaller(double d, int bAllowHalfPrecision);
+IEEE754_DoubleToSmaller(double d, int bAllowHalfPrecision, int bNoNaNPayload);
/**
@@ -118,9 +137,52 @@
* This handles all subnormals and NaN payloads.
*/
IEEE754_union
-IEEE754_SingleToHalf(float f);
+IEEE754_SingleToHalf(float f, int bNoNanPayloads);
+/**
+ * @brief Convert a double-precision float to integer if whole number
+ *
+ * @param[in] d The value to convert.
+ *
+ * @returns Either converted number or conversion status.
+ *
+ * If the value is a whole number that will fit either in a uint64_t
+ * or an int64_t, it is converted. If it is a NaN, then there is no
+ * conversion and and the fact that it is a NaN is indicated in the
+ * returned structure. If it can't be converted, then that is
+ * indicated in the returned structure.
+ *
+ * This always returns postive numbers as a uint64_t even if they will
+ * fit in an int64_t.
+ *
+ * This never fails becaue of precision, but may fail because of range.
+ */
+struct IEEE754_ToInt
+IEEE754_DoubleToInt(double d);
+
+
+/**
+ * @brief Convert a single-precision float to integer if whole number
+ *
+ * @param[in] f The value to convert.
+ *
+ * @returns Either converted number or conversion status.
+ *
+ * If the value is a whole number that will fit either in a uint64_t
+ * or an int64_t, it is converted. If it is a NaN, then there is no
+ * conversion and and the fact that it is a NaN is indicated in the
+ * returned structure. If it can't be converted, then that is
+ * indicated in the returned structure.
+ *
+ * This always returns postive numbers as a uint64_t even if they will
+ * fit in an int64_t.
+ *
+ * This never fails becaue of precision, but may fail because of range.
+ */
+struct IEEE754_ToInt
+IEEE754_SingleToInt(float f);
+
#endif /* ieee754_h */
#endif /* QCBOR_DISABLE_PREFERRED_FLOAT */
diff --git a/src/qcbor_encode.c b/src/qcbor_encode.c
index 767ce58..48d1ea6 100644
--- a/src/qcbor_encode.c
+++ b/src/qcbor_encode.c
@@ -248,6 +248,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 +262,7 @@
memset(pMe, 0, sizeof(QCBOREncodeContext));
UsefulOutBuf_Init(&(pMe->OutBuf), Storage);
Nesting_Init(&(pMe->nesting));
+ pMe->pfnCloseMap = QCBOREncode_Private_CloseMapUnsorted;
}
@@ -807,54 +813,63 @@
* Public functions for adding a double. See qcbor/qcbor_encode.h
*/
void
-QCBOREncode_AddDoubleNoPreferred(QCBOREncodeContext *pMe, const double dNum)
+QCBOREncode_AddDoubleNoPreferred(QCBOREncodeContext *pMe, double dNum)
{
+#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 */
+
QCBOREncode_Private_AddType7(pMe,
sizeof(uint64_t),
UsefulBufUtil_CopyDoubleToUint64(dNum));
}
+#include <math.h> // For NaN. Maybe a better way? TODO:
/*
* 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);
+ 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_To_int_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 +878,12 @@
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;
+ }
+#endif /* ! QCBOR_DISABLE_ENCODE_USAGE_GUARDS */
QCBOREncode_Private_AddType7(pMe,
sizeof(uint32_t),
UsefulBufUtil_CopyFloatToUint32(fNum));
@@ -873,12 +894,36 @@
* 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);
+ 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_To_int_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 +1052,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 +1070,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 +1083,19 @@
}
+/**
+ * @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.
diff --git a/test/float_tests.c b/test/float_tests.c
index 1a7ade1..754eb4f 100644
--- a/test/float_tests.c
+++ b/test/float_tests.c
@@ -41,16 +41,16 @@
#include "half_to_double_from_rfc7049.h"
-struct DoubleTestCase {
+struct FloatTestCase {
double dNumber;
- double fNumber;
+ float fNumber;
UsefulBufC Preferred;
UsefulBufC NotPreferred;
UsefulBufC CDE;
UsefulBufC DCBOR;
};
-/* Boundaries for all destination conversions to test at.
+/* Boundaries for destination conversions:
*
* smallest subnormal single 1.401298464324817e-45 2^^-149
* largest subnormal single 1.1754942106924411e-38 2^^-126
@@ -62,29 +62,34 @@
* smallest normal half 6.103515625E-5
* largest half 65504.0
*
- * Boundaries for origin conversions
+ * Boundaries for origin conversions:
* smallest subnormal double 5.0e-324 2^^-1074
* largest subnormal double
* smallest normal double 2.2250738585072014e-308 2^^-1022
* largest normal double 1.7976931348623157e308 2^^-1023
+ *
+ * Boundaries for double conversion to 64-bit integer:
+ * exponent 51, 52 significand bits set 4503599627370495
+ * exponent 52, 52 significand bits set 9007199254740991
+ * exponent 53, 52 bits set in significand 18014398509481982
*/
/* Always four lines per test case so shell scripts can process into
- * other formats. CDE and DCBOR standards are not complete yet,
- * encodings are a guess. C string literals are used because they
+ * other formats. CDE and DCBOR standards are not complete yet,
+ * encodings are what is expected. C string literals are used because they
* are the shortest notation. They are used __with a length__ . Null
- * termination doesn't work because * there are zero bytes.
+ * termination doesn't work because there are zero bytes.
*/
-static const struct DoubleTestCase DoubleTestCases[] = {
+static const struct FloatTestCase FloatTestCases[] = {
/* Zero */
{0.0, 0.0f,
{"\xF9\x00\x00", 3}, {"\xFB\x00\x00\x00\x00\x00\x00\x00\x00", 9},
- {"\xF9\x00\x00", 3}, {"\xF9\x00\x00", 3}},
+ {"\xF9\x00\x00", 3}, {"\x00", 1}},
/* Negative Zero */
{-0.0, -0.0f,
{"\xF9\x80\x00", 3}, {"\xFB\x80\x00\x00\x00\x00\x00\x00\x00", 9},
- {"\xF9\x80\x00", 3}, {"\xF9\x80\x00", 3}},
+ {"\xF9\x80\x00", 3}, {"\x00", 1}},
/* NaN */
{NAN, NAN,
@@ -104,12 +109,12 @@
/* 1.0 */
{1.0, 1.0f,
{"\xF9\x3C\x00", 3}, {"\xFB\x3F\xF0\x00\x00\x00\x00\x00\x00", 9},
- {"\xF9\x3C\x00", 3}, {"\xF9\x3C\x00", 3}},
+ {"\xF9\x3C\x00", 3}, {"\x01", 1}},
- /* -2.0 -- a negative number that is not zero */
+ /* -2.0 -- a negative */
{-2.0, -2.0f,
{"\xF9\xC0\x00", 3}, {"\xFB\xC0\x00\x00\x00\x00\x00\x00\x00", 9},
- {"\xF9\xC0\x00", 3}, {"\xF9\x3C\x00", 3}},
+ {"\xF9\xC0\x00", 3}, {"\x21", 1}},
/* 1/3 */
{0.333251953125, 0.333251953125f,
@@ -129,45 +134,44 @@
/* 6.097555160522461E-5 -- largest half-precision subnormal */
{6.097555160522461E-5, 0.0f,
{"\xF9\x03\xFF", 3}, {"\xFB\x3F\x0F\xF8\x00\x00\x00\x00\x00", 9},
- {"\xF9\x03\xFF", 3}, {"\xF9\04\00", 3}},
-
- /* 6.103515625E-5 -- smallest possible half-precision normal */
- {6.103515625E-5, 0.0f,
- {"\xF9\04\00", 3}, {"\xFB\x3F\x10\x00\x00\x00\x00\x00\x00", 9},
- {"\xF9\04\00", 3}, {"\xF9\04\00", 3}},
-
- /* 6.1035156250000014E-5 -- slightly larger than smallest half-precision normal */
- {6.1035156250000014E-5, 6.1035156250000014E-5f,
- {"\xFB\x3F\x10\x00\x00\x00\x00\x00\x01", 9}, {"\xFB\x3F\x10\x00\x00\x00\x00\x00\x01", 9},
- {"\xFB\x3F\x10\x00\x00\x00\x00\x00\x01", 9}, {"\xFB\x3F\x10\x00\x00\x00\x00\x00\x01", 9}},
+ {"\xF9\x03\xFF", 3}, {"\xF9\x03\xFF", 3}},
/* 6.1035156249999993E-5 -- slightly smaller than smallest half-precision normal */
{6.1035156249999993E-5, 0.0f,
{"\xFB\x3F\x0F\xFF\xFF\xFF\xFF\xFF\xFF", 9}, {"\xFB\x3F\x0F\xFF\xFF\xFF\xFF\xFF\xFF", 9},
{"\xFB\x3F\x0F\xFF\xFF\xFF\xFF\xFF\xFF", 9}, {"\xFB\x3F\x0F\xFF\xFF\xFF\xFF\xFF\xFF", 9}},
- /* 65504.0 -- largest possible half-precision */
+ /* 6.103515625E-5 -- smallest half-precision normal */
+ {6.103515625E-5, 0.0f,
+ {"\xF9\04\00", 3}, {"\xFB\x3F\x10\x00\x00\x00\x00\x00\x00", 9},
+ {"\xF9\04\00", 3}, {"\xF9\04\00", 3}},
+
+ /* 6.1035156250000014E-5 -- slightly larger than smallest half-precision normal */
+ {6.1035156250000014E-5, 0.0f,
+ {"\xFB\x3F\x10\x00\x00\x00\x00\x00\x01", 9}, {"\xFB\x3F\x10\x00\x00\x00\x00\x00\x01", 9},
+ {"\xFB\x3F\x10\x00\x00\x00\x00\x00\x01", 9}, {"\xFB\x3F\x10\x00\x00\x00\x00\x00\x01", 9}},
+
+ /* 65504.0 -- largest half-precision */
{65504.0, 0.0f,
{"\xF9\x7B\xFF", 3}, {"\xFB\x40\xEF\xFC\x00\x00\x00\x00\x00", 9},
- {"\xF9\x7B\xFF", 3}, {"\xF9\x7B\xFF", 3}},
+ {"\xF9\x7B\xFF", 3}, {"\x19\xFF\xE0", 3}},
- /* 65504.1 -- exponent too large and too much precision to convert */
+ /* 65504.1 -- exponent too large and too much precision to convert to half */
{65504.1, 0.0f,
{"\xFB\x40\xEF\xFC\x03\x33\x33\x33\x33", 9}, {"\xFB\x40\xEF\xFC\x03\x33\x33\x33\x33", 9},
{"\xFB\x40\xEF\xFC\x03\x33\x33\x33\x33", 9}, {"\xFB\x40\xEF\xFC\x03\x33\x33\x33\x33", 9}},
- /* 65536.0 -- exponent too large but not too much precision for single */
+ /* 65536.0 -- exponent too large for half but not too much precision for single */
{65536.0, 65536.0f,
{"\xFA\x47\x80\x00\x00", 5}, {"\xFB\x40\xF0\x00\x00\x00\x00\x00\x00", 9},
- {"\xFA\x47\x80\x00\x00", 5}, {"\xFA\x47\x80\x00\x00", 5}},
+ {"\xFA\x47\x80\x00\x00", 5}, {"\x1A\x00\x01\x00\x00", 5}},
/* 1.401298464324817e-45 -- smallest single subnormal */
{1.401298464324817e-45, 1.40129846E-45f,
{"\xFA\x00\x00\x00\x01", 5}, {"\xFB\x36\xA0\x00\x00\x00\x00\x00\x00", 9},
{"\xFA\x00\x00\x00\x01", 5}, {"\xFA\x00\x00\x00\x01", 5}},
- /* 5.8774717541114375E-39 -- slightly smaller than the smallest
- // single normal */
+ /* 5.8774717541114375E-39 -- slightly smaller than the smallest single normal */
{5.8774717541114375E-39, 5.87747175E-39f,
{"\xFA\x00\x40\x00\x00", 5}, {"\xFB\x38\x00\x00\x00\x00\x00\x00\x00", 9},
{"\xFA\x00\x40\x00\x00", 5}, {"\xFA\x00\x40\x00\x00", 5}},
@@ -192,20 +196,100 @@
{"\xFB\x38\x10\x00\x00\x00\x00\x00\x01", 9}, {"\xFB\x38\x10\x00\x00\x00\x00\x00\x01", 9},
{"\xFB\x38\x10\x00\x00\x00\x00\x00\x01", 9}, {"\xFB\x38\x10\x00\x00\x00\x00\x00\x01", 9}},
+ /* 8388607 -- exponent 22 to test single exponent boundary */
+ {8388607, 8388607.0f,
+ {"\xFA\x4A\xFF\xFF\xFE", 5}, {"\xFB\x41\x5F\xFF\xFF\xC0\x00\x00\x00", 9},
+ {"\xFA\x4A\xFF\xFF\xFE", 5}, {"\x1A\x00\x7F\xFF\xFF", 5}},
+
+ /* 16777215 -- exponent 23 to test single exponent boundary */
+ {16777215, 16777215.0f,
+ {"\xFA\x4B\x7F\xFF\xFF", 5}, {"\xFB\x41\x6F\xFF\xFF\xE0\x00\x00\x00", 9},
+ {"\xFA\x4B\x7F\xFF\xFF", 5}, {"\x1A\x00\xFF\xFF\xFF", 5}},
+
/* 16777216 -- converts to single without loss */
- {16777216, 16777216,
+ {16777216, 16777216.0f,
{"\xFA\x4B\x80\x00\x00", 5}, {"\xFB\x41\x70\x00\x00\x00\x00\x00\x00", 9},
- {"\xFA\x4B\x80\x00\x00", 5}, {"\xFA\x4B\x80\x00\x00", 5}},
+ {"\xFA\x4B\x80\x00\x00", 5}, {"\x1A\x01\x00\x00\x00", 5}},
- /* 16777217 -- one more than above and fails conversion to single */
- {16777217, 16777216,
+ /* 16777217 -- one more than above and fails conversion to single because of precision */
+ {16777217, 0.0f,
{"\xFB\x41\x70\x00\x00\x10\x00\x00\x00", 9}, {"\xFB\x41\x70\x00\x00\x10\x00\x00\x00", 9},
- {"\xFB\x41\x70\x00\x00\x10\x00\x00\x00", 9}, {"\xFB\x41\x70\x00\x00\x10\x00\x00\x00", 9}},
+ {"\xFB\x41\x70\x00\x00\x10\x00\x00\x00", 9}, {"\x1A\x01\x00\x00\x01", 5}},
+
+ /* 33554430 -- exponent 24 to test single exponent boundary */
+ {33554430, 33554430.0f,
+ {"\xFA\x4B\xFF\xFF\xFF", 5}, {"\xFB\x41\x7F\xFF\xFF\xE0\x00\x00\x00", 9},
+ {"\xFA\x4B\xFF\xFF\xFF", 5}, {"\x1A\x01\xFF\xFF\xFE", 5}},
- /* 3.4028234663852886E+38 -- largest possible single normal */
+ /* 4294967295 -- 2^^32 - 1 UINT32_MAX */
+ {4294967295, 0,
+ {"\xFB\x41\xEF\xFF\xFF\xFF\xE0\x00\x00", 9}, {"\xFB\x41\xEF\xFF\xFF\xFF\xE0\x00\x00", 9},
+ {"\xFB\x41\xEF\xFF\xFF\xFF\xE0\x00\x00", 9}, {"\x1A\xFF\xFF\xFF\xFF", 5}},
+
+ /* 4294967296 -- 2^^32, UINT32_MAX + 1 */
+ {4294967296, 4294967296.0f,
+ {"\xFA\x4F\x80\x00\x00", 5}, {"\xFB\x41\xF0\x00\x00\x00\x00\x00\x00", 9},
+ {"\xFA\x4F\x80\x00\x00", 5}, {"\x1B\x00\x00\x00\x01\x00\x00\x00\x00", 9}},
+
+ /* 2251799813685248 -- exponent 51, 0 significand bits set, to test double exponent boundary */
+ {2251799813685248, 0,
+ {"\xFA\x59\x00\x00\x00", 5}, {"\xFB\x43\x20\x00\x00\x00\x00\x00\x00", 9},
+ {"\xFA\x59\x00\x00\x00", 5}, {"\x1B\x00\x08\x00\x00\x00\x00\x00\x00", 9}},
+
+ /* 4503599627370495 -- exponent 51, 52 significand bits set to test double exponent boundary*/
+ {4503599627370495, 0,
+ {"\xFB\x43\x2F\xFF\xFF\xFF\xFF\xFF\xFE", 9}, {"\xFB\x43\x2F\xFF\xFF\xFF\xFF\xFF\xFE", 9},
+ {"\xFB\x43\x2F\xFF\xFF\xFF\xFF\xFF\xFE", 9}, {"\x1B\x00\x0F\xFF\xFF\xFF\xFF\xFF\xFF", 9}},
+
+ /* 9007199254740991 -- exponent 52, 52 significand bits set to test double exponent boundary */
+ {9007199254740991, 0,
+ {"\xFB\x43\x3F\xFF\xFF\xFF\xFF\xFF\xFF", 9}, {"\xFB\x43\x3F\xFF\xFF\xFF\xFF\xFF\xFF", 9},
+ {"\xFB\x43\x3F\xFF\xFF\xFF\xFF\xFF\xFF", 9}, {"\x1B\x00\x1F\xFF\xFF\xFF\xFF\xFF\xFF", 9}},
+
+ /* 18014398509481982 -- exponent 53, 52 bits set in significand (double lacks precision to represent 18014398509481983) */
+ {18014398509481982, 0,
+ {"\xFB\x43\x4F\xFF\xFF\xFF\xFF\xFF\xFF", 9}, {"\xFB\x43\x4F\xFF\xFF\xFF\xFF\xFF\xFF", 9},
+ {"\xFB\x43\x4F\xFF\xFF\xFF\xFF\xFF\xFF", 9}, {"\x1B\x00\x3F\xFF\xFF\xFF\xFF\xFF\xFE", 9}},
+
+ /* 18014398509481984 -- next largest possible double above 18014398509481982 */
+ {18014398509481984, 0,
+ {"\xFA\x5A\x80\x00\x00", 5}, {"\xFB\x43\x50\x00\x00\x00\x00\x00\x00", 9},
+ {"\xFA\x5A\x80\x00\x00", 5}, {"\x1B\x00\x40\x00\x00\x00\x00\x00\x00", 9}},
+
+ /* 18446742974197924000.0.0 -- largest single that can convert to uint64 */
+ {18446742974197924000.0, 18446742974197924000.0f,
+ {"\xFA\x5F\x7F\xFF\xFF", 5}, {"\xFB\x43\xEF\xFF\xFF\xE0\x00\x00\x00", 9},
+ {"\xFA\x5F\x7F\xFF\xFF", 5}, {"\x1B\xFF\xFF\xFF\x00\x00\x00\x00\x00", 9}},
+
+ /* 18446744073709550000.0 -- largest double that can convert to uint64, almost UINT64_MAX (18446744073709551615) */
+ {18446744073709550000.0, 0,
+ {"\xFB\x43\xEF\xFF\xFF\xFF\xFF\xFF\xFF", 9}, {"\xFB\x43\xEF\xFF\xFF\xFF\xFF\xFF\xFF", 9},
+ {"\xFB\x43\xEF\xFF\xFF\xFF\xFF\xFF\xFF", 9}, {"\x1B\xFF\xFF\xFF\xFF\xFF\xFF\xF8\x00", 9}},
+
+ /* 18446744073709552000.0 -- just too large to convert to uint64, but converts to a single, just over UINT64_MAX */
+ {18446744073709552000.0, 18446744073709552000.0f,
+ {"\xFA\x5F\x80\x00\x00", 5}, {"\xFB\x43\xF0\x00\x00\x00\x00\x00\x00", 9},
+ {"\xFA\x5F\x80\x00\x00", 5}, {"\xFA\x5F\x80\x00\x00", 5}},
+
+ /* -4294967295 -- negative UINT32_MAX */
+ {-4294967295.0, 0,
+ {"\xFB\xC1\xEF\xFF\xFF\xFF\xE0\x00\x00", 9}, {"\xFB\xC1\xEF\xFF\xFF\xFF\xE0\x00\x00", 9},
+ {"\xFB\xC1\xEF\xFF\xFF\xFF\xE0\x00\x00", 9}, {"\x3A\xFF\xFF\xFF\xFE", 5}},
+
+ /* -9223372036854774784.0 -- most negative double that converts to int64 */
+ {-9223372036854774784.0, 0,
+ {"\xFB\xC3\xDF\xFF\xFF\xFF\xFF\xFF\xFF", 9}, {"\xFB\xC3\xDF\xFF\xFF\xFF\xFF\xFF\xFF", 9},
+ {"\xFB\xC3\xDF\xFF\xFF\xFF\xFF\xFF\xFF", 9}, {"\x3B\x7F\xFF\xFF\xFF\xFF\xFF\xFB\xFF", 9}},
+
+ /* -18446742974197924000.0.0 -- large negative that converts to float, but too large for int64 */
+ {-18446742974197924000.0, -18446742974197924000.0f,
+ {"\xFA\xDF\x7F\xFF\xFF", 5}, {"\xFB\xC3\xEF\xFF\xFF\xE0\x00\x00\x00", 9},
+ {"\xFA\xDF\x7F\xFF\xFF", 5}, {"\xFA\xDF\x7F\xFF\xFF", 5}},
+
+ /* 3.4028234663852886E+38 -- largest possible single */
{3.4028234663852886E+38, 3.40282347E+38f,
- {"\xFA\x7F\x7F\xFF\xFF", 5}, {"\xFB\x47\xEF\xFF\xFF\xE0\x00\x00\x00", 9},
- {"\xFA\x7F\x7F\xFF\xFF", 5}, {"\xFA\x7F\x7F\xFF\xFF", 5}},
+ {"\xFA\x7F\x7F\xFF\xFF", 5}, {"\xFB\x47\xEF\xFF\xFF\xE0\x00\x00\x00", 9},
+ {"\xFA\x7F\x7F\xFF\xFF", 5}, {"\xFA\x7F\x7F\xFF\xFF", 5}},
/* 3.402823466385289E+38 -- slightly larger than largest possible single */
{3.402823466385289E+38, 0.0f,
@@ -242,9 +326,12 @@
};
+/* Can't use types double and float here because there's no way in C to
+ * construct arbitrary payloads for those types.
+ */
struct NaNTestCase {
- uint64_t uDouble;
- uint32_t uSingle;
+ uint64_t uDouble; /* Converted to double in test */
+ uint32_t uSingle; /* Converted to single in test */
UsefulBufC Preferred;
UsefulBufC NotPreferred;
UsefulBufC CDE;
@@ -292,14 +379,13 @@
/* Payload with all bits set */
{0x7fffffffffffffff, 0x00000000,
{"\xFB\x7F\xFF\xFF\xFF\xFF\xFF\xFF\xFF", 9}, {"\xFB\x7F\xFF\xFF\xFF\xFF\xFF\xFF\xFF", 9},
- {"\xF9\x7E\x00", 3}, {"\xFB\x7F\xFF\xFF\xFF\xFF\xFF\xFF\xFF", 9}},
+ {"\xF9\x7E\x00", 3}, {"\xF9\x7E\x00", 3}},
/* List terminator */
{0, 0, {NULL, 0}, {NULL, 0}, {NULL, 0}, {NULL, 0} }
};
-
/* Public function. See float_tests.h
*
* This is the main test of floating-point encoding / decoding. It is
@@ -311,7 +397,7 @@
FloatValuesTests(void)
{
unsigned int uTestIndex;
- const struct DoubleTestCase *pTestCase;
+ const struct FloatTestCase *pTestCase;
const struct NaNTestCase *pNaNTestCase;
MakeUsefulBufOnStack( TestOutBuffer, 20);
UsefulBufC TestOutput;
@@ -325,11 +411,13 @@
#endif
/* Test a variety of doubles */
- for(uTestIndex = 0; DoubleTestCases[uTestIndex].Preferred.len != 0; uTestIndex++) {
- pTestCase = &DoubleTestCases[uTestIndex];
+ for(uTestIndex = 0; FloatTestCases[uTestIndex].Preferred.len != 0; uTestIndex++) {
+ pTestCase = &FloatTestCases[uTestIndex];
- // if(pTestCase->dNumber == 1.1754943508222874E-38) {
- if(uTestIndex == 19) {
+
+ // 9223372036854774784
+ if(pTestCase->dNumber == 1.7976931348623157e308 ||
+ uTestIndex == 77) {
uErr = 99; /* For setting break points for particular tests */
}
@@ -357,6 +445,47 @@
return MakeTestResultCode(uTestIndex, 2, 200);
}
+ /* Number Encode of CDE */
+ QCBOREncode_Init(&EnCtx, TestOutBuffer);
+ QCBOREncode_SerializationCDE(&EnCtx);
+ QCBOREncode_AddDouble(&EnCtx, pTestCase->dNumber);
+ uErr = QCBOREncode_Finish(&EnCtx, &TestOutput);
+
+ if(uErr != QCBOR_SUCCESS) {
+ return MakeTestResultCode(uTestIndex, 20, uErr);;
+ }
+ if(UsefulBuf_Compare(TestOutput, pTestCase->CDE)) {
+ return MakeTestResultCode(uTestIndex, 21, 200);
+ }
+
+ /* Number Encode of dCBOR */
+ QCBOREncode_Init(&EnCtx, TestOutBuffer);
+ QCBOREncode_SerializationdCBOR(&EnCtx);
+ QCBOREncode_AddDouble(&EnCtx, pTestCase->dNumber);
+ uErr = QCBOREncode_Finish(&EnCtx, &TestOutput);
+
+ if(uErr != QCBOR_SUCCESS) {
+ return MakeTestResultCode(uTestIndex, 22, uErr);;
+ }
+ if(UsefulBuf_Compare(TestOutput, pTestCase->DCBOR)) {
+ return MakeTestResultCode(uTestIndex, 23, 200);
+ }
+
+ if(pTestCase->fNumber != 0) {
+ QCBOREncode_Init(&EnCtx, TestOutBuffer);
+ QCBOREncode_SerializationdCBOR(&EnCtx);
+ QCBOREncode_AddFloat(&EnCtx, pTestCase->fNumber);
+ uErr = QCBOREncode_Finish(&EnCtx, &TestOutput);
+
+ if(uErr != QCBOR_SUCCESS) {
+ return MakeTestResultCode(uTestIndex, 24, uErr);;
+ }
+ if(UsefulBuf_Compare(TestOutput, pTestCase->DCBOR)) {
+ return MakeTestResultCode(uTestIndex, 25, 200);
+ }
+ }
+
+
/* Number Decode of Preferred */
QCBORDecode_Init(&DCtx, pTestCase->Preferred, 0);
uErr = QCBORDecode_GetNext(&DCtx, &Item);
@@ -382,28 +511,28 @@
* indicates single-precision in the encoded CBOR. */
if(pTestCase->Preferred.len == 5) {
if(Item.uDataType != QCBOR_TYPE_FLOAT) {
- return MakeTestResultCode(uTestIndex, 4, 0);
+ return MakeTestResultCode(uTestIndex, 41, 0);
}
if(isnan(pTestCase->dNumber)) {
if(!isnan(Item.val.fnum)) {
- return MakeTestResultCode(uTestIndex, 5, 0);
+ return MakeTestResultCode(uTestIndex, 51, 0);
}
} else {
if(Item.val.fnum != pTestCase->fNumber) {
- return MakeTestResultCode(uTestIndex, 6, 0);
+ return MakeTestResultCode(uTestIndex, 61, 0);
}
}
} else {
if(Item.uDataType != QCBOR_TYPE_DOUBLE) {
- return MakeTestResultCode(uTestIndex, 4, 0);
+ return MakeTestResultCode(uTestIndex, 42, 0);
}
if(isnan(pTestCase->dNumber)) {
if(!isnan(Item.val.dfnum)) {
- return MakeTestResultCode(uTestIndex, 5, 0);
+ return MakeTestResultCode(uTestIndex, 52, 0);
}
} else {
if(Item.val.dfnum != pTestCase->dNumber) {
- return MakeTestResultCode(uTestIndex, 6, 0);
+ return MakeTestResultCode(uTestIndex, 62, 0);
}
}
}
@@ -498,7 +627,7 @@
return MakeTestResultCode(uTestIndex+100, 11, 200);
}
- /* NaN Decode of Preferred */
+ /* NaN Decode of Not Preferred */
QCBORDecode_Init(&DCtx, pNaNTestCase->Preferred, 0);
uErr = QCBORDecode_GetNext(&DCtx, &Item);
if(uErr != QCBOR_SUCCESS) {
@@ -543,6 +672,20 @@
if(uDecoded != pNaNTestCase->uDouble) {
return MakeTestResultCode(uTestIndex+100, 13, 200);
}
+
+
+ /* NaN Encode of DCBOR */
+ QCBOREncode_Init(&EnCtx, TestOutBuffer);
+ QCBOREncode_SerializationdCBOR(&EnCtx);
+ QCBOREncode_AddDouble(&EnCtx, UsefulBufUtil_CopyUint64ToDouble(pNaNTestCase->uDouble));
+ uErr = QCBOREncode_Finish(&EnCtx, &TestOutput);
+ if(uErr != QCBOR_SUCCESS) {
+ return MakeTestResultCode(uTestIndex+100, 11, uErr);;
+ }
+ if(UsefulBuf_Compare(TestOutput, pNaNTestCase->DCBOR)) {
+ return MakeTestResultCode(uTestIndex+100, 11, 200);
+ }
+
}
return 0;
diff --git a/test/qcbor_encode_tests.c b/test/qcbor_encode_tests.c
index c639a84..af64f92 100644
--- a/test/qcbor_encode_tests.c
+++ b/test/qcbor_encode_tests.c
@@ -3363,3 +3363,123 @@
return 0;
}
+
+
+#include <math.h> /* For INFINITY and NAN and isnan() */
+
+
+int32_t CDETest(void)
+{
+ QCBOREncodeContext EC;
+ UsefulBufC Encoded;
+
+ QCBOREncode_Init(&EC, UsefulBuf_FROM_BYTE_ARRAY(spBigBuf));
+
+ QCBOREncode_SerializationCDE(&EC);
+
+ /* Items added to test sorting and preferred encoding of numbers and floats */
+ QCBOREncode_OpenMap(&EC);
+ QCBOREncode_AddFloatToMap(&EC, "k", 1.0f);
+ QCBOREncode_AddInt64ToMap(&EC, "a", 1);
+ QCBOREncode_AddDoubleToMap(&EC, "x", 2.0);
+ QCBOREncode_AddDoubleToMap(&EC, "r", 3.4028234663852886E+38);
+ QCBOREncode_AddDoubleToMap(&EC, "b", NAN);
+ QCBOREncode_AddUndefToMap(&EC, "t"); /* Test because dCBOR disallows */
+
+ QCBOREncode_CloseMap(&EC);
+
+ QCBOREncode_Finish(&EC, &Encoded);
+
+ static const uint8_t spExpectedCDE[] = {
+ 0xA6, 0x61, 0x61, 0x01, 0x61, 0x62, 0xF9, 0x7E,
+ 0x00, 0x61, 0x6B, 0xF9, 0x3C, 0x00, 0x61, 0x72,
+ 0xFA, 0x7F, 0x7F, 0xFF, 0xFF, 0x61, 0x74, 0xF7,
+ 0x61, 0x78, 0xF9, 0x40, 0x00};
+
+ if(UsefulBuf_Compare(UsefulBuf_FROM_BYTE_ARRAY_LITERAL(spExpectedCDE),
+ Encoded)) {
+ return 1;
+ }
+
+ /* Next, make sure methods that encode non-CDE error out */
+ QCBOREncode_Init(&EC, UsefulBuf_FROM_BYTE_ARRAY(spBigBuf));
+ QCBOREncode_SerializationCDE(&EC);
+ QCBOREncode_OpenMapIndefiniteLength(&EC);
+ QCBOREncode_CloseMap(&EC);
+ if(QCBOREncode_GetErrorState(&EC) != QCBOR_ERR_NOT_PREFERRED) {
+ return 100;
+ }
+
+
+ QCBOREncode_Init(&EC, UsefulBuf_FROM_BYTE_ARRAY(spBigBuf));
+ QCBOREncode_SerializationCDE(&EC);
+ QCBOREncode_AddDoubleNoPreferred(&EC, 0);
+ if(QCBOREncode_GetErrorState(&EC) != QCBOR_ERR_NOT_PREFERRED) {
+ return 101;
+ }
+
+ QCBOREncode_Init(&EC, UsefulBuf_FROM_BYTE_ARRAY(spBigBuf));
+ QCBOREncode_SerializationCDE(&EC);
+ QCBOREncode_AddFloatNoPreferred(&EC, 0);
+ if(QCBOREncode_GetErrorState(&EC) != QCBOR_ERR_NOT_PREFERRED) {
+ return 101;
+ }
+
+ return 0;
+}
+
+
+int32_t DCBORTest(void)
+{
+ QCBOREncodeContext EC;
+ UsefulBufC Encoded;
+
+ QCBOREncode_Init(&EC, UsefulBuf_FROM_BYTE_ARRAY(spBigBuf));
+
+ QCBOREncode_SerializationdCBOR(&EC);
+
+ /* Items added to test sorting and preferred encoding of numbers and floats */
+ QCBOREncode_OpenMap(&EC);
+ QCBOREncode_AddFloatToMap(&EC, "k", 1.0f);
+ QCBOREncode_AddInt64ToMap(&EC, "a", 1);
+ QCBOREncode_AddDoubleToMap(&EC, "x", 2.0);
+ QCBOREncode_AddDoubleToMap(&EC, "r", 3.4028234663852886E+38);
+ QCBOREncode_AddDoubleToMap(&EC, "b", NAN);
+
+ QCBOREncode_CloseMap(&EC);
+
+ QCBOREncode_Finish(&EC, &Encoded);
+
+ static const uint8_t spExpecteddCBOR[] = {
+ 0xA5, 0x61, 0x61, 0x01, 0x61, 0x62, 0xF9, 0x7E,
+ 0x00, 0x61, 0x6B, 0x01, 0x61, 0x72, 0xFA, 0x7F,
+ 0x7F, 0xFF, 0xFF, 0x61, 0x78, 0x02};
+
+ if(UsefulBuf_Compare(UsefulBuf_FROM_BYTE_ARRAY_LITERAL(spExpecteddCBOR),
+ Encoded)) {
+ return 1;
+ }
+
+
+ /* Next, make sure methods that encode non-CDE error out */
+ QCBOREncode_Init(&EC, UsefulBuf_FROM_BYTE_ARRAY(spBigBuf));
+ QCBOREncode_SerializationdCBOR(&EC);
+ QCBOREncode_OpenMapIndefiniteLength(&EC);
+ QCBOREncode_CloseMap(&EC);
+ if(QCBOREncode_GetErrorState(&EC) != QCBOR_ERR_NOT_PREFERRED) {
+ return 100;
+ }
+
+ /* Next, make sure methods that encode non-CDE error out */
+ QCBOREncode_Init(&EC, UsefulBuf_FROM_BYTE_ARRAY(spBigBuf));
+ QCBOREncode_SerializationdCBOR(&EC);
+ QCBOREncode_AddUndef(&EC);
+ QCBOREncode_CloseMap(&EC);
+ if(QCBOREncode_GetErrorState(&EC) != QCBOR_ERR_NOT_PREFERRED) {
+ return 101;
+ }
+
+
+ return 0;
+
+}
diff --git a/test/qcbor_encode_tests.h b/test/qcbor_encode_tests.h
index 5271fd4..526bfaf 100644
--- a/test/qcbor_encode_tests.h
+++ b/test/qcbor_encode_tests.h
@@ -1,6 +1,6 @@
/*==============================================================================
Copyright (c) 2016-2018, The Linux Foundation.
- Copyright (c) 2018-2023, Laurence Lundblade.
+ Copyright (c) 2018-2024, Laurence Lundblade.
All rights reserved.
Redistribution and use in source and binary forms, with or without
@@ -196,4 +196,9 @@
int32_t SortMapTest(void);
+/* Test CDE Encoding Mode (TODO: CDE definition is in progress in IETF) */
+int32_t CDETest(void);
+
+int32_t DCBORTest(void);
+
#endif /* defined(__QCBOR__qcbor_encode_tests__) */
diff --git a/test/run_tests.c b/test/run_tests.c
index 30e942e..bf18aa2 100644
--- a/test/run_tests.c
+++ b/test/run_tests.c
@@ -1,7 +1,7 @@
/*==============================================================================
run_tests.c -- test aggregator and results reporting
- Copyright (c) 2018-2023, Laurence Lundblade. All rights reserved.
+ Copyright (c) 2018-2024, Laurence Lundblade. All rights reserved.
Copyright (c) 2021, Arm Limited. All rights reserved.
SPDX-License-Identifier: BSD-3-Clause
@@ -149,7 +149,9 @@
#endif /* QCBOR_DISABLE_EXP_AND_MANTISSA */
TEST_ENTRY(ParseEmptyMapInMapTest),
TEST_ENTRY(BoolTest),
- TEST_ENTRY(SortMapTest)
+ TEST_ENTRY(SortMapTest),
+ TEST_ENTRY(CDETest),
+ TEST_ENTRY(DCBORTest)
};