385 lines
12 KiB
C
385 lines
12 KiB
C
/* =========================================================================
|
|
example.c -- Example code for QCBOR
|
|
|
|
Copyright (c) 2020-2021, Laurence Lundblade. All rights reserved.
|
|
Copyright (c) 2021, Arm Limited. All rights reserved.
|
|
|
|
SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
See BSD-3-Clause license in README.md
|
|
|
|
Created on 6/30/2020
|
|
========================================================================== */
|
|
|
|
#include <stdio.h>
|
|
#include "example.h"
|
|
#include "qcbor/qcbor_encode.h"
|
|
#include "qcbor/qcbor_decode.h"
|
|
#include "qcbor/qcbor_spiffy_decode.h"
|
|
|
|
|
|
/**
|
|
* This is a simple example of encoding and decoding some CBOR from
|
|
* and to a C structure.
|
|
*
|
|
* This also includes a comparison between the original structure
|
|
* and the one decoded from the CBOR to confirm correctness.
|
|
*/
|
|
|
|
|
|
#define MAX_CYLINDERS 16
|
|
|
|
/**
|
|
* The data structure representing a car engine that is encoded and
|
|
* decoded in this example.
|
|
*/
|
|
typedef struct
|
|
{
|
|
UsefulBufC Manufacturer;
|
|
int64_t uDisplacement;
|
|
int64_t uHorsePower;
|
|
#ifndef USEFULBUF_DISABLE_ALL_FLOAT
|
|
double dDesignedCompresion;
|
|
#endif /* USEFULBUF_DISABLE_ALL_FLOAT */
|
|
int64_t uNumCylinders;
|
|
bool bTurboCharged;
|
|
#ifndef USEFULBUF_DISABLE_ALL_FLOAT
|
|
struct {
|
|
double dMeasuredCompression;
|
|
} cylinders[MAX_CYLINDERS];
|
|
#endif /* USEFULBUF_DISABLE_ALL_FLOAT */
|
|
} CarEngine;
|
|
|
|
|
|
/**
|
|
* @brief Initialize the Engine data structure with values to encode.
|
|
*
|
|
* @param[out] pE The Engine structure to fill in
|
|
*/
|
|
void EngineInit(CarEngine *pE)
|
|
{
|
|
pE->Manufacturer = UsefulBuf_FROM_SZ_LITERAL("Porsche");
|
|
pE->uDisplacement = 3296;
|
|
pE->uHorsePower = 210;
|
|
#ifndef USEFULBUF_DISABLE_ALL_FLOAT
|
|
pE->dDesignedCompresion = 9.1;
|
|
#endif /* USEFULBUF_DISABLE_ALL_FLOAT */
|
|
pE->uNumCylinders = 6;
|
|
pE->bTurboCharged = false;
|
|
|
|
#ifndef USEFULBUF_DISABLE_ALL_FLOAT
|
|
pE->cylinders[0].dMeasuredCompression = 9.0;
|
|
pE->cylinders[1].dMeasuredCompression = 9.2;
|
|
pE->cylinders[2].dMeasuredCompression = 8.9;
|
|
pE->cylinders[3].dMeasuredCompression = 8.9;
|
|
pE->cylinders[4].dMeasuredCompression = 9.1;
|
|
pE->cylinders[5].dMeasuredCompression = 9.0;
|
|
#endif /* USEFULBUF_DISABLE_ALL_FLOAT */
|
|
}
|
|
|
|
|
|
/**
|
|
* @brief Compare two Engine structure for equality.
|
|
*
|
|
* @param[in] pE1 First Engine to compare.
|
|
* @param[in] pE2 Second Engine to compare.
|
|
*
|
|
* @retval Return @c true if the two Engine data structures are exactly the
|
|
* same.
|
|
*/
|
|
static bool EngineCompare(const CarEngine *pE1, const CarEngine *pE2)
|
|
{
|
|
if(pE1->uNumCylinders != pE2->uNumCylinders) {
|
|
return false;
|
|
}
|
|
if(pE1->bTurboCharged != pE2->bTurboCharged) {
|
|
return false;
|
|
}
|
|
if(pE1->uDisplacement != pE2->uDisplacement) {
|
|
return false;
|
|
}
|
|
if(pE1->uHorsePower != pE2->uHorsePower) {
|
|
return false;
|
|
}
|
|
#ifndef USEFULBUF_DISABLE_ALL_FLOAT
|
|
if(pE1->dDesignedCompresion != pE2->dDesignedCompresion) {
|
|
return false;
|
|
}
|
|
for(int64_t i = 0; i < pE2->uNumCylinders; i++) {
|
|
if(pE1->cylinders[i].dMeasuredCompression !=
|
|
pE2->cylinders[i].dMeasuredCompression) {
|
|
return false;
|
|
}
|
|
}
|
|
#endif /* USEFULBUF_DISABLE_ALL_FLOAT */
|
|
|
|
if(UsefulBuf_Compare(pE1->Manufacturer, pE2->Manufacturer)) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/**
|
|
* @brief Encode an initialized CarEngine data structure in CBOR.
|
|
*
|
|
* @param[in] pEngine The data structure to encode.
|
|
* @param[in] Buffer Pointer and length of buffer to output to.
|
|
*
|
|
* @return The pointer and length of the encoded CBOR or
|
|
* @ref NULLUsefulBufC on error.
|
|
*
|
|
* This encodes the input structure \c pEngine as a CBOR map of
|
|
* label-value pairs. An array of float is one of the items in the
|
|
* map.
|
|
*
|
|
* This uses the UsefulBuf convention of passing in a non-const empty
|
|
* buffer to be filled in and returning a filled in const buffer. The
|
|
* buffer to write into is given as a pointer and length in a
|
|
* UsefulBuf. The buffer returned with the encoded CBOR is a
|
|
* UsefulBufC also a pointer and length. In this implementation the
|
|
* pointer to the returned data is exactly the same as that of the
|
|
* empty buffer. The returned length will be smaller than or equal to
|
|
* that of the empty buffer. This gives correct const-ness for the
|
|
* buffer passed in and the data returned.
|
|
*
|
|
* @c Buffer must be big enough to hold the output. If it is not @ref
|
|
* NULLUsefulBufC will be returned. @ref NULLUsefulBufC will be
|
|
* returned for any other encoding errors.
|
|
*
|
|
* This can be called with @c Buffer set to @ref SizeCalculateUsefulBuf
|
|
* in which case the size of the encoded engine will be calculated,
|
|
* but no actual encoded CBOR will be output. The calculated size is
|
|
* in @c .len of the returned @ref UsefulBufC.
|
|
*/
|
|
UsefulBufC EncodeEngine(const CarEngine *pEngine, UsefulBuf Buffer)
|
|
{
|
|
/* Set up the encoding context with the output buffer */
|
|
QCBOREncodeContext EncodeCtx;
|
|
QCBOREncode_Init(&EncodeCtx, Buffer);
|
|
|
|
/* Proceed to output all the items, letting the internal error
|
|
* tracking do its work */
|
|
QCBOREncode_OpenMap(&EncodeCtx);
|
|
QCBOREncode_AddTextToMap(&EncodeCtx, "Manufacturer", pEngine->Manufacturer);
|
|
QCBOREncode_AddInt64ToMap(&EncodeCtx, "NumCylinders", pEngine->uNumCylinders);
|
|
QCBOREncode_AddInt64ToMap(&EncodeCtx, "Displacement", pEngine->uDisplacement);
|
|
QCBOREncode_AddInt64ToMap(&EncodeCtx, "Horsepower", pEngine->uHorsePower);
|
|
#ifndef USEFULBUF_DISABLE_ALL_FLOAT
|
|
QCBOREncode_AddDoubleToMap(&EncodeCtx, "DesignedCompression", pEngine->dDesignedCompresion);
|
|
#endif /* USEFULBUF_DISABLE_ALL_FLOAT */
|
|
QCBOREncode_OpenArrayInMap(&EncodeCtx, "Cylinders");
|
|
#ifndef USEFULBUF_DISABLE_ALL_FLOAT
|
|
for(int64_t i = 0 ; i < pEngine->uNumCylinders; i++) {
|
|
QCBOREncode_AddDouble(&EncodeCtx,
|
|
pEngine->cylinders[i].dMeasuredCompression);
|
|
}
|
|
#endif /* USEFULBUF_DISABLE_ALL_FLOAT */
|
|
QCBOREncode_CloseArray(&EncodeCtx);
|
|
QCBOREncode_AddBoolToMap(&EncodeCtx, "Turbo", pEngine->bTurboCharged);
|
|
QCBOREncode_CloseMap(&EncodeCtx);
|
|
|
|
/* Get the pointer and length of the encoded output. If there was
|
|
* any encoding error, it will be returned here */
|
|
UsefulBufC EncodedCBOR;
|
|
QCBORError uErr;
|
|
uErr = QCBOREncode_Finish(&EncodeCtx, &EncodedCBOR);
|
|
if(uErr != QCBOR_SUCCESS) {
|
|
return NULLUsefulBufC;
|
|
} else {
|
|
return EncodedCBOR;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Error results when decoding an Engine data structure.
|
|
*/
|
|
typedef enum {
|
|
EngineSuccess,
|
|
CBORNotWellFormed,
|
|
TooManyCylinders,
|
|
EngineProtocolerror,
|
|
WrongNumberOfCylinders
|
|
} EngineDecodeErrors;
|
|
|
|
|
|
/**
|
|
* Convert @ref QCBORError to @ref EngineDecodeErrors.
|
|
*/
|
|
static EngineDecodeErrors ConvertError(QCBORError uErr)
|
|
{
|
|
EngineDecodeErrors uReturn;
|
|
|
|
switch(uErr)
|
|
{
|
|
case QCBOR_SUCCESS:
|
|
uReturn = EngineSuccess;
|
|
break;
|
|
|
|
case QCBOR_ERR_HIT_END:
|
|
uReturn = CBORNotWellFormed;
|
|
break;
|
|
|
|
default:
|
|
uReturn = EngineProtocolerror;
|
|
break;
|
|
}
|
|
|
|
return uReturn;
|
|
}
|
|
|
|
|
|
/**
|
|
* @brief Simplest engine decode using spiffy decode features.
|
|
*
|
|
* @param[in] EncodedEngine Pointer and length of CBOR-encoded engine.
|
|
* @param[out] pE The structure filled in from the decoding.
|
|
*
|
|
* @return The decode error or success.
|
|
*
|
|
* This decodes the CBOR into the engine structure.
|
|
*
|
|
* As QCBOR automatically supports both definite and indefinite maps
|
|
* and arrays, this will decode either.
|
|
*
|
|
* This uses QCBOR's spiffy decode functions, so the implementation is
|
|
* simple and closely parallels the encode implementation in
|
|
* EncodeEngineDefiniteLength().
|
|
*
|
|
* Another way to decode without using spiffy decode functions is to
|
|
* use QCBORDecode_GetNext() to traverse the whole tree. This
|
|
* requires a more complex implementation, but is faster and will pull
|
|
* in less code from the CBOR library. The speed advantage is likely
|
|
* of consequence when decoding much much larger CBOR on slow small
|
|
* CPUs.
|
|
*
|
|
* A middle way is to use the spiffy decode
|
|
* QCBORDecode_GetItemsInMap(). The implementation has middle
|
|
* complexity and uses less CPU.
|
|
*/
|
|
EngineDecodeErrors DecodeEngineSpiffy(UsefulBufC EncodedEngine, CarEngine *pE)
|
|
{
|
|
QCBORError uErr;
|
|
QCBORDecodeContext DecodeCtx;
|
|
|
|
/* Let QCBORDecode internal error tracking do its work. */
|
|
QCBORDecode_Init(&DecodeCtx, EncodedEngine, QCBOR_DECODE_MODE_NORMAL);
|
|
QCBORDecode_EnterMap(&DecodeCtx, NULL);
|
|
QCBORDecode_GetTextStringInMapSZ(&DecodeCtx, "Manufacturer", &(pE->Manufacturer));
|
|
QCBORDecode_GetInt64InMapSZ(&DecodeCtx, "Displacement", &(pE->uDisplacement));
|
|
QCBORDecode_GetInt64InMapSZ(&DecodeCtx, "Horsepower", &(pE->uHorsePower));
|
|
#ifndef USEFULBUF_DISABLE_ALL_FLOAT
|
|
QCBORDecode_GetDoubleInMapSZ(&DecodeCtx, "DesignedCompression", &(pE->dDesignedCompresion));
|
|
#endif /* USEFULBUF_DISABLE_ALL_FLOAT */
|
|
QCBORDecode_GetBoolInMapSZ(&DecodeCtx, "Turbo", &(pE->bTurboCharged));
|
|
|
|
QCBORDecode_GetInt64InMapSZ(&DecodeCtx, "NumCylinders", &(pE->uNumCylinders));
|
|
|
|
/* Check the internal tracked error now before going on to
|
|
* reference any of the decoded data, particularly
|
|
* pE->uNumCylinders */
|
|
uErr = QCBORDecode_GetError(&DecodeCtx);
|
|
if(uErr != QCBOR_SUCCESS) {
|
|
goto Done;
|
|
}
|
|
|
|
if(pE->uNumCylinders > MAX_CYLINDERS) {
|
|
return TooManyCylinders;
|
|
}
|
|
|
|
QCBORDecode_EnterArrayFromMapSZ(&DecodeCtx, "Cylinders");
|
|
#ifndef USEFULBUF_DISABLE_ALL_FLOAT
|
|
for(int64_t i = 0; i < pE->uNumCylinders; i++) {
|
|
QCBORDecode_GetDouble(&DecodeCtx,
|
|
&(pE->cylinders[i].dMeasuredCompression));
|
|
}
|
|
#endif /* USEFULBUF_DISABLE_ALL_FLOAT */
|
|
QCBORDecode_ExitArray(&DecodeCtx);
|
|
QCBORDecode_ExitMap(&DecodeCtx);
|
|
|
|
/* Catch further decoding error here */
|
|
uErr = QCBORDecode_Finish(&DecodeCtx);
|
|
|
|
Done:
|
|
return ConvertError(uErr);
|
|
}
|
|
|
|
|
|
int32_t RunQCborExample()
|
|
{
|
|
CarEngine InitialEngine;
|
|
CarEngine DecodedEngine;
|
|
|
|
/* For every buffer used by QCBOR a pointer and a length are always
|
|
* carried in a UsefulBuf. This is a secure coding and hygene
|
|
* practice to help make sure code never runs off the end of a
|
|
* buffer.
|
|
*
|
|
* UsefulBuf structures are passed as a stack parameter to make the
|
|
* code prettier. The object code generated isn't much different
|
|
* from passing a pointer parameter and a length parameter.
|
|
*
|
|
* This macro is equivalent to:
|
|
* uint8_t __pBufEngineBuffer[300];
|
|
* UsefulBuf EngineBuffer = {__pBufEngineBuffer, 300};
|
|
*/
|
|
UsefulBuf_MAKE_STACK_UB( EngineBuffer, 300);
|
|
|
|
/* The pointer in UsefulBuf is not const and used for representing
|
|
* a buffer to be written to. For UsefulbufC, the pointer is const
|
|
* and is used to represent a buffer that has been written to.
|
|
*/
|
|
UsefulBufC EncodedEngine;
|
|
EngineDecodeErrors uErr;
|
|
|
|
/* Initialize the structure with some values. */
|
|
EngineInit(&InitialEngine);
|
|
|
|
/* Encode the engine structure. */
|
|
EncodedEngine = EncodeEngine(&InitialEngine, EngineBuffer);
|
|
if(UsefulBuf_IsNULLC(EncodedEngine)) {
|
|
printf("Engine encode failed\n");
|
|
goto Done;
|
|
}
|
|
printf("Example: Definite Length Engine Encoded in %zu bytes\n",
|
|
EncodedEngine.len);
|
|
|
|
/* Decode the CBOR */
|
|
uErr = DecodeEngineSpiffy(EncodedEngine, &DecodedEngine);
|
|
printf("Example: Spiffy Engine Decode Result: %d\n", uErr);
|
|
if(uErr) {
|
|
goto Done;
|
|
}
|
|
|
|
/* Check the results */
|
|
if(!EngineCompare(&InitialEngine, &DecodedEngine)) {
|
|
printf("Example: Spiffy Engine Decode comparison fail\n");
|
|
}
|
|
|
|
|
|
/* Further example of how to calculate the encoded size, then allocate */
|
|
UsefulBufC EncodedEngineSize;
|
|
EncodedEngineSize = EncodeEngine(&InitialEngine, SizeCalculateUsefulBuf);
|
|
if(UsefulBuf_IsNULLC(EncodedEngine)) {
|
|
printf("Engine encode size calculation failed\n");
|
|
goto Done;
|
|
}
|
|
(void)EncodedEngineSize; /* Supress unsed variable warning */
|
|
/* Here malloc could be called to allocate a buffer. Then
|
|
* EncodeEngine() can be called a second time to actually
|
|
* encode. (The actual code is not live here to avoid a
|
|
* dependency on malloc()).
|
|
* UsefulBuf MallocedBuffer;
|
|
* MallocedBuffer.len = EncodedEngineSize.len;
|
|
* MallocedBuffer.ptr = malloc(EncodedEngineSize.len);
|
|
* EncodedEngine = EncodeEngine(&InitialEngine, MallocedBuffer);
|
|
*/
|
|
|
|
Done:
|
|
printf("\n");
|
|
|
|
return 0;
|
|
}
|