| /* |
| * 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 ) ); |
| } |