2021-11-30 14:51:24 +01:00

517 lines
20 KiB
C

/*
* AWS IoT Over-the-air Update v3.2.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 <assert.h>
/**
* @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;
}
/*-----------------------------------------------------------*/