blob: c03f1f20954b2e6961e1279a75614d46fc4e0a57 [file] [log] [blame]
/*
* FreeRTOS Common V1.1.1
* 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.
*
* http://aws.amazon.com/freertos
* http://www.FreeRTOS.org
*/
/**
* @file iot_mqtt_metrics.c
* @brief Source code for generating device metrics for AWS IOT.
* The generated metrics will be included within the username field of MQTT CONNECT message.
*/
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <stdio.h>
#include "iot_config.h"
#include "platform/iot_clock.h"
#include "iot_atomic.h"
/*
* @brief Total length of the hash in bytes.
*/
#define MD5_HASH_LENGTH_BYTES ( 16 )
/*
* Encode Length of one byte for the identifier.
*/
#define IDENTIFIER_BYTE_ENCODE_LENGTH ( 2 )
/*
* @brief Length in bytes of each chunk used for MD5 hash computation.
*/
#define MD5_CHUNK_LENGTH ( 64 )
/*
* @brief Number of bytes used for padding message length to input bytes.
*/
#define MD5_MSGLEN_PADDING_LENGTH ( 8 )
/*
* @brief The first byte of the bit padding ( Most significant bit should be 1 ).
*/
#define MD5_BIT_PADDING_FIRST_BYTE ( 0x80 )
/*
* @brief Total length of the input after padding.
*/
#define MD5_PADDED_LENGTH( length ) ( ( length + MD5_MSGLEN_PADDING_LENGTH + MD5_CHUNK_LENGTH ) & ~( MD5_CHUNK_LENGTH - 1 ) )
/*
* @brief Length of device identifier.
* Device identifier is represented as hex string of MD5 hash of the device certificate.
*/
#define AWS_IOT_DEVICE_IDENTIFIER_LENGTH ( MD5_HASH_LENGTH_BYTES * 2 )
/**
* @brief Device metrics name format
*/
#define AWS_IOT_METRICS_FORMAT "?SDK=" IOT_SDK_NAME "&Version=" IOT_SDK_VERSION "&Platform=" IOT_PLATFORM_NAME "&AFRDevID=%.*s"
/**
* @brief Length of #AWS_IOT_METRICS_FORMAT.
*/
#define AWS_IOT_METRICS_FORMAT_LENGTH ( ( uint16_t ) ( sizeof( AWS_IOT_METRICS_FORMAT ) + AWS_IOT_DEVICE_IDENTIFIER_LENGTH ) )
/*
* @brief Macro for left rotatation at each round of MD5 hash operation.
*/
#define LEFT_ROTATE( x, c ) ( ( x << c ) | ( x >> ( 32 - c ) ) )
/*
* @brief Generates 128-bit MD5 hash from the input data.
* The hash algorithm is adapted from wikipedia and does not dependent on any third party libraries.
*
* @param[in] pData Data on which hash needs to be calculated.
* @param[in] dataLength Length in bytes of the input data
* @param[in] pHash Memory location where the hash value is stored.
* @param[out] hashLength Total size of the memory location for hash storage.
*/
void Utils_generateMD5Hash( const char * pData,
size_t dataLength,
uint8_t * pHash,
size_t hashLength );
/*
* @brief Generates a unique identifier string from the input data.
* Uses MD5 hash algorithm to generate a 128-bit unique identifier and
* encodes as a hexadecimal string.
*/
static void _generateUniqueIdentifier( const char * pInput,
size_t length,
char * pIdentifier,
size_t identifierLength );
/*
* @brief generates device metrics from the device identifier
*/
static void _generateDeviceMetrics( const char * pDeviceIdentifier,
size_t deviceIdentifierLength,
char * pDeviceMetrics,
size_t deviceMetricsLength );
/*
* @brief Spinlock lock implementation using atomic compare and swap.
*/
static void _atomicSpinlock_lock( uint32_t * lock );
/*
* @brief Spinlock unlock implementation using atomic compare and swap.
*/
static void _atomicSpinlock_unlock( uint32_t * lock );
/**
* @brief Metrics for the device.
*/
static char deviceMetrics[ AWS_IOT_METRICS_FORMAT_LENGTH + 1 ] = { 0 };
/**
* @brief Unique identifier for the device.
*/
static char deviceIdentifier[ AWS_IOT_DEVICE_IDENTIFIER_LENGTH + 1 ] = { 0 };
/*
* @brief Constants used for left rotation.
*/
static const unsigned char S[ 64 ] =
{
7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21
};
/*
* @brief Constants representing the binary integer part of the sines of integers from 0 to 63.
*/
static const uint32_t K[ 64 ] =
{
0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee,
0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501,
0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be,
0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,
0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa,
0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed,
0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,
0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c,
0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,
0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05,
0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039,
0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,
0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1,
0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391
};
void Utils_generateMD5Hash( const char * pData,
size_t dataLength,
uint8_t * pHash,
size_t hashLength )
{
uint8_t chunk[ MD5_CHUNK_LENGTH ] = { 0 };
uint32_t * pOutput = ( uint32_t * ) pHash;
const uint32_t * pCurrent = NULL;
size_t paddingOffset, bytesConsumed = 0;
size_t paddingLength, totalLength = MD5_PADDED_LENGTH( dataLength );
uint64_t numBits = ( dataLength * 8 );
bool isPadding = false;
int i;
uint32_t A, B, C, D, F, G;
/*Initialize variables */
pOutput[ 0 ] = 0x67452301;
pOutput[ 1 ] = 0xefcdab89;
pOutput[ 2 ] = 0x98badcfe;
pOutput[ 3 ] = 0x10325476;
configASSERT( hashLength >= MD5_HASH_LENGTH_BYTES );
while( bytesConsumed < totalLength )
{
if( ( bytesConsumed + MD5_CHUNK_LENGTH ) <= dataLength )
{
pCurrent = ( uint32_t * ) pData;
pData += MD5_CHUNK_LENGTH;
bytesConsumed += MD5_CHUNK_LENGTH;
}
else
{
if( bytesConsumed < dataLength )
{
memcpy( chunk, pData, ( dataLength - bytesConsumed ) );
paddingOffset = ( dataLength - bytesConsumed );
}
else
{
paddingOffset = 0;
}
paddingLength = ( MD5_CHUNK_LENGTH - paddingOffset );
if( ( paddingLength >= 1 ) && ( isPadding == false ) )
{
chunk[ paddingOffset++ ] = MD5_BIT_PADDING_FIRST_BYTE;
paddingLength--;
isPadding = true;
}
if( paddingLength > MD5_MSGLEN_PADDING_LENGTH )
{
memset( &chunk[ paddingOffset ], 0x00, ( paddingLength - MD5_MSGLEN_PADDING_LENGTH ) );
paddingOffset += ( paddingLength - MD5_MSGLEN_PADDING_LENGTH );
paddingLength = MD5_MSGLEN_PADDING_LENGTH;
}
if( paddingLength == MD5_MSGLEN_PADDING_LENGTH )
{
chunk[ paddingOffset++ ] = ( numBits & 0xFF );
chunk[ paddingOffset++ ] = ( ( numBits >> 8 ) & 0xFF );
chunk[ paddingOffset++ ] = ( ( numBits >> 16 ) & 0xFF );
chunk[ paddingOffset++ ] = ( ( numBits >> 24 ) & 0xFF );
chunk[ paddingOffset++ ] = ( ( numBits >> 32 ) & 0xFF );
chunk[ paddingOffset++ ] = ( ( numBits >> 40 ) & 0xFF );
chunk[ paddingOffset++ ] = ( ( numBits >> 48 ) & 0xFF );
chunk[ paddingOffset++ ] = ( ( numBits >> 56 ) & 0xFF );
}
pCurrent = ( uint32_t * ) chunk;
bytesConsumed += MD5_CHUNK_LENGTH;
}
A = pOutput[ 0 ];
B = pOutput[ 1 ];
C = pOutput[ 2 ];
D = pOutput[ 3 ];
for( i = 0; i < 64; i++ )
{
if( i < 16 )
{
F = ( B & C ) | ( ( ~B ) & D );
G = i;
}
else if( i < 32 )
{
F = ( D & B ) | ( ( ~D ) & C );
G = ( ( 5 * i ) + 1 ) % 16;
}
else if( i < 48 )
{
F = ( B ^ C ) ^ D;
G = ( ( 3 * i ) + 5 ) % 16;
}
else
{
F = C ^ ( B | ( ~D ) );
G = ( 7 * i ) % 16;
}
F = F + A + K[ i ] + pCurrent[ G ];
A = D;
D = C;
C = B;
B = B + LEFT_ROTATE( F, S[ i ] );
}
pOutput[ 0 ] += A;
pOutput[ 1 ] += B;
pOutput[ 2 ] += C;
pOutput[ 3 ] += D;
}
}
static void _generateDeviceMetrics( const char * pDeviceIdentifier,
size_t deviceIdentifierLength,
char * pDeviceMetrics,
size_t deviceMetricsLength )
{
size_t length;
length = snprintf( pDeviceMetrics,
deviceMetricsLength,
AWS_IOT_METRICS_FORMAT,
deviceIdentifierLength,
pDeviceIdentifier );
configASSERT( length > 0 );
}
static void _atomicSpinlock_lock( uint32_t * lock )
{
uint32_t current = 0;
uint32_t store = 1;
for( ; ; )
{
if( Atomic_CompareAndSwap_u32( lock, store, current ) == 1 )
{
break;
}
/* Does context switch with negligible delay. */
IotClock_SleepMs( 1 );
}
}
static void _atomicSpinlock_unlock( uint32_t * lock )
{
uint32_t current = 1;
uint32_t store = 0;
for( ; ; )
{
if( Atomic_CompareAndSwap_u32( lock, store, current ) == 1 )
{
break;
}
/* Does context switch with negligible delay. */
IotClock_SleepMs( 1 );
}
}
static void _generateUniqueIdentifier( const char * pInput,
size_t length,
char * pIdentifier,
size_t identifierLength )
{
uint8_t hash[ MD5_HASH_LENGTH_BYTES ] = { 0 };
int i;
size_t ret;
configASSERT( identifierLength >= ( 2 * MD5_HASH_LENGTH_BYTES ) );
Utils_generateMD5Hash( pInput, length, hash, MD5_HASH_LENGTH_BYTES );
for( i = 0; i < MD5_HASH_LENGTH_BYTES; i++ )
{
ret = snprintf( pIdentifier, ( IDENTIFIER_BYTE_ENCODE_LENGTH + 1 ), "%02X", hash[ i ] );
configASSERT( ret > 0 );
pIdentifier += IDENTIFIER_BYTE_ENCODE_LENGTH;
}
}
/*
* @brief Retrieves the device identifier.
*/
const char * getDeviceIdentifier( void )
{
const char * pCertificate = IOT_DEVICE_CERTIFICATE;
static uint32_t lock = 0;
if( deviceIdentifier[ 0 ] == '\0' )
{
_atomicSpinlock_lock( &lock );
if( deviceIdentifier[ 0 ] == '\0' )
{
if( ( pCertificate != NULL ) &&
( strcmp( pCertificate, "" ) != 0 ) )
{
_generateUniqueIdentifier( pCertificate, strlen( pCertificate ), deviceIdentifier, sizeof( deviceIdentifier ) );
}
else
{
strncpy( deviceIdentifier, "Unknown", sizeof( deviceIdentifier ) );
}
}
_atomicSpinlock_unlock( &lock );
}
return deviceIdentifier;
}
/*
* @brief Retrieves the device metrics.
*/
const char * getDeviceMetrics( void )
{
const char * pDeviceIdentifier = NULL;
static uint32_t lock = 0;
if( deviceMetrics[ 0 ] == '\0' )
{
_atomicSpinlock_lock( &lock );
if( deviceMetrics[ 0 ] == '\0' )
{
pDeviceIdentifier = getDeviceIdentifier();
_generateDeviceMetrics( pDeviceIdentifier, strlen( pDeviceIdentifier ), deviceMetrics, sizeof( deviceMetrics ) );
}
_atomicSpinlock_unlock( &lock );
}
return deviceMetrics;
}
/*
* @brief Retrieves the device metrics length.
*/
uint16_t getDeviceMetricsLength( void )
{
const char * pDeviceMetrics = getDeviceMetrics();
return ( uint16_t ) ( strlen( pDeviceMetrics ) );
}