/*============================================================================== Copyright (c) 2016-2018, The Linux Foundation. Copyright (c) 2018-2019, Laurence Lundblade. All rights reserved. SPDX-License-Identifier: BSD-3-Clause 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. ==============================================================================*/ /*=================================================================================== FILE: qcbor_encode.c DESCRIPTION: This file contains the implementation of QCBOR. EDIT HISTORY FOR FILE: This section contains comments describing changes made to the module. Notice that changes are listed in reverse chronological order. when who what, where, why -------- ---- --------------------------------------------------- 12/30/18 llundblade Small efficient clever encode of type & argument. 11/29/18 llundblade Rework to simpler handling of tags and labels. 11/9/18 llundblade Error codes are now enums. 11/1/18 llundblade Floating support. 10/31/18 llundblade Switch to one license that is almost BSD-3. 09/28/18 llundblade Added bstr wrapping feature for COSE implementation. 02/05/18 llundbla Works on CPUs which require integer alignment. Requires new version of UsefulBuf. 07/05/17 llundbla Add bstr wrapping of maps/arrays for COSE 03/01/17 llundbla More data types 11/13/16 llundbla Integrate most TZ changes back into github version. 09/30/16 gkanike Porting to TZ. 03/15/16 llundbla Initial Version. =====================================================================================*/ #include "qcbor.h" #include "ieee754.h" /*...... This is a ruler that is 80 characters long...........................*/ /* CBOR's two nesting types, arrays and maps, are tracked here. There is a limit of QCBOR_MAX_ARRAY_NESTING to the number of arrays and maps that can be nested in one encoding so the encoding context stays small enough to fit on the stack. When an array / map is opened, pCurrentNesting points to the element in pArrays that records the type, start position and accumluates a count of the number of items added. When closed the start position is used to go back and fill in the type and number of items in the array / map. Encoded output be just items like ints and strings that are not part of any array / map. That is, the first thing encoded does not have to be an array or a map. */ inline static void Nesting_Init(QCBORTrackNesting *pNesting) { // assumes pNesting has been zeroed pNesting->pCurrentNesting = &pNesting->pArrays[0]; // Implied CBOR array at the top nesting level. This is never returned, // but makes the item count work correctly. pNesting->pCurrentNesting->uMajorType = CBOR_MAJOR_TYPE_ARRAY; } inline static QCBORError Nesting_Increase(QCBORTrackNesting *pNesting, uint8_t uMajorType, uint32_t uPos) { QCBORError nReturn = QCBOR_SUCCESS; if(pNesting->pCurrentNesting == &pNesting->pArrays[QCBOR_MAX_ARRAY_NESTING]) { // trying to open one too many nReturn = QCBOR_ERR_ARRAY_NESTING_TOO_DEEP; } else { pNesting->pCurrentNesting++; pNesting->pCurrentNesting->uCount = 0; pNesting->pCurrentNesting->uStart = uPos; pNesting->pCurrentNesting->uMajorType = uMajorType; } return nReturn; } inline static void Nesting_Decrease(QCBORTrackNesting *pNesting) { pNesting->pCurrentNesting--; } inline static QCBORError Nesting_Increment(QCBORTrackNesting *pNesting) { if(1 >= QCBOR_MAX_ITEMS_IN_ARRAY - pNesting->pCurrentNesting->uCount) { return QCBOR_ERR_ARRAY_TOO_LONG; } pNesting->pCurrentNesting->uCount += 1; return QCBOR_SUCCESS; } inline static uint16_t Nesting_GetCount(QCBORTrackNesting *pNesting) { // The nesting count recorded is always the actual number of individiual // data items in the array or map. For arrays CBOR uses the actual item // count. For maps, CBOR uses the number of pairs. This function returns // the number needed for the CBOR encoding, so it divides the number of // items by two for maps to get the number of pairs. This implementation // takes advantage of the map major type being one larger the array major // type, hence uDivisor is either 1 or 2. const uint16_t uDivisor = pNesting->pCurrentNesting->uMajorType - CBOR_MAJOR_TYPE_ARRAY+1; return pNesting->pCurrentNesting->uCount / uDivisor; } inline static uint32_t Nesting_GetStartPos(QCBORTrackNesting *pNesting) { return pNesting->pCurrentNesting->uStart; } inline static uint8_t Nesting_GetMajorType(QCBORTrackNesting *pNesting) { return pNesting->pCurrentNesting->uMajorType; } inline static int Nesting_IsInNest(QCBORTrackNesting *pNesting) { return pNesting->pCurrentNesting == &pNesting->pArrays[0] ? 0 : 1; } /* Error tracking plan -- Errors are tracked internally and not returned until Finish is called. The CBOR errors are in me->uError. UsefulOutBuf also tracks whether the buffer is full or not in its context. Once either of these errors is set they are never cleared. Only QCBOREncode_Init() resets them. Or said another way, they must never be cleared or we'll tell the caller all is good when it is not. Only one error code is reported by QCBOREncode_Finish() even if there are multiple errors. The last one set wins. The caller might have to fix one error to reveal the next one they have to fix. This is OK. The buffer full error tracked by UsefulBuf is only pulled out of UsefulBuf in Finish() so it is the one that usually wins. UsefulBuf will never go off the end of the buffer even if it is called again and again when full. It is really tempting to not check for overflow on the count in the number of items in an array. It would save a lot of code, it is extremely unlikely that any one will every put 65,000 items in an array, and the only bad thing that would happen is the CBOR would be bogus. Since this does not parse any input, you could in theory remove all error checks in this code if you knew the caller called it correctly. Maybe someday CDDL or some such language will be able to generate the code to call this and the calling code would always be correct. This could also automatically size some of the data structures like array/map nesting resulting in some stack memory savings. Errors returned here fall into two categories: Sizes QCBOR_ERR_BUFFER_TOO_LARGE -- Encoded output exceeded UINT32_MAX QCBOR_ERR_BUFFER_TOO_SMALL -- output buffer too small QCBOR_ERR_ARRAY_NESTING_TOO_DEEP -- Array/map nesting > QCBOR_MAX_ARRAY_NESTING1 QCBOR_ERR_ARRAY_TOO_LONG -- Too many things added to an array/map Nesting constructed incorrectly QCBOR_ERR_TOO_MANY_CLOSES -- more close calls than opens QCBOR_ERR_CLOSE_MISMATCH -- Type of close does not match open QCBOR_ERR_ARRAY_OR_MAP_STILL_OPEN -- Finish called without enough closes */ /* Public function for initialization. See header qcbor.h */ void QCBOREncode_Init(QCBOREncodeContext *me, UsefulBuf Storage) { memset(me, 0, sizeof(QCBOREncodeContext)); UsefulOutBuf_Init(&(me->OutBuf), Storage); Nesting_Init(&(me->nesting)); } /* All CBOR data items have a type and an "argument". The argument is either the value of the item for integer types, the length of the content for string, byte, array and map types, a tag for major type 6, and has several uses for major type 7. This function encodes the type and the argument. There are several encodings for the argument depending on how large it is and how it is used. Every encoding of the type and argument has at least one byte, the "initial byte". The top three bits of the initial byte are the major type for the CBOR data item. The eight major types defined by the standard are defined as CBOR_MAJOR_TYPE_xxxx in qcbor.h. The remaining five bits, known as "additional information", and possibly more bytes encode the argument. If the argument is less than 24, then it is encoded entirely in the five bits. This is neat because it allows you to encode an entire CBOR data item in 1 byte for many values and types (integers 0-23, true, false, and tags). If the argument is larger than 24, then it is encoded in 1,2,4 or 8 additional bytes, with the number of these bytes indicated by the values of the 5 bits 24, 25, 25 and 27. It is possible to encode a particular argument in many ways with this representation. This implementation always uses the smallest possible representation. This conforms with CBOR preferred encoding. This function inserts them into the output buffer at the specified position. AppendEncodedTypeAndNumber() appends to the end. This function takes care of converting to network byte order. This function is also used to insert floats and doubles. Before this function is called the float or double must be copied into a uint64_t. That is how they are passed in. They are then converted to network byte order correctly. The uMinLen param makes sure that even if all the digits of a half, float or double are 0 it is still correctly encoded in 2, 4 or 8 bytes. */ static void InsertEncodedTypeAndNumber(QCBOREncodeContext *me, uint8_t uMajorType, int nMinLen, uint64_t uNumber, size_t uPos) { /* This code does endian conversion without hton or knowing the endianness of the machine using masks and shifts. This avoids the dependency on hton and the mess of figuring out how to find the machine's endianness. This is a good efficient implementation on little-endian machines. A faster and small implementation is possible on big-endian machines because CBOR/network byte order is big endian. However big endian machines are uncommon. On x86, it is about 200 bytes instead of 500 bytes for the more formal unoptimized code. This also does the CBOR preferred shortest encoding for integers and is called to do endian conversion for floats. It works backwards from the LSB to the MSB as needed. Code Reviewers: THIS FUNCTION DOES POINTER MATH */ // Holds up to 9 bytes of type and argument // plus one extra so pointer always points to // valid bytes. uint8_t bytes[sizeof(uint64_t)+2]; // Point to the last bytes and work backwards uint8_t *pByte = &bytes[sizeof(bytes)-1]; // This is the 5 bits in the initial byte that is not the major type uint8_t uAdditionalInfo; if(uNumber < CBOR_TWENTY_FOUR && nMinLen == 0) { // Simple case where argument is < 24 uAdditionalInfo = uNumber; } else { /* Encode argument in 1,2,4 or 8 bytes. Outer loop runs once for 1 byte and 4 times for 8 bytes. Inner loop runs 1, 2 or 4 times depending on outer loop counter. This works backwards taking 8 bits off the argument being encoded at a time until all bits from uNumber have been encoded and the minimum encoding size is reached. Minimum encoding size is for floating point numbers with zero bytes. */ static const uint8_t aIterate[] = {1,1,2,4}; uint8_t i; for(i = 0; uNumber || nMinLen > 0; i++) { const uint8_t uIterations = aIterate[i]; for(int j = 0; j < uIterations; j++) { *--pByte = uNumber & 0xff; uNumber = uNumber >> 8; } nMinLen -= uIterations; } // Additional info is the encoding of the // number of additional bytes to encode // argument. uAdditionalInfo = LEN_IS_ONE_BYTE-1 + i; } *--pByte = (uMajorType << 5) + uAdditionalInfo; UsefulOutBuf_InsertData(&(me->OutBuf), pByte, &bytes[sizeof(bytes)-1] - pByte, uPos); } /* Append the type and number info to the end of the buffer. See InsertEncodedTypeAndNumber() function above for details */ inline static void AppendEncodedTypeAndNumber(QCBOREncodeContext *me, uint8_t uMajorType, uint64_t uNumber) { // An append is an insert at the end. InsertEncodedTypeAndNumber(me, uMajorType, 0, uNumber, UsefulOutBuf_GetEndPosition(&(me->OutBuf))); } /* Public functions for closing arrays and maps. See header qcbor.h */ void QCBOREncode_AddUInt64(QCBOREncodeContext *me, uint64_t uValue) { if(me->uError == QCBOR_SUCCESS) { AppendEncodedTypeAndNumber(me, CBOR_MAJOR_TYPE_POSITIVE_INT, uValue); me->uError = Nesting_Increment(&(me->nesting)); } } /* Public functions for closing arrays and maps. See header qcbor.h */ void QCBOREncode_AddInt64(QCBOREncodeContext *me, int64_t nNum) { if(me->uError == QCBOR_SUCCESS) { uint8_t uMajorType; uint64_t uValue; if(nNum < 0) { // In CBOR -1 encodes as 0x00 with major type negative int. uValue = (uint64_t)(-nNum - 1); uMajorType = CBOR_MAJOR_TYPE_NEGATIVE_INT; } else { uValue = (uint64_t)nNum; uMajorType = CBOR_MAJOR_TYPE_POSITIVE_INT; } AppendEncodedTypeAndNumber(me, uMajorType, uValue); me->uError = Nesting_Increment(&(me->nesting)); } } /* Semi-private function. It is exposed to user of the interface, but they will usually call one of the inline wrappers rather than this. See header qcbor.h Does the work of adding some bytes to the CBOR output. Works for a byte and text strings, which are the same in in CBOR though they have different major types. This is also used to insert raw pre-encoded CBOR. */ void QCBOREncode_AddBuffer(QCBOREncodeContext *me, uint8_t uMajorType, UsefulBufC Bytes) { if(me->uError == QCBOR_SUCCESS) { // If it is not Raw CBOR, add the type and the length if(uMajorType != CBOR_MAJOR_NONE_TYPE_RAW) { AppendEncodedTypeAndNumber(me, uMajorType, Bytes.len); } // Actually add the bytes UsefulOutBuf_AppendUsefulBuf(&(me->OutBuf), Bytes); // Update the array counting if there is any nesting at all me->uError = Nesting_Increment(&(me->nesting)); } } /* Public functions for closing arrays and maps. See header qcbor.h */ void QCBOREncode_AddTag(QCBOREncodeContext *me, uint64_t uTag) { AppendEncodedTypeAndNumber(me, CBOR_MAJOR_TYPE_OPTIONAL, uTag); } /* Semi-private function. It is exposed to user of the interface, but they will usually call one of the inline wrappers rather than this. See header qcbor.h */ void QCBOREncode_AddType7(QCBOREncodeContext *me, size_t uSize, uint64_t uNum) { if(me->uError == QCBOR_SUCCESS) { // This function call takes care of endian swapping for the float / double InsertEncodedTypeAndNumber(me, // The major type for floats and doubles CBOR_MAJOR_TYPE_SIMPLE, // size makes sure floats with zeros encode correctly (int)uSize, // Bytes of the floating point number as a uint uNum, // end position because this is append UsefulOutBuf_GetEndPosition(&(me->OutBuf))); me->uError = Nesting_Increment(&(me->nesting)); } } /* Public functions for closing arrays and maps. See header qcbor.h */ void QCBOREncode_AddDouble(QCBOREncodeContext *me, double dNum) { const IEEE754_union uNum = IEEE754_DoubleToSmallest(dNum); QCBOREncode_AddType7(me, uNum.uSize, uNum.uValue); } /* Semi-public function. It is exposed to user of the interface, but they will usually call one of the inline wrappers rather than this. See header qcbor.h */ void QCBOREncode_OpenMapOrArray(QCBOREncodeContext *me, uint8_t uMajorType) { // Add one item to the nesting level we are in for the new map or array me->uError = Nesting_Increment(&(me->nesting)); if(me->uError == QCBOR_SUCCESS) { // The offset where the length of an array or map will get written // is stored in a uint32_t, not a size_t to keep stack usage smaller. This // checks to be sure there is no wrap around when recording the offset. // Note that on 64-bit machines CBOR larger than 4GB can be encoded as long as no // array / map offsets occur past the 4GB mark, but the public interface // says that the maximum is 4GB to keep the discussion simpler. size_t uEndPosition = UsefulOutBuf_GetEndPosition(&(me->OutBuf)); // QCBOR_MAX_ARRAY_OFFSET is slightly less than UINT32_MAX so this // code can run on a 32-bit machine and tests can pass on a 32-bit // machine. If it was exactly UINT32_MAX, then this code would // not compile or run on a 32-bit machine and an #ifdef or some // machine size detection would be needed reducing portability. if(uEndPosition >= QCBOR_MAX_ARRAY_OFFSET) { me->uError = QCBOR_ERR_BUFFER_TOO_LARGE; } else { // Increase nesting level because this is a map or array. // Cast from size_t to uin32_t is safe because of check above me->uError = Nesting_Increase(&(me->nesting), uMajorType, (uint32_t)uEndPosition); } } } /* Public functions for closing arrays and maps. See header qcbor.h */ void QCBOREncode_CloseMapOrArray(QCBOREncodeContext *me, uint8_t uMajorType, UsefulBufC *pWrappedCBOR) { if(me->uError == QCBOR_SUCCESS) { if(!Nesting_IsInNest(&(me->nesting))) { me->uError = QCBOR_ERR_TOO_MANY_CLOSES; } else if(Nesting_GetMajorType(&(me->nesting)) != uMajorType) { me->uError = QCBOR_ERR_CLOSE_MISMATCH; } else { // When the array, map or bstr wrap was started, nothing was done // except note the position of the start of it. This code goes back // and inserts the actual CBOR array, map or bstr and its length. // That means all the data that is in the array, map or wrapped // needs to be slid to the right. This is done by UsefulOutBuf's // insert function that is called from inside // InsertEncodedTypeAndNumber() const size_t uInsertPosition = Nesting_GetStartPos(&(me->nesting)); const size_t uEndPosition = UsefulOutBuf_GetEndPosition(&(me->OutBuf)); // This can't go negative because the UsefulOutBuf always only grows // and never shrinks. UsefulOutBut itself also has defenses such that // it won't write were it should not even if given hostile input lengths const size_t uLenOfEncodedMapOrArray = uEndPosition - uInsertPosition; // Length is number of bytes for a bstr and number of items a for map & array const size_t uLength = uMajorType == CBOR_MAJOR_TYPE_BYTE_STRING ? uLenOfEncodedMapOrArray : Nesting_GetCount(&(me->nesting)); // Actually insert InsertEncodedTypeAndNumber(me, uMajorType, // major type bstr, array or map 0, // no minimum length for encoding uLength, // either len of bstr or num map / array items uInsertPosition); // position in out buffer // Return pointer and length to the enclosed encoded CBOR. The intended // use is for it to be hashed (e.g., SHA-256) in a COSE implementation. // This must be used right away, as the pointer and length go invalid // on any subsequent calls to this function because of the // InsertEncodedTypeAndNumber() call that slides data to the right. if(pWrappedCBOR) { const UsefulBufC PartialResult = UsefulOutBuf_OutUBuf(&(me->OutBuf)); const size_t uBstrLen = UsefulOutBuf_GetEndPosition(&(me->OutBuf)) - uEndPosition; *pWrappedCBOR = UsefulBuf_Tail(PartialResult, uInsertPosition+uBstrLen); } Nesting_Decrease(&(me->nesting)); } } } /* Public functions to finish and get the encoded result. See header qcbor.h */ QCBORError QCBOREncode_Finish(QCBOREncodeContext *me, UsefulBufC *pEncodedCBOR) { QCBORError uReturn = me->uError; if(uReturn != QCBOR_SUCCESS) { goto Done; } if (Nesting_IsInNest(&(me->nesting))) { uReturn = QCBOR_ERR_ARRAY_OR_MAP_STILL_OPEN; goto Done; } if(UsefulOutBuf_GetError(&(me->OutBuf))) { // Stuff didn't fit in the buffer. // This check catches this condition for all the appends and inserts // so checks aren't needed when the appends and inserts are performed. // And of course UsefulBuf will never overrun the input buffer given // to it. No complex analysis of the error handling in this file is // needed to know that is true. Just read the UsefulBuf code. uReturn = QCBOR_ERR_BUFFER_TOO_SMALL; goto Done; } *pEncodedCBOR = UsefulOutBuf_OutUBuf(&(me->OutBuf)); Done: return uReturn; } /* Public functions to finish and get the encoded result. See header qcbor.h */ QCBORError QCBOREncode_FinishGetSize(QCBOREncodeContext *me, size_t *puEncodedLen) { UsefulBufC Enc; QCBORError nReturn = QCBOREncode_Finish(me, &Enc); if(nReturn == QCBOR_SUCCESS) { *puEncodedLen = Enc.len; } return nReturn; } /* Notes on the code CBOR Major Type Public Function 0 QCBOREncode_AddUInt64 0, 1 QCBOREncode_AddUInt64, QCBOREncode_AddInt64 2, 3 QCBOREncode_AddBuffer, Also QCBOREncode_OpenMapOrArray 4, 5 QCBOREncode_OpenMapOrArray 6 QCBOREncode_AddTag 7 QCBOREncode_AddDouble, QCBOREncode_AddType7 Object code sizes on X86 with LLVM compiler and -Os (Dec 30, 2018) _QCBOREncode_Init 69 _QCBOREncode_AddUInt64 76 _QCBOREncode_AddInt64 87 _QCBOREncode_AddBuffer 113 _QCBOREncode_AddTag 27 _QCBOREncode_AddType7 87 _QCBOREncode_AddDouble 36 _QCBOREncode_OpenMapOrArray 103 _QCBOREncode_CloseMapOrArray 181 _InsertEncodedTypeAndNumber 190 _QCBOREncode_Finish 72 _QCBOREncode_FinishGetSize 70 Total is about 1.1KB _QCBOREncode_CloseMapOrArray is larger because it has a lot of nesting tracking to do and much of Nesting_ inlines into it. It probably can't be reduced much. If the error returned by Nesting_Increment() can be ignored because the limit is so high and the consequence of exceeding is proved to be inconsequential, then a lot of if(me->uError) instance can be removed, saving some code. */