blob: cd2e4e01465b8b50ce0331e44c16ef71c38afe43 [file] [log] [blame]
/* ==========================================================================
* qcbor_spiffy_decode.c -- "Spiffy" QCBOR decoding
*
* Copyright (c) 2016-2018, The Linux Foundation.
* Copyright (c) 2018-2025, Laurence Lundblade.
* Copyright (c) 2021, Arm Limited.
* All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*
* See BSD-3-Clause license in README.md
*
* Forked from qcbor_decode.c on 11/28/24
* ========================================================================== */
#include "qcbor/qcbor_main_decode.h"
#include "qcbor/qcbor_spiffy_decode.h"
#include "decode_nesting.h"
#if (defined(__GNUC__) && !defined(__clang__))
/*
* This is how the -Wmaybe-uninitialized compiler warning is
* handled. It can’t be ignored because some version of gcc enable it
* with -Wall which is a common and useful gcc warning option. It also
* can’t be ignored because it is the goal of QCBOR to compile clean
* out of the box in all environments.
*
* The big problem with -Wmaybe-uninitialized is that it generates
* false positives. It complains things are uninitialized when they
* are not. This is because it is not a thorough static analyzer. This
* is why “maybe” is in its name. The problem is it is just not
* thorough enough to understand all the code (and someone saw fit to
* put it in gcc and worse to enable it with -Wall).
*
* One solution would be to change the code so -Wmaybe-uninitialized
* doesn’t get confused, for example adding an unnecessary extra
* initialization to zero. (If variables were truly uninitialized, the
* correct path is to understand the code thoroughly and set them to
* the correct value at the correct time; in essence this is already
* done; -Wmaybe-uninitialized just can’t tell). This path is not
* taken because it makes the code bigger and is kind of the tail
* wagging the dog.
*
* The solution here is to just use a pragma to disable it for the
* whole file. Disabling it for each line makes the code fairly ugly
* requiring #pragma to push, pop and ignore. Another reason is the
* warnings issues vary by version of gcc and which optimization
* optimizations are selected. Another reason is that compilers other
* than gcc don’t have -Wmaybe-uninitialized.
*
* One may ask how to be sure these warnings are false positives and
* not real issues. 1) The code has been read carefully to check. 2)
* Testing is pretty thorough. 3) This code has been run through
* thorough high-quality static analyzers.
*
* In particularly, most of the warnings are about
* Item.Item->uDataType being uninitialized. QCBORDecode_GetNext()
* *always* sets this value and test case confirm
* this. -Wmaybe-uninitialized just can't tell.
*
* https://stackoverflow.com/questions/5080848/disable-gcc-may-be-used-uninitialized-on-a-particular-variable
*/
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
#endif
/* Public function, see qcbor/qcbor_spiffy_decode.h */
void
QCBORDecode_Private_GetString(QCBORDecodeContext *pMe, UsefulBufC *pText, uint8_t uType)
{
QCBORItem Item;
QCBORDecode_VGetNext(pMe, &Item);
*pText = NULLUsefulBufC;
if(pMe->uLastError == QCBOR_SUCCESS) {
if(Item.uDataType == uType) {
*pText = Item.val.string;
} else {
pMe->uLastError = QCBOR_ERR_UNEXPECTED_TYPE;
}
}
}
/* Return true if the labels in Item1 and Item2 are the same.
Works only for integer and string labels. Returns false
for any other type. */
static bool
QCBORItem_MatchLabel(const QCBORItem Item1, const QCBORItem Item2)
{
if(Item1.uLabelType == QCBOR_TYPE_INT64) {
if(Item2.uLabelType == QCBOR_TYPE_INT64 && Item1.label.int64 == Item2.label.int64) {
return true;
}
#ifndef QCBOR_DISABLE_NON_INTEGER_LABELS
} else if(Item1.uLabelType == QCBOR_TYPE_TEXT_STRING) {
if(Item2.uLabelType == QCBOR_TYPE_TEXT_STRING && !UsefulBuf_Compare(Item1.label.string, Item2.label.string)) {
return true;
}
} else if(Item1.uLabelType == QCBOR_TYPE_BYTE_STRING) {
if(Item2.uLabelType == QCBOR_TYPE_BYTE_STRING && !UsefulBuf_Compare(Item1.label.string, Item2.label.string)) {
return true;
}
} else if(Item1.uLabelType == QCBOR_TYPE_UINT64) {
if(Item2.uLabelType == QCBOR_TYPE_UINT64 && Item1.label.uint64 == Item2.label.uint64) {
return true;
}
#endif /* ! QCBOR_DISABLE_NON_INTEGER_LABELS */
}
/* Other label types are never matched */
return false;
}
/*
Returns true if Item1 and Item2 are the same type
or if either are of QCBOR_TYPE_ANY.
*/
static bool
QCBORItem_MatchType(const QCBORItem Item1, const QCBORItem Item2)
{
if(Item1.uDataType == Item2.uDataType) {
return true;
} else if(Item1.uDataType == QCBOR_TYPE_ANY) {
return true;
} else if(Item2.uDataType == QCBOR_TYPE_ANY) {
return true;
}
return false;
}
/**
* @brief Rewind cursor to start as if map or array were just entered.
*
* @param[in] pMe The decoding context
*
* This affects the nesting tracking and the UsefulInputBuf.
*/
static void
QCBORDecode_Private_RewindMapOrArray(QCBORDecodeContext *pMe)
{
/* Reset nesting tracking to the deepest bounded level */
DecodeNesting_SetCurrentToBoundedLevel(&(pMe->nesting));
DecodeNesting_ResetMapOrArrayCount(&(pMe->nesting));
/* Reposition traversal cursor to the start of the map/array */
UsefulInputBuf_Seek(&(pMe->InBuf),
DecodeNesting_GetMapOrArrayStart(&(pMe->nesting)));
}
/* Public function, see qcbor/qcbor_spiffy_decode.h */
void
QCBORDecode_Rewind(QCBORDecodeContext *pMe)
{
if(pMe->nesting.pCurrentBounded != NULL) {
/* In a bounded map, array or bstr-wrapped CBOR */
if(DecodeNesting_IsBoundedType(&(pMe->nesting), QCBOR_TYPE_BYTE_STRING)) {
/* In bstr-wrapped CBOR. */
/* Reposition traversal cursor to start of wrapping byte string */
UsefulInputBuf_Seek(&(pMe->InBuf),
pMe->nesting.pCurrentBounded->u.bs.uBstrStartOffset);
DecodeNesting_SetCurrentToBoundedLevel(&(pMe->nesting));
} else {
/* In a map or array */
QCBORDecode_Private_RewindMapOrArray(pMe);
}
} else {
/* Not in anything bounded */
/* Reposition traversal cursor to the start of input CBOR */
UsefulInputBuf_Seek(&(pMe->InBuf), 0ULL);
/* Reset nesting tracking to beginning of input. */
DecodeNesting_Init(&(pMe->nesting));
}
pMe->uLastError = QCBOR_SUCCESS;
}
/**
* @brief Search a map for a set of items.
*
* @param[in] pMe The decode context to search.
* @param[in,out] pItemArray The items to search for and the items found.
* @param[out] pInfo Several bits of meta-info returned by search.
* @param[in] pCallBack Callback object or @c NULL.
*
* @retval QCBOR_ERR_NOT_ENTERED Trying to search without entering a map.
*
* @retval QCBOR_ERR_DUPLICATE_LABEL Duplicate items (items with the same label)
* were found for one of the labels being
* search for. This duplicate detection is
* only performed for items in pItemArray,
* not every item in the map.
*
* @retval QCBOR_ERR_UNEXPECTED_TYPE A label was matched, but the type was
* wrong for the matchd label.
*
* @retval Also errors returned by QCBORDecode_GetNext().
*
* On input, @c pItemArray contains a list of labels and data types of
* items to be found.
*
* On output, the fully retrieved items are filled in with values and
* such. The label was matched, so it never changes.
*
* If an item was not found, its data type is set to @ref QCBOR_TYPE_NONE.
*
* This also finds the ends of maps and arrays when they are exited.
*/
QCBORError
QCBORDecode_Private_MapSearch(QCBORDecodeContext *pMe,
QCBORItem *pItemArray,
MapSearchInfo *pInfo,
MapSearchCallBack *pCallBack)
{
QCBORError uReturn;
uint64_t uFoundItemBitMap = 0;
if(pMe->uLastError != QCBOR_SUCCESS) {
uReturn = pMe->uLastError;
goto Done2;
}
if(!DecodeNesting_IsBoundedType(&(pMe->nesting), QCBOR_TYPE_MAP) &&
pItemArray->uLabelType != QCBOR_TYPE_NONE) {
/* QCBOR_TYPE_NONE as first item indicates just looking
for the end of an array, so don't give error. */
uReturn = QCBOR_ERR_MAP_NOT_ENTERED;
goto Done2;
}
if(DecodeNesting_IsBoundedEmpty(&(pMe->nesting))) {
// It is an empty bounded array or map
if(pItemArray->uLabelType == QCBOR_TYPE_NONE) {
// Just trying to find the end of the map or array
pMe->uMapEndOffsetCache = DecodeNesting_GetMapOrArrayStart(&(pMe->nesting));
uReturn = QCBOR_SUCCESS;
} else {
// Nothing is ever found in an empty array or map. All items
// are marked as not found below.
uReturn = QCBOR_SUCCESS;
}
goto Done2;
}
QCBORDecodeNesting SaveNesting;
size_t uSavePos = UsefulInputBuf_Tell(&(pMe->InBuf));
DecodeNesting_PrepareForMapSearch(&(pMe->nesting), &SaveNesting);
/* Reposition to search from the start of the map / array */
QCBORDecode_Private_RewindMapOrArray(pMe);
/*
Loop over all the items in the map or array. Each item
could be a map or array, but label matching is only at
the main level. This handles definite- and indefinite-
length maps and arrays. The only reason this is ever
called on arrays is to find their end position.
This will always run over all items in order to do
duplicate detection.
This will exit with failure if it encounters an
unrecoverable error, but continue on for recoverable
errors.
If a recoverable error occurs on a matched item, then
that error code is returned.
*/
const uint8_t uMapNestLevel = DecodeNesting_GetBoundedModeLevel(&(pMe->nesting));
if(pInfo) {
pInfo->uItemCount = 0;
}
uint8_t uNextNestLevel;
do {
/* Remember offset of the item because sometimes it has to be returned */
const size_t uOffset = UsefulInputBuf_Tell(&(pMe->InBuf));
/* Get the item */
QCBORItem Item;
/* QCBORDecode_Private_GetNextTagContent() rather than GetNext()
* because a label match is performed on recoverable errors to
* be able to return the the error code for the found item. */
QCBORError uResult = QCBORDecode_Private_GetNextTagContent(pMe, &Item);
if(QCBORDecode_IsUnrecoverableError(uResult)) {
/* The map/array can't be decoded when unrecoverable errors occur */
uReturn = uResult;
goto Done;
}
if(uResult == QCBOR_ERR_NO_MORE_ITEMS) {
/* Unexpected end of map or array. */
uReturn = uResult;
goto Done;
}
/* See if item has one of the labels that are of interest */
bool bMatched = false;
for(int nIndex = 0; pItemArray[nIndex].uLabelType != QCBOR_TYPE_NONE; nIndex++) {
if(QCBORItem_MatchLabel(Item, pItemArray[nIndex])) {
/* A label match has been found */
if(uFoundItemBitMap & (0x01ULL << nIndex)) {
uReturn = QCBOR_ERR_DUPLICATE_LABEL;
goto Done;
}
if(uResult != QCBOR_SUCCESS) {
/* The label matches, but the data item is in error.
* It is OK to have recoverable errors on items that
* are not matched. */
uReturn = uResult;
goto Done;
}
if(!QCBORItem_MatchType(Item, pItemArray[nIndex])) {
/* The data item is not of the type(s) requested */
uReturn = QCBOR_ERR_UNEXPECTED_TYPE;
goto Done;
}
/* Successful match. Return the item. */
pItemArray[nIndex] = Item;
uFoundItemBitMap |= 0x01ULL << nIndex;
if(pInfo) {
pInfo->uStartOffset = uOffset;
}
bMatched = true;
}
}
if(!bMatched && pCallBack != NULL) {
/*
Call the callback on unmatched labels.
(It is tempting to do duplicate detection here, but that would
require dynamic memory allocation because the number of labels
that might be encountered is unbounded.)
*/
uReturn = (*(pCallBack->pfCallback))(pCallBack->pCBContext, &Item);
if(uReturn != QCBOR_SUCCESS) {
goto Done;
}
}
/*
Consume the item whether matched or not. This
does the work of traversing maps and array and
everything in them. In this loop only the
items at the current nesting level are examined
to match the labels.
*/
uReturn = QCBORDecode_Private_ConsumeItem(pMe, &Item, NULL, &uNextNestLevel);
if(uReturn != QCBOR_SUCCESS) {
goto Done;
}
if(pInfo) {
pInfo->uItemCount++;
}
} while (uNextNestLevel >= uMapNestLevel);
uReturn = QCBOR_SUCCESS;
const size_t uEndOffset = UsefulInputBuf_Tell(&(pMe->InBuf));
// Check here makes sure that this won't accidentally be
// QCBOR_MAP_OFFSET_CACHE_INVALID which is larger than
// QCBOR_MAX_SIZE.
// Cast to uint32_t to possibly address cases where SIZE_MAX < UINT32_MAX
if((uint32_t)uEndOffset >= QCBOR_MAX_SIZE) {
uReturn = QCBOR_ERR_INPUT_TOO_LARGE;
goto Done;
}
/* Cast OK because encoded CBOR is limited to UINT32_MAX */
pMe->uMapEndOffsetCache = (uint32_t)uEndOffset;
Done:
DecodeNesting_RestoreFromMapSearch(&(pMe->nesting), &SaveNesting);
UsefulInputBuf_Seek(&(pMe->InBuf), uSavePos);
Done2:
/* For all items not found, set the data and label type to QCBOR_TYPE_NONE */
for(int i = 0; pItemArray[i].uLabelType != 0; i++) {
if(!(uFoundItemBitMap & (0x01ULL << i))) {
pItemArray[i].uDataType = QCBOR_TYPE_NONE;
pItemArray[i].uLabelType = QCBOR_TYPE_NONE;
}
}
return uReturn;
}
/* Public function, see qcbor/qcbor_spiffy_decode.h */
void
QCBORDecode_SeekToLabelN(QCBORDecodeContext *pMe, int64_t nLabel)
{
MapSearchInfo Info;
QCBORItem OneItemSeach[2];
if(pMe->uLastError != QCBOR_SUCCESS) {
return;
}
OneItemSeach[0].uLabelType = QCBOR_TYPE_INT64;
OneItemSeach[0].label.int64 = nLabel;
OneItemSeach[0].uDataType = QCBOR_TYPE_ANY;
OneItemSeach[1].uLabelType = QCBOR_TYPE_NONE; // Indicates end of array
pMe->uLastError = (uint8_t)QCBORDecode_Private_MapSearch(pMe, OneItemSeach, &Info, NULL);
if(pMe->uLastError == QCBOR_SUCCESS) {
UsefulInputBuf_Seek(&(pMe->InBuf), Info.uStartOffset);
}
}
void
QCBORDecode_SeekToLabelSZ(QCBORDecodeContext *pMe, const char *szLabel)
{
#ifndef QCBOR_DISABLE_NON_INTEGER_LABELS
MapSearchInfo Info;
QCBORItem OneItemSeach[2];
if(pMe->uLastError != QCBOR_SUCCESS) {
return;
}
OneItemSeach[0].uLabelType = QCBOR_TYPE_TEXT_STRING;
OneItemSeach[0].label.string = UsefulBuf_FromSZ(szLabel);
OneItemSeach[0].uDataType = QCBOR_TYPE_ANY;
OneItemSeach[1].uLabelType = QCBOR_TYPE_NONE; // Indicates end of array
pMe->uLastError = (uint8_t)QCBORDecode_Private_MapSearch(pMe, OneItemSeach, &Info, NULL);
if(pMe->uLastError == QCBOR_SUCCESS) {
UsefulInputBuf_Seek(&(pMe->InBuf), Info.uStartOffset);
}
#else
(void)pMe;
(void)szLabel;
pMe->uLastError = QCBOR_ERR_LABEL_NOT_FOUND;
#endif /* ! QCBOR_DISABLE_NON_INTEGER_LABELS */
}
void
QCBORDecode_Private_GetItemInMapNoCheck(QCBORDecodeContext *pMe,
QCBORItem *OneItemSeach,
QCBORItem *pItem,
size_t *puOffset)
{
QCBORError uErr;
MapSearchInfo SearchInfo;
if(pMe->uLastError != QCBOR_SUCCESS) {
return;
}
uErr = QCBORDecode_Private_MapSearch(pMe, OneItemSeach, &SearchInfo, NULL);
if(uErr == QCBOR_SUCCESS && OneItemSeach[0].uDataType == QCBOR_TYPE_NONE) {
uErr = QCBOR_ERR_LABEL_NOT_FOUND;
}
*pItem = OneItemSeach[0];
*puOffset = SearchInfo.uStartOffset;
if(uErr == QCBOR_SUCCESS) {
QCBORDecode_Private_SaveTagNumbers(pMe, pItem);
}
pMe->uLastError = (uint8_t)uErr;
}
static void
QCBORDecode_Private_GetItemInMap(QCBORDecodeContext *pMe, QCBORItem *OneItemSeach, QCBORItem *pItem)
{
QCBORError uErr;
size_t uOffset;
QCBORDecode_Private_GetItemInMapNoCheck(pMe, OneItemSeach, pItem, &uOffset);
uErr = QCBORDecode_Private_GetItemChecks(pMe, pMe->uLastError, uOffset, pItem);
if(uErr != QCBOR_SUCCESS) {
goto Done;
}
QCBORDecode_Private_SaveTagNumbers(pMe, pItem);
Done:
pMe->uLastError = (uint8_t)uErr;
}
/* Public function, see qcbor/qcbor_spiffy_decode.h */
void
QCBORDecode_GetItemInMapN(QCBORDecodeContext *pMe,
const int64_t nLabel,
const uint8_t uQcborType,
QCBORItem *pItem)
{
QCBORItem OneItemSeach[2];
OneItemSeach[0].uLabelType = QCBOR_TYPE_INT64;
OneItemSeach[0].label.int64 = nLabel;
OneItemSeach[0].uDataType = uQcborType;
OneItemSeach[1].uLabelType = QCBOR_TYPE_NONE; // Indicates end of array
QCBORDecode_Private_GetItemInMap(pMe, OneItemSeach, pItem);
}
/**
* @brief Get an item by label by type.
*
* @param[in] pMe The decode context.
* @param[in] nLabel The label to search map for.
* @param[in] uQcborType The QCBOR type to look for.
* @param[out] pItem The item found.
* @param[out] puOffset The offset of item for tag consumption check.
*
* This finds the item with the given label in currently open
* map. This does not call QCBORDecode_Private_GetItemChecks()
* to check tag number consumption or decode conformance.
*/
void
QCBORDecode_Private_GetItemInMapNoCheckN(QCBORDecodeContext *pMe,
const int64_t nLabel,
const uint8_t uQcborType,
QCBORItem *pItem,
size_t *puOffset)
{
QCBORItem OneItemSeach[2];
OneItemSeach[0].uLabelType = QCBOR_TYPE_INT64;
OneItemSeach[0].label.int64 = nLabel;
OneItemSeach[0].uDataType = uQcborType;
OneItemSeach[1].uLabelType = QCBOR_TYPE_NONE; // Indicates end of array
QCBORDecode_Private_GetItemInMapNoCheck(pMe, OneItemSeach, pItem, puOffset);
}
/* Public function, see qcbor/qcbor_spiffy_decode.h */
void
QCBORDecode_GetItemInMapSZ(QCBORDecodeContext *pMe,
const char *szLabel,
const uint8_t uQcborType,
QCBORItem *pItem)
{
#ifndef QCBOR_DISABLE_NON_INTEGER_LABELS
QCBORItem OneItemSeach[2];
OneItemSeach[0].uLabelType = QCBOR_TYPE_TEXT_STRING;
OneItemSeach[0].label.string = UsefulBuf_FromSZ(szLabel);
OneItemSeach[0].uDataType = uQcborType;
OneItemSeach[1].uLabelType = QCBOR_TYPE_NONE; // Indicates end of array
QCBORDecode_Private_GetItemInMap(pMe, OneItemSeach, pItem);
#else
(void)pMe;
(void)szLabel;
(void)uQcborType;
(void)pItem;
pMe->uLastError = QCBOR_ERR_LABEL_NOT_FOUND;
#endif /* ! QCBOR_DISABLE_NON_INTEGER_LABELS */
}
/**
* @brief Get an item by string label of a particular type
*
* @param[in] pMe The decode context.
* @param[in] szLabel The label to search map for.
* @param[in] uQcborType The QCBOR type to look for.
* @param[out] pItem The item found.
* @param[out] puOffset The offset of item for tag consumption check.
*
* This finds the item with the given label in currently open
* map. This does not call QCBORDecode_Private_GetItemChecks()
* to check tag number consumption or decode conformance.
*/
void
QCBORDecode_Private_GetItemInMapNoCheckSZ(QCBORDecodeContext *pMe,
const char *szLabel,
const uint8_t uQcborType,
QCBORItem *pItem,
size_t *puOffset)
{
#ifndef QCBOR_DISABLE_NON_INTEGER_LABELS
QCBORItem OneItemSeach[2];
OneItemSeach[0].uLabelType = QCBOR_TYPE_TEXT_STRING;
OneItemSeach[0].label.string = UsefulBuf_FromSZ(szLabel);
OneItemSeach[0].uDataType = uQcborType;
OneItemSeach[1].uLabelType = QCBOR_TYPE_NONE; // Indicates end of array
QCBORDecode_Private_GetItemInMapNoCheck(pMe, OneItemSeach, pItem, puOffset);
#else
(void)pMe;
(void)szLabel;
(void)uQcborType;
(void)pItem;
(void)puOffset;
pMe->uLastError = QCBOR_ERR_LABEL_NOT_FOUND;
#endif /* ! QCBOR_DISABLE_NON_INTEGER_LABELS */
}
/**
* @brief Semi-private. Get pointer, length and item count of an array or map.
*
* @param[in] pMe The decode context.
* @param[in] pTarget The label and type of the array or map to retrieve.
* @param[out] pItem The item for the array/map.
* @param[out] pEncodedCBOR Pointer and length of the encoded map or array.
*
* The next item to be decoded must be a map or array as specified by @c uType.
*
* When this is complete, the traversal cursor is unchanged.
*/void
QCBORDecode_Private_SearchAndGetArrayOrMap(QCBORDecodeContext *pMe,
QCBORItem *pTarget,
QCBORItem *pItem,
UsefulBufC *pEncodedCBOR)
{
MapSearchInfo Info;
QCBORDecodeNesting SaveNesting;
size_t uSaveCursor;
pMe->uLastError = (uint8_t)QCBORDecode_Private_MapSearch(pMe, pTarget, &Info, NULL);
if(pMe->uLastError != QCBOR_SUCCESS) {
return;
}
pMe->uLastError = (uint8_t)QCBORDecode_Private_GetItemChecks(pMe, pMe->uLastError, Info.uStartOffset, pItem);
if(pMe->uLastError != QCBOR_SUCCESS) {
return;
}
/* Save the whole position of things so they can be restored.
* so the cursor position is unchanged by this operation, like
* all the other GetXxxxInMap() operations. */
DecodeNesting_PrepareForMapSearch(&(pMe->nesting), &SaveNesting);
uSaveCursor = UsefulInputBuf_Tell(&(pMe->InBuf));
DecodeNesting_ResetMapOrArrayCount(&(pMe->nesting));
UsefulInputBuf_Seek(&(pMe->InBuf), Info.uStartOffset);
QCBORDecode_Private_GetArrayOrMap(pMe, pTarget[0].uDataType, pItem, pEncodedCBOR);
UsefulInputBuf_Seek(&(pMe->InBuf), uSaveCursor);
DecodeNesting_RestoreFromMapSearch(&(pMe->nesting), &SaveNesting);
}
/* Public function, see qcbor/qcbor_spiffy_decode.h */
void
QCBORDecode_GetItemsInMap(QCBORDecodeContext *pMe, QCBORItem *pItemList)
{
pMe->uLastError = (uint8_t)QCBORDecode_Private_MapSearch(pMe, pItemList, NULL, NULL);
}
/* Public function, see qcbor/qcbor_spiffy_decode.h */
void
QCBORDecode_GetItemsInMapWithCallback(QCBORDecodeContext *pMe,
QCBORItem *pItemList,
void *pCallbackCtx,
QCBORItemCallback pfCB)
{
MapSearchCallBack CallBack;
CallBack.pCBContext = pCallbackCtx;
CallBack.pfCallback = pfCB;
pMe->uLastError = (uint8_t)QCBORDecode_Private_MapSearch(pMe, pItemList, NULL, &CallBack);
}
/**
* @brief Search for a map/array by label and enter it
*
* @param[in] pMe The decode context.
* @param[in] pSearch The map/array to search for.
*
* @c pSearch is expected to contain one item of type map or array
* with the label specified. The current bounded map will be searched for
* this and if found will be entered.
*
* If the label is not found, or the item found is not a map or array,
* the error state is set.
*/
static void
QCBORDecode_Private_SearchAndEnter(QCBORDecodeContext *pMe, QCBORItem pSearch[])
{
QCBORError uErr;
MapSearchInfo SearchInfo;
// The first item in pSearch is the one that is to be
// entered. It should be the only one filled in. Any other
// will be ignored unless it causes an error.
if(pMe->uLastError != QCBOR_SUCCESS) {
return;
}
uErr = QCBORDecode_Private_MapSearch(pMe, pSearch, &SearchInfo, NULL);
pMe->uLastError = (uint8_t)QCBORDecode_Private_GetItemChecks(pMe, uErr, SearchInfo.uStartOffset, pSearch);
if(pMe->uLastError != QCBOR_SUCCESS) {
return;
}
if(pSearch->uDataType == QCBOR_TYPE_NONE) {
pMe->uLastError = QCBOR_ERR_LABEL_NOT_FOUND;
return;
}
/* The map or array was found. Now enter it.
*
* QCBORDecode_EnterBoundedMapOrArray() used here, requires the
* next item for the pre-order traversal cursor to be the map/array
* found by MapSearch(). The next few lines of code force the
* cursor to that.
*
* There is no need to retain the old cursor because
* QCBORDecode_EnterBoundedMapOrArray() will set it to the
* beginning of the map/array being entered.
*
* The cursor is forced by: 1) setting the input buffer position to
* the item offset found by MapSearch(), 2) setting the map/array
* counter to the total in the map/array, 3) setting the nesting
* level. Setting the map/array counter to the total is not
* strictly correct, but this is OK because this cursor only needs
* to be used to get one item and MapSearch() has already found it
* confirming it exists.
*/
UsefulInputBuf_Seek(&(pMe->InBuf), SearchInfo.uStartOffset);
DecodeNesting_ResetMapOrArrayCount(&(pMe->nesting));
DecodeNesting_SetCurrentToBoundedLevel(&(pMe->nesting));
QCBORDecode_Private_EnterBoundedMapOrArray(pMe, pSearch->uDataType, NULL);
}
/* Public function, see qcbor/qcbor_spiffy_decode.h */
void
QCBORDecode_EnterMapFromMapN(QCBORDecodeContext *pMe, int64_t nLabel)
{
QCBORItem OneItemSeach[2];
OneItemSeach[0].uLabelType = QCBOR_TYPE_INT64;
OneItemSeach[0].label.int64 = nLabel;
OneItemSeach[0].uDataType = QCBOR_TYPE_MAP;
OneItemSeach[1].uLabelType = QCBOR_TYPE_NONE;
/* The map to enter was found, now finish off entering it. */
QCBORDecode_Private_SearchAndEnter(pMe, OneItemSeach);
}
/* Public function, see qcbor/qcbor_spiffy_decode.h */
void
QCBORDecode_EnterMapFromMapSZ(QCBORDecodeContext *pMe, const char *szLabel)
{
#ifndef QCBOR_DISABLE_NON_INTEGER_LABELS
QCBORItem OneItemSeach[2];
OneItemSeach[0].uLabelType = QCBOR_TYPE_TEXT_STRING;
OneItemSeach[0].label.string = UsefulBuf_FromSZ(szLabel);
OneItemSeach[0].uDataType = QCBOR_TYPE_MAP;
OneItemSeach[1].uLabelType = QCBOR_TYPE_NONE;
QCBORDecode_Private_SearchAndEnter(pMe, OneItemSeach);
#else
(void)szLabel;
pMe->uLastError = QCBOR_ERR_LABEL_NOT_FOUND;
#endif /* ! QCBOR_DISABLE_NON_INTEGER_LABELS */
}
/* Public function, see qcbor/qcbor_spiffy_decode.h */
void
QCBORDecode_EnterArrayFromMapN(QCBORDecodeContext *pMe, int64_t nLabel)
{
QCBORItem OneItemSeach[2];
OneItemSeach[0].uLabelType = QCBOR_TYPE_INT64;
OneItemSeach[0].label.int64 = nLabel;
OneItemSeach[0].uDataType = QCBOR_TYPE_ARRAY;
OneItemSeach[1].uLabelType = QCBOR_TYPE_NONE;
QCBORDecode_Private_SearchAndEnter(pMe, OneItemSeach);
}
/* Public function, see qcbor/qcbor_spiffy_decode.h */
void
QCBORDecode_EnterArrayFromMapSZ(QCBORDecodeContext *pMe, const char *szLabel)
{
#ifndef QCBOR_DISABLE_NON_INTEGER_LABELS
QCBORItem OneItemSeach[2];
OneItemSeach[0].uLabelType = QCBOR_TYPE_TEXT_STRING;
OneItemSeach[0].label.string = UsefulBuf_FromSZ(szLabel);
OneItemSeach[0].uDataType = QCBOR_TYPE_ARRAY;
OneItemSeach[1].uLabelType = QCBOR_TYPE_NONE;
QCBORDecode_Private_SearchAndEnter(pMe, OneItemSeach);
#else
(void)szLabel;
pMe->uLastError = QCBOR_ERR_LABEL_NOT_FOUND;
#endif /* ! QCBOR_DISABLE_NON_INTEGER_LABELS */
}
/**
* @brief Semi-private to do the the work for EnterMap() and EnterArray().
*
* @param[in] pMe The decode context
* @param[in] uType QCBOR_TYPE_MAP or QCBOR_TYPE_ARRAY.
* @param[out] pItem The data item for the map or array entered.
*
* The next item in the traversal must be a map or array. This
* consumes that item and does the book keeping to enter the map or
* array.
*/
void
QCBORDecode_Private_EnterBoundedMapOrArray(QCBORDecodeContext *pMe,
const uint8_t uType,
QCBORItem *pItem)
{
QCBORError uErr;
/* Must only be called on maps and arrays. */
if(pMe->uLastError != QCBOR_SUCCESS) {
// Already in error state; do nothing.
return;
}
/* Get the data item that is the map or array being entered. */
QCBORItem Item;
uErr = QCBORDecode_GetNext(pMe, &Item);
if(uErr != QCBOR_SUCCESS) {
goto Done;
}
uint8_t uItemDataType = Item.uDataType;
#ifndef QCBOR_DISABLE_NON_INTEGER_LABELS
if(uItemDataType == QCBOR_TYPE_MAP_AS_ARRAY ) {
uItemDataType = QCBOR_TYPE_ARRAY;
}
#endif /* ! QCBOR_DISABLE_NON_INTEGER_LABELS */
if(uItemDataType != uType) {
uErr = QCBOR_ERR_UNEXPECTED_TYPE;
goto Done;
}
QCBORDecode_Private_SaveTagNumbers(pMe, &Item);
const bool bIsEmpty = (Item.uNextNestLevel <= Item.uNestingLevel);
if(bIsEmpty) {
if(DecodeNesting_IsCurrentDefiniteLength(&(pMe->nesting))) {
// Undo decrement done by QCBORDecode_GetNext() so the the
// the decrement when exiting the map/array works correctly
pMe->nesting.pCurrent->u.ma.uCountCursor++;
}
// Special case to increment nesting level for zero-length maps
// and arrays entered in bounded mode.
DecodeNesting_Descend(&(pMe->nesting), uType);
}
pMe->uMapEndOffsetCache = QCBOR_MAP_OFFSET_CACHE_INVALID;
uErr = DecodeNesting_EnterBoundedMapOrArray(&(pMe->nesting), bIsEmpty,
UsefulInputBuf_Tell(&(pMe->InBuf)));
if(pItem != NULL) {
*pItem = Item;
}
Done:
pMe->uLastError = (uint8_t)uErr;
}
/**
* @brief Exit a bounded map, array or bstr (semi-private).
*
* @param[in] pMe Decode context.
* @param[in] uEndOffset The input buffer offset of the end of item exited.
*
* @returns QCBOR_SUCCESS or an error code.
*
* This is the common work for exiting a level that is a bounded map,
* array or bstr wrapped CBOR.
*
* One chunk of work is to set up the pre-order traversal so it is at
* the item just after the bounded map, array or bstr that is being
* exited. This is somewhat complex.
*
* The other work is to level-up the bounded mode to next higest
* bounded mode or the top level if there isn't one.
*/
QCBORError
QCBORDecode_Private_ExitBoundedLevel(QCBORDecodeContext *pMe,
const uint32_t uEndOffset)
{
QCBORError uErr;
/*
* First the pre-order-traversal byte offset is positioned to the
* item just after the bounded mode item that was just consumed.
*/
UsefulInputBuf_Seek(&(pMe->InBuf), uEndOffset);
/*
* Next, set the current nesting level to one above the bounded
* level that was just exited.
*
* DecodeNesting_CheckBoundedType() is always called before this
* and makes sure pCurrentBounded is valid.
*/
DecodeNesting_LevelUpCurrent(&(pMe->nesting));
/*
* This does the complex work of leveling up the pre-order
* traversal when the end of a map or array or another bounded
* level is reached. It may do nothing, or ascend all the way to
* the top level.
*/
uErr = QCBORDecode_Private_NestLevelAscender(pMe, NULL, false);
if(uErr != QCBOR_SUCCESS) {
goto Done;
}
/*
* This makes the next highest bounded level the current bounded
* level. If there is no next highest level, then no bounded mode
* is in effect.
*/
DecodeNesting_LevelUpBounded(&(pMe->nesting));
pMe->uMapEndOffsetCache = QCBOR_MAP_OFFSET_CACHE_INVALID;
Done:
return uErr;
}
/**
* @brief Get started exiting a map or array (semi-private)
*
* @param[in] pMe The decode context
* @param[in] uType QCBOR_TYPE_ARRAY or QCBOR_TYPE_MAP
*
* This does some work for map and array exiting (but not
* bstr exiting). Then QCBORDecode_Private_ExitBoundedLevel()
* is called to do the rest.
*/
void
QCBORDecode_Private_ExitBoundedMapOrArray(QCBORDecodeContext *pMe,
const uint8_t uType)
{
if(pMe->uLastError != QCBOR_SUCCESS) {
/* Already in error state; do nothing. */
return;
}
QCBORError uErr;
if(!DecodeNesting_IsBoundedType(&(pMe->nesting), uType)) {
uErr = QCBOR_ERR_EXIT_MISMATCH;
goto Done;
}
/*
Have to set the offset to the end of the map/array
that is being exited. If there is no cached value,
from previous map search, then do a dummy search.
*/
if(pMe->uMapEndOffsetCache == QCBOR_MAP_OFFSET_CACHE_INVALID) {
QCBORItem Dummy;
Dummy.uLabelType = QCBOR_TYPE_NONE;
uErr = QCBORDecode_Private_MapSearch(pMe, &Dummy, NULL, NULL);
if(uErr != QCBOR_SUCCESS) {
goto Done;
}
}
uErr = QCBORDecode_Private_ExitBoundedLevel(pMe, pMe->uMapEndOffsetCache);
Done:
pMe->uLastError = (uint8_t)uErr;
}
/**
* @brief Process simple type true and false, a boolean
*
* @param[in] pMe The decode context.
* @param[in] pItem The item with either true or false.
* @param[out] pBool The boolean value output.
*
* Sets the internal error if the item isn't a true or a false. Also
* records any tag numbers as the tag numbers of the last item.
*/
static void
QCBORDecode_Private_ProcessBool(QCBORDecodeContext *pMe,
const QCBORItem *pItem,
bool *pBool)
{
if(pMe->uLastError != QCBOR_SUCCESS) {
/* Already in error state, do nothing */
return;
}
switch(pItem->uDataType) {
case QCBOR_TYPE_TRUE:
*pBool = true;
break;
case QCBOR_TYPE_FALSE:
*pBool = false;
break;
default:
pMe->uLastError = QCBOR_ERR_UNEXPECTED_TYPE;
break;
}
}
/* Public function, see qcbor/qcbor_spiffy_decode.h */
void
QCBORDecode_GetBool(QCBORDecodeContext *pMe, bool *pValue)
{
QCBORItem Item;
QCBORDecode_VGetNext(pMe, &Item);
QCBORDecode_Private_ProcessBool(pMe, &Item, pValue);
}
/* Public function, see qcbor/qcbor_spiffy_decode.h */
void
QCBORDecode_GetBoolInMapN(QCBORDecodeContext *pMe,
const int64_t nLabel,
bool *pValue)
{
QCBORItem Item;
QCBORDecode_GetItemInMapN(pMe, nLabel, QCBOR_TYPE_ANY, &Item);
QCBORDecode_Private_ProcessBool(pMe, &Item, pValue);
}
/* Public function, see qcbor/qcbor_spiffy_decode.h */
void
QCBORDecode_GetBoolInMapSZ(QCBORDecodeContext *pMe,
const char *szLabel,
bool *pValue)
{
QCBORItem Item;
QCBORDecode_GetItemInMapSZ(pMe, szLabel, QCBOR_TYPE_ANY, &Item);
QCBORDecode_Private_ProcessBool(pMe, &Item, pValue);
}
/**
* @brief Process simple values.
*
* @param[in] pMe The decode context.
* @param[in] pItem The item with the simple value.
* @param[out] puSimple The simple value output.
*
* Sets the internal error if the item isn't a true or a false. Also
* records any tag numbers as the tag numbers of the last item.
*/
static void
QCBORDecode_Private_ProcessSimple(QCBORDecodeContext *pMe,
const QCBORItem *pItem,
uint8_t *puSimple)
{
if(pMe->uLastError != QCBOR_SUCCESS) {
return;
}
/* It's kind of lame to remap true...undef back to simple values, but
* this function isn't used much and to not do it would require
* changing GetNext() behavior in an incompatible way.
*/
switch(pItem->uDataType) {
case QCBOR_TYPE_UKNOWN_SIMPLE:
*puSimple = pItem->val.uSimple;
break;
case QCBOR_TYPE_TRUE:
*puSimple = CBOR_SIMPLEV_TRUE;
break;
case QCBOR_TYPE_FALSE:
*puSimple = CBOR_SIMPLEV_FALSE;
break;
case QCBOR_TYPE_NULL:
*puSimple = CBOR_SIMPLEV_NULL;
break;
case QCBOR_TYPE_UNDEF:
*puSimple = CBOR_SIMPLEV_UNDEF;
break;
default:
pMe->uLastError = QCBOR_ERR_UNEXPECTED_TYPE;
return;
}
}
/* Public function, see qcbor/qcbor_spiffy_decode.h */
void
QCBORDecode_GetSimple(QCBORDecodeContext *pMe, uint8_t *puSimple)
{
QCBORItem Item;
QCBORDecode_VGetNext(pMe, &Item);
QCBORDecode_Private_ProcessSimple(pMe, &Item, puSimple);
}
/* Public function, see qcbor/qcbor_spiffy_decode.h */
void
QCBORDecode_GetSimpleInMapN(QCBORDecodeContext *pMe,
int64_t nLabel,
uint8_t *puSimpleValue)
{
QCBORItem Item;
QCBORDecode_GetItemInMapN(pMe, nLabel, QCBOR_TYPE_ANY, &Item);
QCBORDecode_Private_ProcessSimple(pMe, &Item, puSimpleValue);
}
/* Public function, see qcbor/qcbor_spiffy_decode.h */
void
QCBORDecode_GetSimpleInMapSZ(QCBORDecodeContext *pMe,
const char *szLabel,
uint8_t *puSimpleValue)
{
QCBORItem Item;
QCBORDecode_GetItemInMapSZ(pMe, szLabel, QCBOR_TYPE_ANY, &Item);
QCBORDecode_Private_ProcessSimple(pMe, &Item, puSimpleValue);
}
// Improvement: add methods for wrapped CBOR, a simple alternate
// to EnterBstrWrapped