Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for RFC 8746 arrays #111

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 81 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,34 +201,94 @@ unlimited.

Some of the error codes have changed.

## Floating Point Support
## Floating Point Support & Configuration

By default, all QCBOR floating-point features are enabled. This includes
encoding and decoding of half-precision, CBOR preferred serialization
for floating-point and floating-point epoch dates.

If full floating-point is not needed the following #defines can be
used to reduce object code size.
used to reduce object code size and dependency.

See discussion in qcbor_encode.h for other details.

### QCBOR_DISABLE_FLOAT_HW_USE

This removes dependency on:

* floating-point hardware and floating-point instructions
* <math.h> and <fenv.h>
* the math library (libm, -lm)

For most limited environments, this removes enough
floating-point dependencies to be able to compile
and run QCBOR.

Note that this does not remove use of the types
double and float from QCBOR, but it limits QCBOR's
use of them to converting the encoded byte stream to them
and copying them. Converting and copying them
usually doesn't require any hardware, libraries
or includes. The C compiler takes care of it on its
own.

QCBOR uses it's own implementation of
half-precision float-pointing that doesn't depend on
math libraries. It uses masks and shifts instead. Thus
even with this define, half-precision encoding and
decoding works.

When this is defined, the QCBOR functionality lost
is minimal and only for decoding:

* Decoding floating-point format dates are not handled
* There is no conversion between floats and integers when decoding. For
example, QCBORDecode_GetUInt64ConvertAll() will be unable to convert
to and from float-point.
* Floats will not be converted to double when decoding.

No interfaces are disabled or removed with this define.
If input that requires floating-point conversion or functions
are called that request floating-point conversion, an error
code like QCBOR_ERR_HW_FLOAT_DISABLED will be
returned.

This saves only a small amount of object code. The primary
purpose for defining this is to remove dependency on
floating point hardware and libraries.

### QCBOR_DISABLE_PREFERRED_FLOAT

This eliminates support for half-precision
and CBOR preferred serialization by disabling
QCBOR's shift and mask based implementation of
half-precision floating-point.

With this defined, single and double-precision floating-point
numbers can still be encoded and decoded. Conversion
of floating-point to and from integers, big numbers and
such is also supported. Floating-point dates are still
supported.

The primary reason to define this is to save object code.
Roughly 900 bytes are saved, though about half of this
can be saved just by not calling any functions that
encode floating point numbers.

### Compiler options

Compilers support a number of options that control
which float-point related code is generated. For example,
it is usually possible to give options to the compiler to avoid all
floating-point hardware and instructions, to use software
and replacement libraries instead. These are usually
bigger and slower, but these options may still be useful
in getting QCBOR to run in some environments in
combination with QCBOR_DISABLE_FLOAT_HW_USE.
In particular, -mfloat-abi=soft, disables use of
hardware instructions for the float and double
types in C for some architectures.

QCBOR_DISABLE_FLOAT_HW_USE -- Avoid all use of floating-point hardware
and inclusion of <math.h> and <fenv.h>

QCBOR_DISABLE_PREFERRED_FLOAT -- Eliminates support for half-precision
and CBOR preferred serialization.

Even if these are #defined, QCBOR can still encode and decode basic
floating point numbers.

Defining QCBOR_DISABLE_PREFERRED_FLOAT will reduce object code size by
about 900 bytes, though 550 of these bytes can be avoided without the
#define by just not calling any of the functions that encode
floating-point numbers.

Defining QCBOR_DISABLE_FLOAT_HW_USE will save a small amount of object
code. Its main use is on CPUs that have no floating-point hardware.

See discussion in qcbor_encode.h for details.
G
## Comparison to TinyCBOR

TinyCBOR is a popular widely used implementation. Like QCBOR,
Expand Down
32 changes: 23 additions & 9 deletions inc/qcbor/UsefulBuf.h
Original file line number Diff line number Diff line change
Expand Up @@ -1580,6 +1580,18 @@ static inline size_t UsefulInputBuf_GetBufferLength(UsefulInputBuf *pUInBuf);
static void UsefulInputBuf_SetBufferLength(UsefulInputBuf *pUInBuf, size_t uNewLen);


// TODO: document this; maybe add tests...
#if defined(USEFULBUF_CONFIG_BSWAP)
#define USEFUL_SWAP32(integer) \
__builtin_bswap32(integer);
#else
#define USEFUL_SWAP32(integer) \
((integer & 0xff) >> 24) + \
((integer & 0xff00) >> 16) + \
((integer & 0xff0000) >> 8) + \
(integer & 0xff000000)
#endif



