blob: 9e51198573547f0fecd889bb0b8d0d8a2451281f [file] [log] [blame]
/* =========================================================================
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 encoding will be done. The size is in @c .len of the returned
* 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;
}
/* 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;
}