/* * AWS IoT Over-the-air Update v3.3.0 * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in * the Software without restriction, including without limitation the rights to * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of * the Software, and to permit persons to whom the Software is furnished to do so, * subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * @file ota_base64.c * @brief Implements a Base64 decoding routine. */ #include "ota_base64_private.h" #include /** * @brief Number to represent both line feed and carriage return symbols in the * pBase64SymbolToIndexMap table. */ #define NEWLINE 64U /** * @brief Number to represent the whitespace character in the pBase64SymbolToIndexMap table. */ #define WHITESPACE 65U /** * @brief Number to represent the Base64 padding symbol in the pBase64SymbolToIndexMap table. */ #define PADDING_SYMBOL 66U /** * @brief Number to represent values that are invalid in the pBase64SymbolToIndexMap table. */ #define NON_BASE64_INDEX 67U /** * @brief Maximum value for a Base64 index that represents a valid, non-formatting Base64 symbol. */ #define VALID_BASE64_SYMBOL_INDEX_RANGE_MAX 63U /** * @brief Number of bits in a sextet. */ #define SEXTET_SIZE 6 /** * @brief Maximum number of Base64 symbols to store in a buffer before decoding them. */ #define MAX_NUM_BASE64_DATA 4U /** * @brief Maximum number of padding symbols in a string of encoded data that is considered valid. */ #define MAX_EXPECTED_NUM_PADDING 2 /** * @brief Smallest amount of data that can be Base64 encoded is a byte. Encoding a single byte of * data results in 2 bytes of encoded data. Therefore if the encoded data is smaller than 2 * bytes, there is an error with the data. */ #define MIN_VALID_ENCODED_DATA_SIZE 2U /** * @brief The number of bits in a single octet. */ #define SIZE_OF_ONE_OCTET 8U /** * @brief The number of bits in two octets. */ #define SIZE_OF_TWO_OCTETS 16U /** * @brief The number of padding bits that are present when there are two sextets of encoded data. */ #define SIZE_OF_PADDING_WITH_TWO_SEXTETS 4 /** * @brief The number of padding bits that are present when there are three sextets of encoded data. */ #define SIZE_OF_PADDING_WITH_THREE_SEXTETS 2 /** * @brief Inclusive upper bound for valid values that can be contained in pBase64SymbolToIndexMap. */ #define SYMBOL_TO_INDEX_MAP_VALUE_UPPER_BOUND 67U /** * @brief Inclusive upper bound for the range of valid Base64 index values. */ #define BASE64_INDEX_VALUE_UPPER_BOUND 63U /** * @brief This table takes is indexed by an Ascii character and returns the respective Base64 index. * The Ascii character used to index into this table is assumed to represent a symbol in a * string of Base64 encoded data. There are three kinds of possible ascii characters: * 1) Base64 Symbols. These are the digits of a Base 64 number system. * 2) Formatting characters. These are newline, whitespace, and padding. * 3) Symbols that are impossible to have inside of correctly Base64 encoded data. * * This table assumes that the padding symbol is the Ascii character '=' * * Valid Base64 symbols will have an index ranging from 0-63. The Base64 digits being used * are "ABCDEFGHIJKLMNOPQRSTUVWXYabcdefghijklmnopqrstuvwxyz0123456789+/" where 'A' is the * 0th index of the Base64 symbols and '/' is the 63rd index. * * Outside of the numbers 0-63, there are magic numbers in this table: * - The 11th entry in this table has the number 64. This is to identify the ascii character * '\n' as a newline character. * - The 14th entry in this table has the number 64. This is to identify the ascii character * '\\r' as a newline character. * - The 33rd entry in this table has the number 65. This is to identify the ascii character * ' ' as a whitespace character. * - The 62nd entry in this table has the number 66. This is to identify the ascii character * '=' as the padding character. * - All positions in the ascii table that are invalid symbols are identified with the * number 67 (other than '\n','\\r',' ','='). */ static const uint8_t pBase64SymbolToIndexMap[] = { 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 64, 67, 67, 64, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 65, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 62, 67, 67, 67, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 67, 67, 67, 66, 67, 67, 67, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 67, 67, 67, 67, 67, 67, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67 }; /** * @brief Validates the input Base64 index based on the context of what * has been decoded so far and the value of the index. Updates * the input counters that are used to keep track of the number * of whitespace and padding symbols that have been parsed so * far. * * @param[in] base64Index Base64 index that can have on of the values * listed in pBase64SymbolToIndexMap. This index represents the * value of a valid Base64 symbol, a number to identify it as a * formatting symbol, or a number to identify it as an invalid * symbol. * @param[in,out] pNumPadding Pointer to the number of padding symbols that are * present before the input Base64 index in the encoded data. * This number is incremented if the input symbol is a padding * symbol. * @param[in,out] pNumWhitespace Pointer to the number of whitespace symbols * that are present before the input Base64 index in the encoded * data. This number is incremented if the input symbol is a * whitespace symbol. * * @return One of the following: * - #Base64Success if the Base64 encoded data was valid * and successfully decoded. * - An error code defined in ota_base64_private.h if the * encoded data or input parameters are invalid. */ static Base64Status_t preprocessBase64Index( uint8_t base64Index, int64_t * pNumPadding, int64_t * pNumWhitespace ) { Base64Status_t returnVal = Base64Success; int64_t numPadding; int64_t numWhitespace; assert( pNumPadding != NULL ); assert( pNumWhitespace != NULL ); numPadding = *pNumPadding; numWhitespace = *pNumWhitespace; /* Validate that the Base64 index is valid and in an appropriate place. */ if( base64Index == NON_BASE64_INDEX ) { returnVal = Base64InvalidSymbol; } else if( base64Index == PADDING_SYMBOL ) { if( numWhitespace != 0 ) { returnVal = Base64InvalidSymbolOrdering; } else if( ++numPadding > MAX_EXPECTED_NUM_PADDING ) { returnVal = Base64InvalidPaddingSymbol; } else { /* No action required. */ } } else if( base64Index == WHITESPACE ) { ++numWhitespace; } else if( base64Index == NEWLINE ) { /* No action required. */ } /* In this case, the input is valid because the value of its index is inclusively between 0 * and 63. Check that there was not a whitespace or padding symbol before this valid index. */ else { assert( base64Index <= BASE64_INDEX_VALUE_UPPER_BOUND ); if( ( numWhitespace != 0 ) || ( numPadding != 0 ) ) { returnVal = Base64InvalidSymbolOrdering; } } *pNumWhitespace = numWhitespace; *pNumPadding = numPadding; return returnVal; } /** * @brief Add a Base64 index to a Base64 index buffer. The buffer will * only be updated if the index represents a Base64 digit. * * @param[in] base64Index Base64 index that can have one of the values * listed in pBase64SymbolToIndexMap. * @param[in,out] pBase64IndexBuffer Pointer to a 32 bit variable that contains * Base64 indexes that will be decoded. * @param[in,out] pNumDataInBuffer Pointer to the number of sextets that are * stored in pBase64IndexBuffer. This will be incremented if * base64Index is stored in pBase64IndexBuffer. */ static void updateBase64DecodingBuffer( const uint8_t base64Index, uint32_t * pBase64IndexBuffer, uint32_t * pNumDataInBuffer ) { uint32_t base64IndexBuffer; uint32_t numDataInBuffer; assert( pBase64IndexBuffer != NULL ); assert( pNumDataInBuffer != NULL ); assert( base64Index <= SYMBOL_TO_INDEX_MAP_VALUE_UPPER_BOUND ); base64IndexBuffer = *pBase64IndexBuffer; numDataInBuffer = *pNumDataInBuffer; /* Only update the buffer if the Base64 index is representing a Base64 digit. Base64 digits * have a Base64 index that is inclusively between 0 and 63. */ if( base64Index <= VALID_BASE64_SYMBOL_INDEX_RANGE_MAX ) { /* Shift the previously stored data over to make room for the next Base64 sextet and * store the current Base64 index that is represented by the 6 least significant bits. * Six is the number of bits you need to represent a character in Base64 (log2(64) = 6). * The remaining two most significant bits will always be 0 since the only valid range of * input data is between 0 and 63. */ base64IndexBuffer = ( base64IndexBuffer << SEXTET_SIZE ) | base64Index; ++numDataInBuffer; } *pBase64IndexBuffer = base64IndexBuffer; *pNumDataInBuffer = numDataInBuffer; } /** * @brief Decode a buffer containing two, three, or four Base64 * indices. * * @param[in,out] pBase64IndexBuffer Pointer to a 32 bit variable that contains * Base64 indexes that will be decoded. Each index is * represented by a sextet in the buffer. * @param[in,out] pNumDataInBuffer Pointer to the number of sextets (indexes) * that are concatenated and stored in pBase64IndexBuffer. This * will be set to zero before this function returns. * @param[out] pDest Pointer to a buffer used for storing the decoded data. * @param[in] destLen Length of the pDest buffer. * @param[in,out] pOutputLen Pointer to the index of pDest where the output * should be written. * * @return One of the following: * - #Base64Success if the Base64 encoded data was valid * and successfully decoded. * - An error code defined in ota_base64_private.h if the * encoded data or input parameters are invalid. */ static Base64Status_t decodeBase64IndexBuffer( uint32_t * pBase64IndexBuffer, uint32_t * pNumDataInBuffer, uint8_t * pDest, const size_t destLen, size_t * pOutputLen ) { Base64Status_t returnVal = Base64Success; size_t outputLen; uint32_t base64IndexBuffer; uint32_t numDataInBuffer; uint32_t numDataToWrite; assert( pBase64IndexBuffer != NULL ); assert( pNumDataInBuffer != NULL ); assert( ( *pNumDataInBuffer == 2U ) || ( *pNumDataInBuffer == 3U ) || ( *pNumDataInBuffer == 4U ) ); assert( pDest != NULL ); assert( pOutputLen != NULL ); outputLen = *pOutputLen; base64IndexBuffer = *pBase64IndexBuffer; numDataInBuffer = *pNumDataInBuffer; numDataToWrite = ( numDataInBuffer * 3U ) / 4U; if( destLen < ( outputLen + numDataToWrite ) ) { returnVal = Base64InvalidBufferSize; } else { /* If the buffer is full, convert the 4 sextets of encoded data into * three sequential octets of decoded data starting from the most * significant bits and ending at the least significant bits. */ if( numDataInBuffer == MAX_NUM_BASE64_DATA ) { pDest[ outputLen ] = ( uint8_t ) ( ( base64IndexBuffer >> SIZE_OF_TWO_OCTETS ) & 0xFFU ); pDest[ outputLen + 1U ] = ( uint8_t ) ( ( base64IndexBuffer >> SIZE_OF_ONE_OCTET ) & 0xFFU ); pDest[ outputLen + 2U ] = ( uint8_t ) ( base64IndexBuffer & 0xFFU ); outputLen += 3U; } if( numDataInBuffer == 3U ) { /* When there are only three sextets of data remaining at the end of the encoded data, * it is assumed that these three sextets should be decoded into two octets of data. In * this case, the two least significant bits are ignored and the following sixteen * least significant bits are converted into two octets of data. */ if( ( base64IndexBuffer & 0x3U ) != 0U ) { returnVal = Base64NonZeroPadding; } if( returnVal == Base64Success ) { base64IndexBuffer = base64IndexBuffer >> SIZE_OF_PADDING_WITH_THREE_SEXTETS; pDest[ outputLen ] = ( uint8_t ) ( ( base64IndexBuffer >> SIZE_OF_ONE_OCTET ) & 0xFFU ); pDest[ outputLen + 1U ] = ( uint8_t ) ( base64IndexBuffer & 0xFFU ); outputLen += 2U; } } if( numDataInBuffer == 2U ) { /* When there are only two sextets of data remaining at the end of the encoded data, it * is assumed that these two sextets should be decoded into one octet of data. In this * case, the four least significant bits are ignored and the following eight least * significant bits are converted into one octet of data. */ if( ( base64IndexBuffer & 0xFU ) != 0U ) { returnVal = Base64NonZeroPadding; } if( returnVal == Base64Success ) { base64IndexBuffer = base64IndexBuffer >> SIZE_OF_PADDING_WITH_TWO_SEXTETS; pDest[ outputLen ] = ( uint8_t ) ( base64IndexBuffer & 0xFFU ); outputLen += 1U; } } } *pNumDataInBuffer = 0; *pOutputLen = outputLen; *pBase64IndexBuffer = 0; return returnVal; } /** * @brief Decode Base64 encoded data. * * @param[out] pDest Pointer to a buffer for storing the decoded result. * @param[in] destLen Length of the pDest buffer. * @param[out] pResultLen Pointer to the length of the decoded result. * @param[in] pEncodedData Pointer to a buffer containing the Base64 encoded * data that is intended to be decoded. * @param[in] encodedLen Length of the pEncodedData buffer. * * @return One of the following: * - #Base64Success if the Base64 encoded data was valid * and successfully decoded. * - An error code defined in ota_base64_private.h if the * encoded data or input parameters are invalid. */ Base64Status_t base64Decode( uint8_t * pDest, const size_t destLen, size_t * pResultLen, const uint8_t * pEncodedData, const size_t encodedLen ) { uint32_t base64IndexBuffer = 0; uint32_t numDataInBuffer = 0; const uint8_t * pCurrBase64Symbol = pEncodedData; size_t outputLen = 0; int64_t numPadding = 0; int64_t numWhitespace = 0; Base64Status_t returnVal = Base64Success; if( ( pEncodedData == NULL ) || ( pDest == NULL ) || ( pResultLen == NULL ) ) { returnVal = Base64NullPointerInput; } if( encodedLen < MIN_VALID_ENCODED_DATA_SIZE ) { returnVal = Base64InvalidInputSize; } /* This loop will decode the first (encodedLen - (encodedLen % 4)) amount of data. */ while( ( returnVal == Base64Success ) && ( pCurrBase64Symbol < ( pEncodedData + encodedLen ) ) ) { uint8_t base64Index = 0; /* Read in the next Ascii character that represents the current Base64 symbol. */ uint8_t base64AsciiSymbol = *pCurrBase64Symbol++; /* Get the Base64 index that represents the Base64 symbol. */ base64Index = pBase64SymbolToIndexMap[ base64AsciiSymbol ]; /* Validate the input and update counters for padding and whitespace. */ returnVal = preprocessBase64Index( base64Index, &numPadding, &numWhitespace ); if( returnVal != Base64Success ) { break; } /* Add the current Base64 index to a buffer. */ updateBase64DecodingBuffer( base64Index, &base64IndexBuffer, &numDataInBuffer ); /* Decode the buffer when it's full and store the result. */ if( numDataInBuffer == MAX_NUM_BASE64_DATA ) { returnVal = decodeBase64IndexBuffer( &base64IndexBuffer, &numDataInBuffer, pDest, destLen, &outputLen ); } } if( returnVal == Base64Success ) { /* This scenario is only possible when the number of encoded symbols ( excluding newlines * and padding ) being decoded mod four is equal to one. There is no valid scenario where * data can be encoded to create a result of this size. Therefore if this size is * encountered, it's assumed that the incoming Base64 data is not encoded correctly. */ if( numDataInBuffer == 1U ) { returnVal = Base64InvalidInputSize; } } if( returnVal == Base64Success ) { /* Handle the scenarios where there is padding at the end of the encoded data. * * Note: This implementation assumes that non-zero padding bits are an error. This prevents * having multiple non-matching encoded data strings map to identical decoded strings. */ if( ( numDataInBuffer == 2U ) || ( numDataInBuffer == 3U ) ) { returnVal = decodeBase64IndexBuffer( &base64IndexBuffer, &numDataInBuffer, pDest, destLen, &outputLen ); } } if( returnVal == Base64Success ) { *pResultLen = outputLen; } return returnVal; } /*-----------------------------------------------------------*/