/*----------------------------------------------------------
Expand Down Expand Up @@ -1789,6 +1801,7 @@ static inline void UsefulOutBuf_InsertByte(UsefulOutBuf *me,
}



static inline void UsefulOutBuf_InsertUint16(UsefulOutBuf *me,
uint16_t uInteger16,
size_t uPos)
Expand Down Expand Up @@ -1836,20 +1849,21 @@ static inline void UsefulOutBuf_InsertUint32(UsefulOutBuf *pMe,
uint32_t uTmp = htonl(uInteger32);
pBytes = &uTmp;

#elif defined(USEFULBUF_CONFIG_LITTLE_ENDIAN) && defined(USEFULBUF_CONFIG_BSWAP)
uint32_t uTmp = __builtin_bswap32(uInteger32);

#elif defined(USEFULBUF_CONFIG_LITTLE_ENDIAN)
uint32_t uTmp = XSWAP(uInteger32);
pBytes = &uTmp;

#else
uint8_t aTmp[4];

aTmp[0] = (uint8_t)((uInteger32 & 0xff000000) >> 24);
aTmp[1] = (uint8_t)((uInteger32 & 0xff0000) >> 16);
aTmp[2] = (uint8_t)((uInteger32 & 0xff00) >> 8);
aTmp[3] = (uint8_t)(uInteger32 & 0xff);
uint32_t uTmp =
((uInteger32 & 0xff) >> 24) + \
((uInteger32 & 0xff00) >> 16) + \
((uInteger32 & 0xff0000) >> 8) + \
(uInteger32 & 0xff000000);

pBytes = &uTmp;


pBytes = aTmp;
#endif

UsefulOutBuf_InsertData(pMe, pBytes, 4, uPos);
Expand Down
21 changes: 21 additions & 0 deletions inc/qcbor/qcbor_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,9 @@ IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#define CBOR_TAG_MIME 36
/** See QCBOREncode_AddBinaryUUID(). */
#define CBOR_TAG_BIN_UUID 37
/** An array where all elements are of the same type. See
[RFC8746](https://tools.ietf.org/html/rfc8932) */
#define CBOR_TAG_HOMOGENEOUS_ARRAY 41
/** The data is a CBOR Web Token per [RFC 8392]
(https://tools.ietf.org/html/rfc8932). No API is provided for this
tag. */
Expand All @@ -194,6 +197,16 @@ IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
tag. */
#define CBOR_TAG_CBOR_SEQUENCE 63

#define CBOR_TAG_UINT8_ARRAY 64

#define CBOR_TAG_UINT16_BIG_ENDIAN_ARRAY 65
#define CBOR_TAG_UINT32_BIG_ENDIAN_ARRAY 66
#define CBOR_TAG_UINT64_BIG_ENDIAN_ARRAY 67

#define CBOR_TAG_UINT16_LITTLE_ENDIAN_ARRAY 69
#define CBOR_TAG_UINT32_LITTLE_ENDIAN_ARRAY 70
#define CBOR_TAG_UINT64_LITTLE_ENDIAN_ARRAY 71


#define CBOR_TAG_ENCRYPT 96
/** Not Decoded by QCBOR. Tag for COSE format MAC. See [RFC 8152, COSE]
Expand Down Expand Up @@ -502,6 +515,14 @@ typedef enum {
indefinite length map or array in the input CBOR. */
QCBOR_ERR_INDEF_LEN_ARRAYS_DISABLED = 44,

/** The input CBOR needs to be a multiple of 2, 4 or 8 bytes and it is not. */
QCBOR_ERR_INPUT_SIZE_MULTIPLE = 45,

/** The input CBOR is not a tag indicating endianness and the input endianness
needs to be know to proceed. */
QCBOR_ERR_INPUT_ENDIANNESS_UNKNOWN = 45,


/* This is stored in uint8_t; never add values > 255 */
} QCBORError;

Expand Down
115 changes: 109 additions & 6 deletions inc/qcbor/qcbor_encode.h
Original file line number Diff line number Diff line change
Expand Up @@ -503,9 +503,9 @@ void QCBOREncode_Init(QCBOREncodeContext *pCtx, UsefulBuf Storage);
*/
void QCBOREncode_AddInt64(QCBOREncodeContext *pCtx, int64_t nNum);

static void QCBOREncode_AddInt64ToMap(QCBOREncodeContext *pCtx, const char *szLabel, int64_t uNum);
static void QCBOREncode_AddInt64ToMap(QCBOREncodeContext *pCtx, const char *szLabel, int64_t nNum);

static void QCBOREncode_AddInt64ToMapN(QCBOREncodeContext *pCtx, int64_t nLabel, int64_t uNum);
static void QCBOREncode_AddInt64ToMapN(QCBOREncodeContext *pCtx, int64_t nLabel, int64_t nNum);


