blob: e23c2d35913d79afa9bbd2190522f2b0b25a39a0 [file] [log] [blame]
/* ===========================================================================
* Copyright (c) 2016-2018, The Linux Foundation.
* Copyright (c) 2018-2024, Laurence Lundblade.
* Copyright (c) 2021, Arm Limited.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
* * Neither the name of The Linux Foundation nor the names of its
* contributors, nor the name "Laurence Lundblade" may be used to
* endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
* IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
* ========================================================================= */
#include "qcbor/qcbor_number_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_number_encode.c
*
*/
/*
* Public function for adding signed integers. See qcbor/qcbor_encode.h
*/
void
QCBOREncode_AddInt64(QCBOREncodeContext *pMe, const int64_t nNum)
{
uint8_t uMajorType;
uint64_t uValue;
if(nNum < 0) {
/* In CBOR -1 encodes as 0x00 with major type negative int.
* First add one as a signed integer because that will not
* overflow. Then change the sign as needed for encoding (the
* opposite order, changing the sign and subtracting, can cause
* an overflow when encoding INT64_MIN). */
int64_t nTmp = nNum + 1;
uValue = (uint64_t)-nTmp;
uMajorType = CBOR_MAJOR_TYPE_NEGATIVE_INT;
} else {
uValue = (uint64_t)nNum;
uMajorType = CBOR_MAJOR_TYPE_POSITIVE_INT;
}
QCBOREncode_Private_AppendCBORHead(pMe, uMajorType, uValue, 0);
}
#ifndef QCBOR_DISABLE_PREFERRED_FLOAT
/**
* @brief Semi-private method to add a double using preferred encoding.
*
* @param[in] pMe The encode context.
* @param[in] dNum The double to add.
*
* This converts the double to a float or half-precision if it can be done
* without a loss of precision. See QCBOREncode_AddDouble().
*/
void
QCBOREncode_Private_AddPreferredDouble(QCBOREncodeContext *pMe, double dNum)
{
IEEE754_union FloatResult;
bool bNoNaNPayload;
struct IEEE754_ToInt IntResult;
uint64_t uNegValue;
#ifndef QCBOR_DISABLE_ENCODE_USAGE_GUARDS
if(IEEE754_DoubleHasNaNPayload(dNum) && !(pMe->uConfigFlags & QCBOR_ENCODE_CONFIG_ALLOW_NAN_PAYLOAD)) {
pMe->uError = QCBOR_ERR_NOT_ALLOWED;
return;
}
#endif /* ! QCBOR_DISABLE_ENCODE_USAGE_GUARDS */
if(pMe->uConfigFlags & QCBOR_ENCODE_CONFIG_FLOAT_REDUCTION) {
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_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;
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);
}
/**
* @brief Semi-private method to add a float using preferred encoding.
*
* @param[in] pMe The encode context.
* @param[in] fNum The float to add.
*
* This converts the float to a half-precision if it can be done
* without a loss of precision. See QCBOREncode_AddFloat().
*/
void
QCBOREncode_Private_AddPreferredFloat(QCBOREncodeContext *pMe, float fNum)
{
IEEE754_union FloatResult;
bool bNoNaNPayload;
struct IEEE754_ToInt IntResult;
uint64_t uNegValue;
#ifndef QCBOR_DISABLE_ENCODE_USAGE_GUARDS
if(IEEE754_SingleHasNaNPayload(fNum) && !(pMe->uConfigFlags & QCBOR_ENCODE_CONFIG_ALLOW_NAN_PAYLOAD)) {
pMe->uError = QCBOR_ERR_NOT_ALLOWED;
return;
}
#endif /* ! QCBOR_DISABLE_ENCODE_USAGE_GUARDS */
if(pMe->uConfigFlags & QCBOR_ENCODE_CONFIG_FLOAT_REDUCTION) {
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_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;
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);
}
#endif /* ! QCBOR_DISABLE_PREFERRED_FLOAT */
/**
* @brief Convert a big number to unsigned integer.
*
* @param[in] BigNumber Big number to convert.
*
* @return Converted unsigned.
*
* The big number must be less than 8 bytes long.
**/
static uint64_t
QCBOREncode_Private_BigNumberToUInt(const UsefulBufC BigNumber)
{
uint64_t uInt;
size_t uIndex;
uInt = 0;
for(uIndex = 0; uIndex < BigNumber.len; uIndex++) {
uInt = (uInt << 8) + UsefulBufC_NTH_BYTE(BigNumber, uIndex);
}
return uInt;
}
/**
* @brief Is there a carry when you subtract 1 from the BigNumber.
*
* @param[in] BigNumber Big number to check for carry.
*
* @return If there is a carry, \c true.
*
* If this returns @c true, then @c BigNumber - 1 is one byte shorter
* than @c BigNumber.
**/
static bool
QCBOREncode_Private_BigNumberCarry(const UsefulBufC BigNumber)
{
bool bCarry;
UsefulBufC SubBigNum;
// Improvement: rework without recursion?
if(BigNumber.len == 0) {
return true; /* Subtracting one from zero-length string gives a carry */
} else {
SubBigNum = UsefulBuf_Tail(BigNumber, 1);
bCarry = QCBOREncode_Private_BigNumberCarry(SubBigNum);
if(UsefulBufC_NTH_BYTE(BigNumber, 0) == 0x00 && bCarry) {
/* Subtracting one from 0 gives a carry */
return true;
} else {
return false;
}
}
}
/**
* @brief Output negative bignum bytes with subtraction of 1.
*
* @param[in] pMe The decode context.
* @param[in] uTagRequirement Either @ref QCBOR_ENCODE_AS_TAG or
* @ref QCBOR_ENCODE_AS_BORROWED.
* @param[in] BigNumber The negative big number.
*/
static void
QCBOREncode_Private_AddTNegativeBigNumber(QCBOREncodeContext *pMe,
const uint8_t uTagRequirement,
const UsefulBufC BigNumber)
{
size_t uLen;
bool bCarry;
bool bCopiedSomething;
uint8_t uByte;
UsefulBufC SubString;
UsefulBufC NextSubString;
QCBOREncode_Private_BigNumberTag(pMe, uTagRequirement, true);
/* 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
*
* This outputs the big number a byte at a time to be able to operate on
* a big number of any length without memory allocation.
*/
/* Compute the length up front because it goes in the encoded head */
bCarry = QCBOREncode_Private_BigNumberCarry(UsefulBuf_Tail(BigNumber, 1));
uLen = BigNumber.len;
if(bCarry && BigNumber.len > 1 && UsefulBufC_NTH_BYTE(BigNumber, 0) >= 1) {
uLen--;
}
QCBOREncode_Private_AppendCBORHead(pMe, CBOR_MAJOR_TYPE_BYTE_STRING, uLen,0);
SubString = BigNumber;
bCopiedSomething = false;
while(SubString.len) {
uByte = UsefulBufC_NTH_BYTE(SubString, 0);
NextSubString = UsefulBuf_Tail(SubString, 1);
bCarry = QCBOREncode_Private_BigNumberCarry(NextSubString);
if(bCarry) {
uByte--;
}
/* This avoids all but the last leading zero. See
* QCBOREncode_Private_SkipLeadingZeros() */
if(bCopiedSomething || NextSubString.len == 0 || uByte != 0) {
UsefulOutBuf_AppendByte(&(pMe->OutBuf), uByte);
bCopiedSomething = true;
}
SubString = NextSubString;
}
}
/**
* @brief Remove leading zeros.
*
* @param[in] BigNumber The big number.
*
* @return Big number with no leading zeros.
*
* If the big number is all zeros, this returns a big number that is
* one zero rather than the empty string.
*
* RFC 8949 3.4.3 does not explicitly decoders MUST handle the empty
* string, but does say decoders MUST handle leading zeros. So
* Postel's Law is applied here and 0 is not encoded as an empty
* string.
*/
static UsefulBufC
QCBOREncode_Private_SkipLeadingZeros(const UsefulBufC BigNumber)
{
UsefulBufC NLZ;
NLZ = UsefulBuf_SkipLeading(BigNumber, 0x00);
/* An all-zero string reduces to one 0, not an empty string. */
if(NLZ.len == 0 &&
BigNumber.len > 0 &&
UsefulBufC_NTH_BYTE(BigNumber, 0) == 0x00) {
NLZ.len = 1;
}
return NLZ;
}
/**
* @brief Output a big number, preferred or not, with negative offset
*
* @param[in] pMe The decode context.
* @param[in] uTagRequirement Either @ref QCBOR_ENCODE_AS_TAG or
* @ref QCBOR_ENCODE_AS_BORROWED.
* @param[in] bPreferred Uses preferred serialization if true
* @param[in] bNegative Indicates big number is negative or postive.
* @param[in] BigNumber The big number.
*
* Regardless of whether preferred serialization is used, if the big
* number is negative, one is subtracted before is output per CBOR
* convetion for big numbers. This requires a little big number
* arithmetic and adds some object code.
*
* If preferred serialization is used, then if the number is smaller
* than UINT64_MAX and postive it is output as type 0 and if it is
* equal to or smaller than UINT64_MAX it is output as a type 1
* integer minus one.
*
* See QCBOREncode_AddTBigNumberRaw() for simple copy through.
*/
void
QCBOREncode_Private_AddTBigNumberMain(QCBOREncodeContext *pMe,
const uint8_t uTagRequirement,
const bool bPreferred,
const bool bNegative,
const UsefulBufC BigNumber)
{
uint64_t uInt;
bool bIs2exp64;
uint8_t uMajorType;
UsefulBufC BigNumberNLZ;
#ifndef QCBOR_DISABLE_ENCODE_USAGE_GUARDS
if(!bPreferred && pMe->uConfigFlags & QCBOR_ENCODE_CONFIG_ONLY_PREFERRED_BIG_NUMBERS) {
pMe->uError = QCBOR_ERR_NOT_PREFERRED;
return;
}
#endif /* ! QCBOR_DISABLE_ENCODE_USAGE_GUARDS */
BigNumberNLZ = QCBOREncode_Private_SkipLeadingZeros(BigNumber);
static const uint8_t twoExp64[] = {0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
bIs2exp64 = ! UsefulBuf_Compare(BigNumber, UsefulBuf_FROM_BYTE_ARRAY_LITERAL(twoExp64));
if(bPreferred && (BigNumberNLZ.len <= 8 || (bNegative && bIs2exp64))) {
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_BigNumberToUInt(BigNumberNLZ);
if(bNegative) {
uInt--;
}
}
uMajorType = bNegative ? CBOR_MAJOR_TYPE_NEGATIVE_INT :
CBOR_MAJOR_TYPE_POSITIVE_INT;
QCBOREncode_Private_AppendCBORHead(pMe, uMajorType, uInt, 0);
} else {
if(bNegative) {
QCBOREncode_Private_AddTNegativeBigNumber(pMe, uTagRequirement, BigNumberNLZ);
} else {
QCBOREncode_AddTBigNumberRaw(pMe, false, uTagRequirement,BigNumberNLZ);
}
}
}
#ifndef QCBOR_DISABLE_EXP_AND_MANTISSA
/**
* @brief Semi-private method to add bigfloats and decimal fractions.
*
* @param[in] pMe The encoding context to add the value to.
* @param[in] uTagNumber The type 6 tag indicating what this is to be.
* @param[in] nMantissa The @c int64_t mantissa if it is not a big number.
* @param[in] nExponent The exponent.
*
* This outputs either the @ref CBOR_TAG_DECIMAL_FRACTION or
* @ref CBOR_TAG_BIGFLOAT tag. if @c uTag is @ref CBOR_TAG_INVALID64,
* then this outputs the "borrowed" content format.
*
* The tag content output by this is an array with two members, the
* exponent and then the mantissa. The mantissa can be either a big
* number or an @c int64_t.
*
* This implementation cannot output an exponent further from 0 than
* @c INT64_MAX.
*
* To output a mantissa that is between INT64_MAX and UINT64_MAX from 0,
* it must be as a big number.
*
* Typically, QCBOREncode_AddTDecimalFraction(), QCBOREncode_AddTBigFloat(),
* QCBOREncode_AddTDecimalFractionBigNum() or QCBOREncode_AddTBigFloatBigNum()
* is called instead of this.
*/
void
QCBOREncode_Private_AddTExpIntMantissa(QCBOREncodeContext *pMe,
const int uTagRequirement,
const uint64_t uTagNumber,
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
* mantissa. The difference between the two is that the exponent
* is base-2 for big floats and base-10 for decimal fractions, but
* that has no effect on the code here.
*/
/* Separate from QCBOREncode_Private_AddTExpBigMantissa() because
* linking QCBOREncode_AddTBigNumber() adds a lot because it
* does preferred serialization of big numbers and the offset of 1
* for CBOR negative numbers.
*/
if(uTagRequirement == QCBOR_ENCODE_AS_TAG) {
QCBOREncode_AddTagNumber(pMe, uTagNumber);
}
QCBOREncode_OpenArray(pMe);
QCBOREncode_AddInt64(pMe, nExponent);
QCBOREncode_AddInt64(pMe, nMantissa);
QCBOREncode_CloseArray(pMe);
}
void
QCBOREncode_Private_AddTExpBigMantissa(QCBOREncodeContext *pMe,
const int uTagRequirement,
const uint64_t uTagNumber,
const int64_t nExponent,
const UsefulBufC BigNumMantissa,
const bool bBigNumIsNegative)
{
/* 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
* mantissa. The difference between the two is that the exponent
* is base-2 for big floats and base-10 for decimal fractions, but
* that has no effect on the code here.
*/
if(uTagRequirement == QCBOR_ENCODE_AS_TAG) {
QCBOREncode_AddTagNumber(pMe, uTagNumber);
}
QCBOREncode_OpenArray(pMe);
QCBOREncode_AddInt64(pMe, nExponent);
QCBOREncode_AddTBigNumber(pMe, QCBOR_ENCODE_AS_TAG, bBigNumIsNegative, BigNumMantissa);
QCBOREncode_CloseArray(pMe);
}
void
QCBOREncode_Private_AddTExpBigMantissaRaw(QCBOREncodeContext *pMe,
const int uTagRequirement,
const uint64_t uTagNumber,
const int64_t nExponent,
const UsefulBufC BigNumMantissa,
const bool bBigNumIsNegative)
{
/* 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
* mantissa. The difference between the two is that the exponent
* is base-2 for big floats and base-10 for decimal fractions, but
* that has no effect on the code here.
*/
/* Separate from QCBOREncode_Private_AddTExpBigMantissa() because
* linking QCBOREncode_AddTBigNumber() adds a lot because it
* does preferred serialization of big numbers and the offset of 1
* for CBOR negative numbers.
*/
if(uTagRequirement == QCBOR_ENCODE_AS_TAG) {
QCBOREncode_AddTagNumber(pMe, uTagNumber);
}
QCBOREncode_OpenArray(pMe);
QCBOREncode_AddInt64(pMe, nExponent);
QCBOREncode_AddTBigNumberRaw(pMe, QCBOR_ENCODE_AS_TAG, bBigNumIsNegative, BigNumMantissa);
QCBOREncode_CloseArray(pMe);
}
#endif /* ! QCBOR_DISABLE_EXP_AND_MANTISSA */