Full big num implementation with preferred serialization and 65-bit negs (#219)
Encoding and decoding of negative big numbers now takes into account the offset of 1 for all CBOR negative numbers. Also, big numbers are encoded with preferred serialization -- when they can be encoded with type 0 and type 1 integers, they are. Encoding and decoding big numbers is no longer a pass through for tagging a binary string.
This is an incompatible change with QCBOR 1.x. A mode configuration call is added to return to the 1.x behavior if desired.
This is because of the realization in work on 65-bit negative numbers that big numbers need preferred serialization.
This affects big floats and decimal fractions when the mantissa is a big number.
New methods to encode big numbers with non-preferred serialization are added.
A new method is added to process a raw big number for the offset of one for negatives. This is outside of spiffy decode. It does a little big number arithmetic.
dCBOR numeric reduction now includes 65-bit integers both for number encoding and decoding.
* Checkpoint work on 65-bit negs
* check point ... mostly working
* Checkpoint work on preferred bignums
* Tests passing...
* bignum encode improved to near completion
* Fix #ifdef fan out and error condition bug
* Code tidiness
* debugging unreplicatable CI failure
* v1 compat mode; EAM encode test fan out starting
* debugging ci...
* hacking ci failures
* CI failure hacking
* CI hacking
* Clean up some left over junk
* more leftover junk
* Clean up test leftovers
* Fixes for #ifdef fan out
* Fix conversion of uint to zero, even though it's never used
* REmove more junk
---------
Co-authored-by: Laurence Lundblade <lgl@securitytheory.com>
diff --git a/src/ieee754.c b/src/ieee754.c
index f9b7a3f..40421ad 100644
--- a/src/ieee754.c
+++ b/src/ieee754.c
@@ -691,6 +691,7 @@
/* 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;
+ const uint64_t bIsNegative = uDouble & DOUBLE_SIGN_MASK;
if(nDoubleUnbiasedExponent == DOUBLE_EXPONENT_ZERO) {
if(uDoubleSignificand == 0) {
@@ -706,13 +707,21 @@
/* --- NAN --- */
Result.type = IEEE754_ToInt_NaN; /* dCBOR doesn't care about payload */
} else {
- /* --- INIFINITY --- */
+ /* --- INFINITY --- */
Result.type = IEEE754_ToInt_NO_CONVERSION;
}
- } else if(nDoubleUnbiasedExponent < 0 ||
- (nDoubleUnbiasedExponent >= ((uDouble & DOUBLE_SIGN_MASK) ? 63 : 64))) {
+ } else if(nDoubleUnbiasedExponent < 0) {
/* --- Exponent out of range --- */
Result.type = IEEE754_ToInt_NO_CONVERSION;
+ } else if(nDoubleUnbiasedExponent >= 64) {
+ if(nDoubleUnbiasedExponent == 64 && uDoubleSignificand == 0 && bIsNegative) {
+ /* Very special case for -18446744073709551616.0 */
+ Result.integer.un_signed = 0; /* No negative 0, use it to indicate 2^64 */
+ Result.type = IEEE754_ToInt_IS_65BIT_NEG;
+ } else {
+ /* --- Exponent out of range --- */
+ Result.type = IEEE754_ToInt_NO_CONVERSION;
+ }
} else {
/* Conversion only fails when the input is too large or is not a
* whole number, never because of lack of precision because
@@ -737,10 +746,15 @@
/* Numbers greater than 2^52 with at most 52 significant bits */
uInteger <<= nDoubleUnbiasedExponent - DOUBLE_NUM_SIGNIFICAND_BITS;
}
- if(uDouble & DOUBLE_SIGN_MASK) {
+ if(bIsNegative) {
/* Cast safe because exponent range check above */
- Result.integer.is_signed = -((int64_t)uInteger);
- Result.type = IEEE754_ToInt_IS_INT;
+ if(nDoubleUnbiasedExponent == 63) {
+ Result.integer.un_signed = uInteger;
+ Result.type = IEEE754_ToInt_IS_65BIT_NEG;
+ } else {
+ 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;
@@ -769,6 +783,8 @@
/* 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;
+ const uint64_t bIsNegative = uSingle & SINGLE_SIGN_MASK;
+
if(nSingleUnbiasedExponent == SINGLE_EXPONENT_ZERO) {
if(uSingleleSignificand == 0 && !(uSingle & SINGLE_SIGN_MASK)) {
@@ -786,10 +802,18 @@
} else {
Result.type = IEEE754_ToInt_NO_CONVERSION;
}
- } else if(nSingleUnbiasedExponent < 0 ||
- (nSingleUnbiasedExponent >= ((uSingle & SINGLE_SIGN_MASK) ? 63 : 64))) {
+ } else if(nSingleUnbiasedExponent < 0) {
/* --- Exponent out of range --- */
Result.type = IEEE754_ToInt_NO_CONVERSION;
+ } else if(nSingleUnbiasedExponent >= 64) {
+ if(nSingleUnbiasedExponent == 64 && uSingleleSignificand == 0 && bIsNegative) {
+ /* Very special case for -18446744073709551616.0 */
+ Result.integer.un_signed = 0; /* No negative 0, use it to indicate 2^64 */
+ Result.type = IEEE754_ToInt_IS_65BIT_NEG;
+ } else {
+ /* --- Exponent out of range --- */
+ Result.type = IEEE754_ToInt_NO_CONVERSION;
+ }
} else {
/* Conversion only fails when the input is too large or is not a
* whole number, never because of lack of precision because
@@ -814,9 +838,15 @@
/* 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;
+ if(bIsNegative) {
+ /* Cast safe because exponent range check above */
+ if(nSingleUnbiasedExponent == 63) {
+ Result.integer.un_signed = uInteger;
+ Result.type = IEEE754_ToInt_IS_65BIT_NEG;
+ } else {
+ 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;
@@ -837,31 +867,37 @@
uint64_t uDoubleSignificand;
int nPrecisionBits;
- /* Figure out the exponent and normalize the significand. This is
- * done by shifting out all leading zero bits and counting them. If
- * none are shifted out, the exponent is 63. */
- uDoubleSignificand = uInt;
- nDoubleUnbiasedExponent = 63;
- while(1) {
- if(uDoubleSignificand & 0x8000000000000000UL) {
- break;
+ if(uInt == 0) {
+ uDoubleSignificand = 0;
+ nDoubleUnbiasedExponent = DOUBLE_EXPONENT_ZERO;
+
+ } else {
+ /* Figure out the exponent and normalize the significand. This is
+ * done by shifting out all leading zero bits and counting them. If
+ * none are shifted out, the exponent is 63. */
+ uDoubleSignificand = uInt;
+ nDoubleUnbiasedExponent = 63;
+ while(1) {
+ if(uDoubleSignificand & 0x8000000000000000UL) {
+ break;
+ }
+ uDoubleSignificand <<= 1;
+ nDoubleUnbiasedExponent--;
+ };
+
+ /* Position significand correctly for a double. Only shift 63 bits
+ * because of the 1 that is present by implication in IEEE 754.*/
+ uDoubleSignificand >>= 63 - DOUBLE_NUM_SIGNIFICAND_BITS;
+
+ /* Subtract 1 which is present by implication in IEEE 754 */
+ uDoubleSignificand -= 1ULL << (DOUBLE_NUM_SIGNIFICAND_BITS);
+
+ nPrecisionBits = IEEE754_Private_CountPrecisionBits(uInt) - (64 - nDoubleUnbiasedExponent);
+
+ if(nPrecisionBits > DOUBLE_NUM_SIGNIFICAND_BITS) {
+ /* Will lose precision if converted */
+ return IEEE754_UINT_TO_DOUBLE_OOB;
}
- uDoubleSignificand <<= 1;
- nDoubleUnbiasedExponent--;
- };
-
- /* Position significand correctly for a double. Only shift 63 bits
- * because of the 1 that is present by implication in IEEE 754.*/
- uDoubleSignificand >>= 63 - DOUBLE_NUM_SIGNIFICAND_BITS;
-
- /* Subtract 1 which is present by implication in IEEE 754 */
- uDoubleSignificand -= 1ULL << (DOUBLE_NUM_SIGNIFICAND_BITS);
-
- nPrecisionBits = IEEE754_Private_CountPrecisionBits(uInt) - (64 - nDoubleUnbiasedExponent);
-
- if(nPrecisionBits > DOUBLE_NUM_SIGNIFICAND_BITS) {
- /* Will lose precision if converted */
- return IEEE754_UINT_TO_DOUBLE_OOB;
}
return IEEE754_AssembleDouble((uint64_t)uIsNegative,
diff --git a/src/ieee754.h b/src/ieee754.h
index c82b9b4..fed28f1 100644
--- a/src/ieee754.h
+++ b/src/ieee754.h
@@ -97,6 +97,7 @@
struct IEEE754_ToInt {
enum {IEEE754_ToInt_IS_INT,
IEEE754_ToInt_IS_UINT,
+ IEEE754_ToInt_IS_65BIT_NEG,
IEEE754_ToInt_NO_CONVERSION,
IEEE754_ToInt_NaN
} type;
diff --git a/src/qcbor_decode.c b/src/qcbor_decode.c
index 05363f0..41e6b21 100644
--- a/src/qcbor_decode.c
+++ b/src/qcbor_decode.c
@@ -895,12 +895,18 @@
*
* CBOR doesn't explicitly specify two's compliment for integers but
* all CPUs use it these days and the test vectors in the RFC are
- * so. All integers in the CBOR structure are positive and the major
+ * so. All integers in encoded CBOR are unsigned and the CBOR major
* type indicates positive or negative. CBOR can express positive
- * integers up to 2^x - 1 where x is the number of bits and negative
- * integers down to 2^x. Note that negative numbers can be one more
- * away from zero than positive. Stdint, as far as I can tell, uses
- * two's compliment to represent negative integers.
+ * integers up to 2^64 - 1 negative integers down to -2^64. Note that
+ * negative numbers can be one more
+ * away from zero than positive because there is no negative zero.
+ *
+ * The "65-bit negs" are values CBOR can encode that can't fit
+ * into an int64_t or uint64_t. They decoded as a special type
+ * QCBOR_TYPE_65BIT_NEG_INT. Not that this type does NOT
+ * take into account the offset of one for CBOR negative integers.
+ * It must be applied to get the correct value. Applying this offset
+ * would overflow a uint64_t.
*/
static QCBORError
QCBOR_Private_DecodeInteger(const int nMajorType,
@@ -927,11 +933,8 @@
} else {
if(uArgument <= INT64_MAX) {
- /* CBOR's representation of negative numbers lines up with
- * the two-compliment representation. A negative integer has
- * one more in range than a positive integer. INT64_MIN is
- * equal to (-INT64_MAX) - 1.
- */
+ /* INT64_MIN is one further away from 0 than INT64_MAX
+ * so the -1 here doesn't overflow. */
pDecodedItem->val.int64 = (-(int64_t)uArgument) - 1;
pDecodedItem->uDataType = QCBOR_TYPE_INT64;
@@ -1169,6 +1172,8 @@
}
+#ifndef USEFULBUF_DISABLE_ALL_FLOAT
+
#if !defined(QCBOR_DISABLE_DECODE_CONFORMANCE) && !defined(QCBOR_DISABLE_PREFERRED_FLOAT)
static QCBORError
@@ -1256,8 +1261,7 @@
return QCBOR_SUCCESS;
}
-#else /* ! QCBOR_DISABLE_PREFERRED_FLOAT && ! QCBOR_DISABLE_PREFERRED_FLOAT */
-#ifndef QCBOR_DISABLE_FLOAT_HW_USE
+#else /* ! QCBOR_DISABLE_DECODE_CONFORMANCE && ! QCBOR_DISABLE_PREFERRED_FLOAT */
static QCBORError
QCBORDecode_Private_SingleConformance(const float f, uint8_t uDecodeMode)
@@ -1280,11 +1284,9 @@
return QCBOR_SUCCESS;
}
}
-#endif /* ! QCBOR_DISABLE_FLOAT_HW_USE */
#endif /* ! QCBOR_DISABLE_DECODE_CONFORMANCE && ! QCBOR_DISABLE_PREFERRED_FLOAT */
-
/*
* Decode a float
*/
@@ -1318,7 +1320,6 @@
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
@@ -1349,12 +1350,10 @@
* 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;
@@ -1362,13 +1361,14 @@
if(uReturn != QCBOR_SUCCESS) {
break;
}
-#endif /* ! USEFULBUF_DISABLE_ALL_FLOAT */
uReturn = FLOAT_ERR_CODE_NO_FLOAT(QCBOR_SUCCESS);
break;
}
return uReturn;
}
+#endif /* ! USEFULBUF_DISABLE_ALL_FLOAT */
+
/* Make sure #define value line up as DecodeSimple counts on this. */
@@ -1437,7 +1437,11 @@
case HALF_PREC_FLOAT: /* 25 */
case SINGLE_PREC_FLOAT: /* 26 */
case DOUBLE_PREC_FLOAT: /* 27 */
+#ifndef USEFULBUF_DISABLE_ALL_FLOAT
uReturn = QCBOR_Private_DecodeFloat(uDecodeMode, nAdditionalInfo, uArgument, pDecodedItem);
+#else
+ uReturn = QCBOR_ERR_ALL_FLOAT_DISABLED;
+#endif /* ! USEFULBUF_DISABLE_ALL_FLOAT */
break;
case CBOR_SIMPLEV_FALSE: /* 20 */
@@ -1528,6 +1532,8 @@
uint64_t uArgument = 0;
int nAdditionalInfo = 0;
+ memset(pDecodedItem, 0, sizeof(QCBORItem));
+
/* Decode the "head" that every CBOR item has into the major type,
* argument and the additional info.
*/
@@ -1539,12 +1545,11 @@
&nMajorType,
&uArgument,
&nAdditionalInfo);
+
if(uReturn != QCBOR_SUCCESS) {
return uReturn;
}
- memset(pDecodedItem, 0, sizeof(QCBORItem));
-
/* All the functions below get inlined by the optimizer. This code
* is easier to read with them all being similar functions, even if
* some functions don't do much.
@@ -5297,7 +5302,7 @@
* numbers.
*/
static QCBORError
-QCBOR_Private_ProcessBigNum(const uint8_t uTagRequirement,
+QCBOR_Private_ProcessBigNum(const uint8_t uTagRequirement,
const QCBORItem *pItem,
UsefulBufC *pValue,
bool *pbIsNegative)
@@ -5336,6 +5341,7 @@
bool *pbIsNegative)
{
QCBORItem Item;
+
QCBORDecode_VGetNext(pMe, &Item);
if(pMe->uLastError) {
return;
@@ -6708,6 +6714,7 @@
case QCBOR_TYPE_65BIT_NEG_INT:
#ifndef QCBOR_DISABLE_FLOAT_HW_USE
+ // TODO: don't use float HW. We have the function to do it.
*pdValue = -(double)pItem->val.uint64 - 1;
break;
#else
@@ -7715,15 +7722,17 @@
{
QCBORItem Item;
struct IEEE754_ToInt ToInt;
- double d;
+ double dNum;
QCBORError uError;
if(pMe->uLastError != QCBOR_SUCCESS) {
return;
}
+ // TODO:VGetNext?
uError = QCBORDecode_GetNext(pMe, &Item);
if(uError != QCBOR_SUCCESS) {
+ *pNumber = Item;
pMe->uLastError = (uint8_t)uError;
return;
}
@@ -7772,15 +7781,25 @@
}
break;
-
case QCBOR_TYPE_65BIT_NEG_INT:
- d = IEEE754_UintToDouble(Item.val.uint64, 1);
- if(d == IEEE754_UINT_TO_DOUBLE_OOB) {
- *pNumber = Item;
- } else {
+ if(Item.val.uint64 == UINT64_MAX) {
+ /* The value -18446744073709551616 is encoded as an
+ * unsigned 18446744073709551615. It's a whole number that
+ * needs to be returned as a double. It can't be handled
+ * by IEEE754_UintToDouble because 18446744073709551616
+ * doesn't fit into a uint64_t. You can't get it by adding
+ * 1 to 18446744073709551615.
+ */
+ pNumber->val.dfnum = -18446744073709551616.0;
pNumber->uDataType = QCBOR_TYPE_DOUBLE;
- /* -1 is because of CBOR offset of negative numbers */
- pNumber->val.dfnum = d - 1;
+ } else {
+ dNum = IEEE754_UintToDouble(Item.val.uint64 + 1, 1);
+ if(dNum == IEEE754_UINT_TO_DOUBLE_OOB) {
+ *pNumber = Item;
+ } else {
+ pNumber->val.dfnum = dNum;
+ pNumber->uDataType = QCBOR_TYPE_DOUBLE;
+ }
}
break;
@@ -7792,3 +7811,317 @@
}
#endif /* ! USEFULBUF_DISABLE_ALL_FLOAT && ! QCBOR_DISABLE_PREFERRED_FLOAT */
+
+
+static UsefulBufC
+QCBORDecode_IntToBigNum(uint64_t uNum,
+ const UsefulBuf BigNumBuf)
+{
+ UsefulOutBuf OB;
+
+ /* With a UsefulOutBuf, there's no pointer math here. */
+ UsefulOutBuf_Init(&OB, BigNumBuf);
+
+ /* Must copy one byte even if zero. The loop, mask and shift
+ * algorithm provides endian conversion.
+ */
+ do {
+ UsefulOutBuf_InsertByte(&OB, uNum & 0xff, 0);
+ uNum >>= 8;
+ } while(uNum);
+
+ return UsefulOutBuf_OutUBuf(&OB);
+}
+
+
+static UsefulBufC
+QCBORDecode_Private_RemoveLeadingZeros(UsefulBufC String)
+{
+ while(String.len > 1) {
+ if(*(const uint8_t *)String.ptr) {
+ break;
+ }
+ String.len--;
+ String.ptr = (const uint8_t *)String.ptr + 1;
+ }
+
+ return String;
+}
+
+
+/* Add one to the big number and put the result in a new UsefulBufC
+ * from storage in UsefulBuf.
+ *
+ * Leading zeros must be removed before calling this.
+ *
+ * Code Reviewers: THIS FUNCTION DOES POINTER MATH
+ */
+static UsefulBufC
+QCBORDecode_BigNumCopyPlusOne(UsefulBufC BigNum,
+ UsefulBuf BigNumBuf)
+{
+ uint8_t uCarry;
+ uint8_t uSourceValue;
+ const uint8_t *pSource;
+ uint8_t *pDest;
+ ptrdiff_t uDestBytesLeft;
+
+ /* Start adding at the LSB */
+ pSource = &((const uint8_t *)BigNum.ptr)[BigNum.len-1];
+ pDest = &((uint8_t *)BigNumBuf.ptr)[BigNumBuf.len-1];
+
+ uCarry = 1; /* Gets set back to zero if add the next line doesn't wrap */
+ *pDest = *pSource + 1;
+ while(1) {
+ /* Wrap around from 0xff to 0 is a defined operation for
+ unsigned addition in C. */
+ if(*pDest != 0) {
+ /* The add operation didn't wrap so no more carry. This
+ * funciton only adds one, so when there is no more carry,
+ * carrying is over to the end.
+ */
+ uCarry = 0;
+ }
+
+ uDestBytesLeft = pDest - (uint8_t *)BigNumBuf.ptr;
+ if(pSource <= (const uint8_t *)BigNum.ptr && uCarry == 0) {
+ break; /* Successful exit */
+ }
+ if(pSource > (const uint8_t *)BigNum.ptr) {
+ uSourceValue = *--pSource;
+ } else {
+ /* All source bytes processed, but not the last carry */
+ uSourceValue = 0;
+ }
+
+ pDest--;
+ if(uDestBytesLeft < 0) {
+ return NULLUsefulBufC; /* Not enough space in destination buffer */
+ }
+
+ *pDest = uSourceValue + uCarry;
+ }
+
+ return (UsefulBufC){pDest, BigNumBuf.len - (size_t)uDestBytesLeft};
+}
+
+
+/* This returns 1 when uNum is 0 */
+static size_t
+QCBORDecode_Private_CountNonZeroBytes(uint64_t uNum)
+{
+ size_t uCount = 0;
+ do {
+ uCount++;
+ uNum >>= 8;
+ } while(uNum);
+
+ return uCount;
+}
+
+
+/*
+ * Public function, see header qcbor/qcbor_decode.h
+ */
+QCBORError
+QCBORDecode_BignumPreferred(const QCBORItem Item,
+ UsefulBuf BigNumBuf,
+ UsefulBufC *pBigNum,
+ bool *pbIsNegative)
+{
+ QCBORError uResult;
+ size_t uLen;
+ UsefulBufC BigNum;
+ int uType;
+
+ uType = Item.uDataType;
+ if(uType == QCBOR_TYPE_BYTE_STRING) {
+ uType = *pbIsNegative ? QCBOR_TYPE_POSBIGNUM : QCBOR_TYPE_NEGBIGNUM;
+ }
+
+ static const uint8_t Zero[] = {0x00};
+ BigNum = UsefulBuf_FROM_BYTE_ARRAY_LITERAL(Zero);
+ if((uType == QCBOR_TYPE_POSBIGNUM || uType == QCBOR_TYPE_NEGBIGNUM) &&
+ Item.val.bigNum.len) {
+ BigNum = QCBORDecode_Private_RemoveLeadingZeros(Item.val.bigNum);
+ }
+
+ /* Compute required length so it can be returned if buffer is too small */
+ switch(uType) {
+ case QCBOR_TYPE_INT64:
+ uLen = QCBORDecode_Private_CountNonZeroBytes((uint64_t)(Item.val.int64 < 0 ? -Item.val.int64 : Item.val.int64));
+ break;
+
+ case QCBOR_TYPE_UINT64:
+ uLen = QCBORDecode_Private_CountNonZeroBytes(Item.val.uint64);
+ break;
+
+ case QCBOR_TYPE_65BIT_NEG_INT:
+ uLen = Item.val.uint64 == UINT64_MAX ? 9 : QCBORDecode_Private_CountNonZeroBytes(Item.val.uint64);
+ break;
+
+ case QCBOR_TYPE_POSBIGNUM:
+ uLen = BigNum.len;
+ break;
+
+ case QCBOR_TYPE_NEGBIGNUM:
+ uLen = BigNum.len;
+ if(UsefulBuf_IsValue(BigNum, 0xff) == SIZE_MAX) {
+ uLen++;
+ }
+ break;
+
+ default:
+ uLen = 0;
+ }
+
+ *pBigNum = (UsefulBufC){NULL, uLen};
+
+ if(BigNumBuf.len < uLen || uLen == 0 || BigNumBuf.ptr == NULL) {
+ return BigNumBuf.ptr == NULL ? QCBOR_SUCCESS : QCBOR_ERR_BUFFER_TOO_SMALL;
+ /* Buffer is too short or type is wrong */
+ }
+
+ uResult = QCBOR_SUCCESS;
+
+ if(uType == QCBOR_TYPE_POSBIGNUM) {
+ *pBigNum = UsefulBuf_Copy(BigNumBuf, BigNum);
+ *pbIsNegative = false;
+ } else if(uType == QCBOR_TYPE_UINT64) {
+ *pBigNum = QCBORDecode_IntToBigNum(Item.val.uint64, BigNumBuf);
+ *pbIsNegative = false;
+ } else if(uType == QCBOR_TYPE_INT64) {
+ *pbIsNegative = Item.val.int64 < 0;
+ *pBigNum = QCBORDecode_IntToBigNum((uint64_t)(*pbIsNegative ? -Item.val.int64 : Item.val.int64), BigNumBuf);
+ } else if(uType == QCBOR_TYPE_65BIT_NEG_INT) {
+ *pbIsNegative = true;
+ if(Item.val.uint64 == UINT64_MAX) {
+ /* The one value that can't be done with a computation
+ * because it would overflow a uint64_t*/
+ static const uint8_t TwoToThe64[] = {0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ *pBigNum = UsefulBuf_Copy(BigNumBuf, UsefulBuf_FROM_BYTE_ARRAY_LITERAL(TwoToThe64));
+ } else {
+ *pBigNum = QCBORDecode_IntToBigNum(Item.val.uint64 + 1, BigNumBuf);
+ }
+ } else if(uType == QCBOR_TYPE_NEGBIGNUM) {
+ /* The messy one. Take the stuff in the buffer and copy it to
+ * the new buffer, adding one to it. This might be one byte
+ * bigger than the original because of the carry from adding
+ * one.*/
+ *pbIsNegative = true;
+ *pBigNum = QCBORDecode_BigNumCopyPlusOne(BigNum, BigNumBuf);
+
+ } else {
+ uResult = QCBOR_ERR_UNEXPECTED_TYPE;
+ }
+
+ return uResult;
+}
+
+
+static QCBORError
+QCBOR_Private_ProcessPreferredBigNum(const uint8_t uTagRequirement,
+ const QCBORItem *pItem,
+ UsefulBuf BigNumBuf,
+ UsefulBufC *pValue,
+ bool *pbIsNegative)
+{
+ if(pItem->uDataType != QCBOR_TYPE_INT64 &&
+ pItem->uDataType != QCBOR_TYPE_UINT64 &&
+ pItem->uDataType != QCBOR_TYPE_65BIT_NEG_INT) {
+
+ /* The integer types are always OK. If it's not an integer type drop
+ * in to the tag type checking system. */
+ const QCBOR_Private_TagSpec TagSpec =
+ {
+ uTagRequirement,
+ {QCBOR_TYPE_POSBIGNUM, QCBOR_TYPE_NEGBIGNUM, QCBOR_TYPE_NONE, QCBOR_TYPE_NONE},
+ {QCBOR_TYPE_BYTE_STRING, QCBOR_TYPE_NONE, QCBOR_TYPE_NONE, QCBOR_TYPE_NONE}
+ };
+
+ QCBORError uErr = QCBOR_Private_CheckTagRequirement(TagSpec, pItem);
+ if(uErr != QCBOR_SUCCESS) {
+ return uErr;
+ }
+ }
+
+ return QCBORDecode_BignumPreferred(*pItem, BigNumBuf, pValue, pbIsNegative);
+}
+
+
+/*
+ * Public function, see header qcbor/qcbor_decode.h
+ */
+void
+QCBORDecode_GetBigNumPreferred(QCBORDecodeContext *pMe,
+ const uint8_t uTagRequirement,
+ UsefulBuf BigNumBuf,
+ UsefulBufC *pValue,
+ bool *pbIsNegative)
+{
+ QCBORItem Item;
+
+ if(pMe->uLastError != QCBOR_SUCCESS) {
+ /* Already in error state, do nothing */
+ return;
+ }
+
+ QCBORError uError = QCBORDecode_GetNext(pMe, &Item);
+ if(uError != QCBOR_SUCCESS) {
+ pMe->uLastError = (uint8_t)uError;
+ return;
+ }
+
+ QCBOR_Private_ProcessPreferredBigNum(uTagRequirement, &Item, BigNumBuf, pValue, pbIsNegative);
+}
+
+
+/*
+ * Public function, see header qcbor/qcbor_decode.h
+ */
+void
+QCBORDecode_GetPreferredBignumInMapN(QCBORDecodeContext *pMe,
+ const int64_t nLabel,
+ const uint8_t uTagRequirement,
+ UsefulBuf BigNumBuf,
+ UsefulBufC *pValue,
+ bool *pbIsNegative)
+{
+ QCBORItem Item;
+ QCBORDecode_GetItemInMapN(pMe, nLabel, QCBOR_TYPE_ANY, &Item);
+ if(pMe->uLastError != QCBOR_SUCCESS) {
+ return;
+ }
+
+ pMe->uLastError = (uint8_t)QCBOR_Private_ProcessPreferredBigNum(uTagRequirement,
+ &Item,
+ BigNumBuf,
+ pValue,
+ pbIsNegative);
+}
+
+/*
+ * Public function, see header qcbor/qcbor_decode.h
+ */
+void
+QCBORDecode_GetPreferredBignumInMapSZ(QCBORDecodeContext *pMe,
+ const char * szLabel,
+ const uint8_t uTagRequirement,
+ UsefulBuf BigNumBuf,
+ UsefulBufC *pValue,
+ bool *pbIsNegative)
+{
+ QCBORItem Item;
+ QCBORDecode_GetItemInMapSZ(pMe, szLabel, QCBOR_TYPE_ANY, &Item);
+ if(pMe->uLastError != QCBOR_SUCCESS) {
+ return;
+ }
+
+ pMe->uLastError = (uint8_t)QCBOR_Private_ProcessPreferredBigNum(uTagRequirement,
+ &Item,
+ BigNumBuf,
+ pValue,
+ pbIsNegative);
+}
+
+
diff --git a/src/qcbor_encode.c b/src/qcbor_encode.c
index 7d2f7fb..fa7f485 100644
--- a/src/qcbor_encode.c
+++ b/src/qcbor_encode.c
@@ -580,29 +580,6 @@
/*
- * Public functions for adding negative integers. See qcbor/qcbor_encode.h
- */
-void
-QCBOREncode_AddNegativeUInt64(QCBOREncodeContext *pMe, const uint64_t uValue)
-{
-#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);
-}
-
-
-/*
* Public functions for adding signed integers. See qcbor/qcbor_encode.h
*/
void
@@ -679,6 +656,7 @@
IEEE754_union FloatResult;
bool bNoNaNPayload;
struct IEEE754_ToInt IntResult;
+ uint64_t uNegValue;
#ifndef QCBOR_DISABLE_ENCODE_USAGE_GUARDS
if(IEEE754_DoubleHasNaNPayload(dNum) && !(pMe->uAllow & QCBOR_ENCODE_ALLOW_NAN_PAYLOAD)) {
@@ -696,6 +674,16 @@
case IEEE754_ToInt_IS_UINT:
QCBOREncode_AddUInt64(pMe, IntResult.integer.un_signed);
return;
+ case IEEE754_ToInt_IS_65BIT_NEG:
+ {
+ if(IntResult.integer.un_signed == 0) {
+ uNegValue = UINT64_MAX;
+ } else {
+ uNegValue = IntResult.integer.un_signed-1;
+ }
+ QCBOREncode_AddNegativeUInt64(pMe, uNegValue);
+ }
+ return;
case IEEE754_ToInt_NaN:
dNum = NAN;
bNoNaNPayload = true;
@@ -728,6 +716,7 @@
IEEE754_union FloatResult;
bool bNoNaNPayload;
struct IEEE754_ToInt IntResult;
+ uint64_t uNegValue;
#ifndef QCBOR_DISABLE_ENCODE_USAGE_GUARDS
if(IEEE754_SingleHasNaNPayload(fNum) && !(pMe->uAllow & QCBOR_ENCODE_ALLOW_NAN_PAYLOAD)) {
@@ -745,6 +734,16 @@
case IEEE754_ToInt_IS_UINT:
QCBOREncode_AddUInt64(pMe, IntResult.integer.un_signed);
return;
+ case IEEE754_ToInt_IS_65BIT_NEG:
+ {
+ if(IntResult.integer.un_signed == 0) {
+ uNegValue = UINT64_MAX;
+ } else {
+ uNegValue = IntResult.integer.un_signed-1;
+ }
+ QCBOREncode_AddNegativeUInt64(pMe, uNegValue);
+ }
+ return;
case IEEE754_ToInt_NaN:
fNum = NAN;
bNoNaNPayload = true;
@@ -764,6 +763,302 @@
+
+/* Actual addition of a positive/negative big num tag */
+static void
+QCBOREncode_Private_AddTBignum(QCBOREncodeContext *pMe,
+ const uint64_t uTag,
+ const uint8_t uTagRequirement,
+ const UsefulBufC BigNum)
+{
+ if(uTagRequirement == QCBOR_ENCODE_AS_TAG) {
+ QCBOREncode_AddTag(pMe, uTag);
+ }
+ QCBOREncode_AddBytes(pMe, BigNum);
+}
+
+
+/* Add a positive/negative big num, non-preferred */
+static void
+QCBOREncode_Private_AddTBignumNoPreferred(QCBOREncodeContext *pMe,
+ const uint64_t uTag,
+ const uint8_t uTagRequirement,
+ const UsefulBufC BigNum)
+{
+#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_AddTBignum(pMe, uTag, uTagRequirement, BigNum);
+}
+
+
+/* Is there a carry when you add 1 to the BigNum? */
+static bool
+QCBOREncode_Private_BigNumCarry(UsefulBufC BigNum)
+{
+ bool bCarry;
+ UsefulBufC SubBigNum;
+
+ if(BigNum.len == 0) {
+ return true; /* Adding one to zero-length string gives a carry */
+ } else {
+ SubBigNum = UsefulBuf_Tail(BigNum, 1);
+ bCarry = QCBOREncode_Private_BigNumCarry(SubBigNum);
+ if(*(const uint8_t *)BigNum.ptr == 0x00 && bCarry) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+}
+
+
+/*
+ * Output negative bignum bytes with subtraction of 1
+ */
+void
+QCBOREncode_Private_AddTNegativeBignum(QCBOREncodeContext *pMe,
+ const uint8_t uTagRequirement,
+ const UsefulBufC BigNum)
+{
+ size_t uLen;
+ bool bCarry;
+ bool bCopiedSomething;
+ uint8_t uByte;
+ UsefulBufC SubString;
+ UsefulBufC NextSubString;
+
+ if(uTagRequirement == QCBOR_ENCODE_AS_TAG) {
+ QCBOREncode_AddTag(pMe, CBOR_TAG_NEG_BIGNUM);
+ }
+
+ /* This works on any length without the need of an additional buffer */
+
+ /* This subtracts one, possibly making the string shorter by one
+ * 0x01 -> 0x00
+ * 0x01 0x00 -> 0xff
+ * 0x00 0x01 0x00 -> 0x00 0xff
+ * 0x02 0x00 -> 0x01 0xff
+ * 0xff -> 0xfe
+ * 0xff 0x00 -> 0xfe 0xff
+ * 0x01 0x00 0x00 -> 0xff 0xff
+ */
+
+ /* Compute the length up front because it goes in the head */
+ bCarry = QCBOREncode_Private_BigNumCarry(UsefulBuf_Tail(BigNum, 1));
+ uLen = BigNum.len;
+ if(bCarry && *(const uint8_t *)BigNum.ptr >= 1 && BigNum.len > 1) {
+ uLen--;
+ }
+ QCBOREncode_Private_AppendCBORHead(pMe, CBOR_MAJOR_TYPE_BYTE_STRING, uLen, 0);
+
+ SubString = BigNum;
+ bCopiedSomething = false;
+ while(SubString.len) {
+ uByte = *((const uint8_t *)SubString.ptr);
+ NextSubString = UsefulBuf_Tail(SubString, 1);
+ bCarry = QCBOREncode_Private_BigNumCarry(NextSubString);
+ if(bCarry) {
+ uByte--;
+ }
+ if(bCopiedSomething || NextSubString.len == 0 || uByte != 0) { /* No leading zeros, but one zero is OK */
+ UsefulOutBuf_AppendByte(&(pMe->OutBuf), uByte);
+ bCopiedSomething = true;
+ }
+ SubString = NextSubString;
+ }
+}
+
+
+static UsefulBufC
+QCBOREncode_Private_RemoveLeadingZeros(UsefulBufC String)
+{
+ while(String.len > 1) {
+ if(*(const uint8_t *)String.ptr) {
+ break;
+ }
+ String.len--;
+ String.ptr = (const uint8_t *)String.ptr + 1;
+ }
+
+ return String;
+}
+
+
+/*
+ * Public function. See qcbor/qcbor_encode.h
+ */
+void
+QCBOREncode_AddTNegativeBignumNoPreferred(QCBOREncodeContext *pMe,
+ const uint8_t uTagRequirement,
+ const UsefulBufC BigNum)
+{
+#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 */
+
+ if(UsefulBuf_IsValue(BigNum, 0) == SIZE_MAX) {
+ pMe->uError = QCBOR_ERR_NO_NEGATIVE_ZERO;
+ return;
+ }
+
+ if(pMe->uConfig & QCBOR_ENCODE_CONFIG_V1_COMPAT) {
+ QCBOREncode_Private_AddTBignum(pMe, CBOR_TAG_NEG_BIGNUM, uTagRequirement, BigNum);
+ } else {
+ QCBOREncode_Private_AddTNegativeBignum(pMe, uTagRequirement, QCBOREncode_Private_RemoveLeadingZeros(BigNum));
+ }
+}
+
+
+void
+QCBOREncode_AddTNegativeBignumNoPreferredToMap(QCBOREncodeContext *pMe,
+ const char *szLabel,
+ uint8_t uTagRequirement,
+ UsefulBufC BigNumber)
+{
+ QCBOREncode_AddSZString(pMe, szLabel);
+ QCBOREncode_AddTNegativeBignumNoPreferred(pMe, uTagRequirement, BigNumber);
+}
+
+
+void
+QCBOREncode_AddTNegativeBignumNoPreferredToMapN(QCBOREncodeContext *pMe,
+ int64_t nLabel,
+ uint8_t uTagRequirement,
+ UsefulBufC BigNumber)
+{
+ QCBOREncode_AddInt64(pMe, nLabel);
+ QCBOREncode_AddTNegativeBignumNoPreferred(pMe, uTagRequirement, BigNumber);
+}
+
+/*
+ * Public function. See qcbor/qcbor_encode.h
+ */
+void
+QCBOREncode_AddTPositiveBignumNoPreferred(QCBOREncodeContext *pMe,
+ const uint8_t uTagRequirement,
+ const UsefulBufC BigNum)
+{
+ QCBOREncode_Private_AddTBignumNoPreferred(pMe, CBOR_TAG_POS_BIGNUM, uTagRequirement, BigNum);
+}
+
+void
+QCBOREncode_AddTPositiveBignumNoPreferredToMap(QCBOREncodeContext *pMe,
+ const char *szLabel,
+ const uint8_t uTagRequirement,
+ const UsefulBufC BigNum)
+{
+ QCBOREncode_AddSZString(pMe, szLabel);
+ QCBOREncode_Private_AddTBignumNoPreferred(pMe, CBOR_TAG_POS_BIGNUM, uTagRequirement, BigNum);
+}
+
+void
+QCBOREncode_AddTPositiveBignumNoPreferredToMapN(QCBOREncodeContext *pMe,
+ int64_t nLabel,
+ const uint8_t uTagRequirement,
+ const UsefulBufC BigNum)
+{
+ QCBOREncode_AddInt64(pMe, nLabel);
+ QCBOREncode_Private_AddTBignumNoPreferred(pMe, CBOR_TAG_POS_BIGNUM, uTagRequirement, BigNum);
+}
+
+
+
+
+/* This will return an erroneous value if BigNum.len > 8 */
+/* Convert from bignum to uint with endianess conversion */
+static uint64_t
+QCBOREncode_Private_BigNumToUInt(const UsefulBufC BigNum)
+{
+ uint64_t uInt;
+ size_t uIndex;
+
+ uInt = 0;
+ for(uIndex = 0; uIndex < BigNum.len; uIndex++) {
+ uInt = (uInt << 8) + ((const uint8_t *)BigNum.ptr)[uIndex];
+ }
+
+ return uInt;
+}
+
+
+/*
+ * Public function. See qcbor/qcbor_encode.h
+ */
+void
+QCBOREncode_AddTPositiveBignum(QCBOREncodeContext *pMe,
+ const uint8_t uTagRequirement,
+ const UsefulBufC BigNum)
+{
+ if(pMe->uConfig & QCBOR_ENCODE_CONFIG_V1_COMPAT) {
+ QCBOREncode_AddTPositiveBignumNoPreferred(pMe, uTagRequirement, BigNum);
+ } else {
+ const UsefulBufC BigNumNLZ = QCBOREncode_Private_RemoveLeadingZeros(BigNum);
+ if(BigNumNLZ.len <= 8) {
+ /* Preferred serialization requires conversion to type 0 */
+ QCBOREncode_AddUInt64(pMe, QCBOREncode_Private_BigNumToUInt(BigNumNLZ));
+ } else {
+ QCBOREncode_Private_AddTBignum(pMe, CBOR_TAG_POS_BIGNUM, uTagRequirement, BigNumNLZ);
+ }
+ }
+}
+
+
+/*
+ * Public function. See qcbor/qcbor_encode.h
+ */
+void
+QCBOREncode_AddTNegativeBignum(QCBOREncodeContext *pMe,
+ const uint8_t uTagRequirement,
+ const UsefulBufC BigNum)
+{
+ uint64_t uInt;
+ bool bIs2exp64;
+ static const uint8_t twoExp64[] = {0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+ if(UsefulBuf_IsValue(BigNum, 0) == SIZE_MAX) {
+ pMe->uError = QCBOR_ERR_NO_NEGATIVE_ZERO;
+ return;
+ }
+
+ if(pMe->uConfig & QCBOR_ENCODE_CONFIG_V1_COMPAT) {
+ QCBOREncode_AddTNegativeBignumNoPreferred(pMe, uTagRequirement, BigNum);
+
+ } else {
+ /* Here we do preferred serialization. That requires removal of leading zeros */
+ const UsefulBufC BigNumNLZ = QCBOREncode_Private_RemoveLeadingZeros(BigNum);
+
+ bIs2exp64 = ! UsefulBuf_Compare(BigNumNLZ, UsefulBuf_FROM_BYTE_ARRAY_LITERAL(twoExp64));
+
+ if(BigNumNLZ.len <= 8 || bIs2exp64) {
+ /* Must convert to CBOR type 1, a negative integer */
+ if(bIs2exp64) {
+ /* 2^64 is a 9 byte big number. Since negative numbers are offset
+ * by one in CBOR, it can be encoded as a type 1 negative. The
+ * conversion below won't work because the uInt will overflow
+ * before the subtraction of 1.
+ */
+ uInt = UINT64_MAX;
+ } else {
+ uInt = QCBOREncode_Private_BigNumToUInt(BigNumNLZ);
+ uInt--; /* CBOR's negative offset of 1 */
+ }
+ QCBOREncode_AddNegativeUInt64(pMe, uInt);
+
+ } else {
+ QCBOREncode_Private_AddTNegativeBignum(pMe, uTagRequirement, BigNumNLZ);
+ }
+ }
+}
+
+
#ifndef QCBOR_DISABLE_EXP_AND_MANTISSA
/**
* @brief Semi-private method to add bigfloats and decimal fractions.
@@ -798,10 +1093,10 @@
void
QCBOREncode_Private_AddExpMantissa(QCBOREncodeContext *pMe,
const uint64_t uTag,
+ const int64_t nExponent,
const UsefulBufC BigNumMantissa,
const bool bBigNumIsNegative,
- const int64_t nMantissa,
- const int64_t nExponent)
+ const int64_t nMantissa)
{
/* This is for encoding either a big float or a decimal fraction,
* both of which are an array of two items, an exponent and a