/**
Expand Down Expand Up @@ -1959,6 +1959,109 @@ UsefulBufC QCBOREncode_EncodeHead(UsefulBuf buffer,




/**
@brief Encode a "homogeneous" array of signed integers.

@param[in] pCtx The encoding context.
@param[in] uTagRequirement Either @ref QCBOR_ENCODE_AS_TAG or
@ref QCBOR_ENCODE_AS_BORROWED.
@param[in] pnNum The array of integers to output.
@param[in] uNumInts The number of integers in \c pnNum.

This encodes a homogeneous array of signed integers as described in section 3.2 in [RFC 8746](https://tools.ietf.org/html/rfc8746).

To encode the actual integers, this just opens an array, adds all the integers
and closes the array as one might expect. The only thing unusual is the
encoding of this as a tag.

If \c uTagRequirement is @ref QCBOR_ENCODE_AS_TAG then
this is encoded as the homogenous array tag, which means the array
is preceded by the tag number \ref CBOR_TAG_HOMOGENEOUS_ARRAY.

If \c uTagRequirement is @ref QCBOR_ENCODE_AS_BORROWED then
there is no tag number and this is the straight forward encoding of an
array of integers. This is "borrowed" because the definition of a homogenous
array is as a tag.

This uses QCBOREncode_AddInt64() to encode the integers so
preferred encoding in the shortest form is used and this will encode
with either major type 0 or 1 depending on whether each integer is
positive or negative.
*/
void
QCBOREncode_AddArrayOfInt64s(QCBOREncodeContext *pCtx,
uint8_t uTagRequirement,
const int64_t pnNum[],
size_t uNumInts);


void
QCBOREncode_AddArrayOfUInt64s(QCBOREncodeContext *pMe,
uint8_t uTagRequirement,
const uint64_t puInts[],
size_t uNumUInts);


void
QCBOREncode_AddArrayOfDoubles(QCBOREncodeContext *pMe,
uint8_t uTagRequirement,
const double pdDoubles[],
size_t uNumDoubles);


void
QCBOREncode_AddArrayOfByteStrings(QCBOREncodeContext *pMe,
uint8_t uTagRequirement,
const UsefulBufC pStrings[],
size_t uNumStrings);


void
QCBOREncode_AddArrayOfTextStrings(QCBOREncodeContext *pMe,
uint8_t uTagRequirement,
const UsefulBufC pStrings[],
size_t uNumStrings);


void
QCBOREncode_AddArrayOfSZStrings(QCBOREncodeContext *pMe,
uint8_t uTagRequirement,
const char *pStrings[],
size_t uNumStrings);

/*
Definitely need to have two functions, one for BE and one for LE.
Since we have to say what format to output. There is no wire format.

Do we want a third that just outputs without swapping? It would
be called when the caller doesn't want to figure out which
endianness they have. They want the minimal processing.
The implementation will have to figure out which though to
use correct tag.


There are going to be about 20 functions here for
all the different integer types.


*/




void QCBOREncode_AddTypedArrayOfUInt32BigEndian(QCBOREncodeContext *pCtx,
const uint32_t array[],
size_t uArrayLen);

void QCBOREncode_AddUint32ArrayLittleEndian(QCBOREncodeContext *pCtx,
const uint32_t array[],
size_t uArrayLen);





/* =========================================================================
BEGINNING OF PRIVATE INLINE IMPLEMENTATION
========================================================================= */
Expand Down Expand Up @@ -2123,18 +2226,18 @@ static inline void QCBOREncode_AddBytesLenOnlyToMapN(QCBOREncodeContext *pCtx, i


static inline void
QCBOREncode_AddInt64ToMap(QCBOREncodeContext *pMe, const char *szLabel, int64_t uNum)
QCBOREncode_AddInt64ToMap(QCBOREncodeContext *pMe, const char *szLabel, int64_t nNum)
{
// Use _AddBuffer() because _AddSZString() is defined below, not above
QCBOREncode_AddBuffer(pMe, CBOR_MAJOR_TYPE_TEXT_STRING, UsefulBuf_FromSZ(szLabel));
QCBOREncode_AddInt64(pMe, uNum);
QCBOREncode_AddInt64(pMe, nNum);
}

static inline void
QCBOREncode_AddInt64ToMapN(QCBOREncodeContext *pMe, int64_t nLabel, int64_t uNum)
QCBOREncode_AddInt64ToMapN(QCBOREncodeContext *pMe, int64_t nLabel, int64_t nNum)
{
QCBOREncode_AddInt64(pMe, nLabel);
QCBOREncode_AddInt64(pMe, uNum);
QCBOREncode_AddInt64(pMe, nNum);
}


Expand Down
Loading