/* * AWS IoT Device Defender Client v1.1.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 defender.c * @brief Implementation of the AWS IoT Device Defender Client Library. */ /* Standard includes. */ #include #include #include /* Defender API include. */ #include "defender.h" /** * @brief Get the topic length for a given defender API. * * @param[in] thingNameLength The length of the thing name as registered with AWS IoT. * @param[in] api The defender API value. * * @return The topic length for the given defender API. */ static uint16_t getTopicLength( uint16_t thingNameLength, DefenderTopic_t api ); /** * @brief Write the format and suffix part for the given defender API to the buffer. * * Format: json or cbor. * Suffix: /accepted or /rejected or empty. * * @param[in] pBuffer The buffer to write the format and suffix part into. * @param[in] api The defender API value. * * @note This function assumes that the buffer is large enough to hold the * value. */ static void writeFormatAndSuffix( char * pBuffer, DefenderTopic_t api ); /** * @brief Check if the unparsed topic so far starts with the defender prefix. * * The defender prefix is "$aws/things/". * * @param[in] pRemainingTopic Starting location of the unparsed topic. * @param[in] remainingTopicLength The length of the unparsed topic. * * @return #DefenderSuccess if the unparsed topic starts with the defender * prefix; #DefenderNoMatch otherwise. */ static DefenderStatus_t matchPrefix( const char * pRemainingTopic, uint16_t remainingTopicLength ); /** * @brief Extract the length of thing name in the unparsed topic so far. * * The end of thing name is marked by a forward slash. A zero length thing name * is not valid. * * This function extracts the same thing name from the following topic strings: * - $aws/things/THING_NAME/defender/metrics/json * - $aws/things/THING_NAME * The second topic is not a valid defender topic and the matching will fail * when we try to match the bridge part. * * @param[in] pRemainingTopic Starting location of the unparsed topic. * @param[in] remainingTopicLength The length of the unparsed topic. * @param[out] pOutThingNameLength The length of the thing name in the topic string. * * @return #DefenderSuccess if a valid thing name is found; #DefenderNoMatch * otherwise. */ static DefenderStatus_t extractThingNameLength( const char * pRemainingTopic, uint16_t remainingTopicLength, uint16_t * pOutThingNameLength ); /** * @brief Check if the unparsed topic so far starts with the defender bridge. * * The defender bridge is "/defender/metrics/". * * @param[in] pRemainingTopic Starting location of the unparsed topic. * @param[in] remainingTopicLength The length of the unparsed topic. * * @return #DefenderSuccess if the unparsed topic starts with the defender * bridge; #DefenderNoMatch otherwise. */ static DefenderStatus_t matchBridge( const char * pRemainingTopic, uint16_t remainingTopicLength ); /** * @brief Check if the unparsed topic so far exactly matches one of the defender * api topics. * * The defender api topics are the following: * - json * - json/accepted * - json/rejected * - cbor * - cbor/accepted * - cbor/rejected * * The function also outputs the defender API value if a match is found. * * @param[in] pRemainingTopic Starting location of the unparsed topic. * @param[in] remainingTopicLength The length of the unparsed topic. * @param[out] pOutApi The defender topic API value. * * @return #DefenderSuccess if the unparsed topic exactly matches one of the * defender api topics; #DefenderNoMatch otherwise. */ static DefenderStatus_t matchApi( const char * pRemainingTopic, uint16_t remainingTopicLength, DefenderTopic_t * pOutApi ); /*-----------------------------------------------------------*/ static uint16_t getTopicLength( uint16_t thingNameLength, DefenderTopic_t api ) { uint16_t topicLength = 0U; assert( ( thingNameLength != 0U ) && ( thingNameLength <= DEFENDER_THINGNAME_MAX_LENGTH ) ); assert( ( api > DefenderInvalidTopic ) && ( api < DefenderMaxTopic ) ); switch( api ) { case DefenderJsonReportPublish: topicLength = DEFENDER_API_LENGTH_JSON_PUBLISH( thingNameLength ); break; case DefenderJsonReportAccepted: topicLength = DEFENDER_API_LENGTH_JSON_ACCEPTED( thingNameLength ); break; case DefenderJsonReportRejected: topicLength = DEFENDER_API_LENGTH_JSON_REJECTED( thingNameLength ); break; case DefenderCborReportPublish: topicLength = DEFENDER_API_LENGTH_CBOR_PUBLISH( thingNameLength ); break; case DefenderCborReportAccepted: topicLength = DEFENDER_API_LENGTH_CBOR_ACCEPTED( thingNameLength ); break; /* The default is here just to silence compiler warnings in a way which * does not bring coverage down. The assert at the beginning of this * function ensures that the only api value hitting this case can be * DefenderCborReportRejected. */ case DefenderCborReportRejected: default: topicLength = DEFENDER_API_LENGTH_CBOR_REJECTED( thingNameLength ); break; } return topicLength; } /*-----------------------------------------------------------*/ static void writeFormatAndSuffix( char * pBuffer, DefenderTopic_t api ) { /* The following variables are to address MISRA Rule 7.4 violation of * passing const char * for const void * param of memcpy. */ const char * pDefenderApiJsonFormat = DEFENDER_API_JSON_FORMAT; const char * pDefenderApiCborFormat = DEFENDER_API_CBOR_FORMAT; const char * pDefenderApiAcceptedSuffix = DEFENDER_API_ACCEPTED_SUFFIX; const char * pDefenderApiRejectedSuffix = DEFENDER_API_REJECTED_SUFFIX; assert( pBuffer != NULL ); assert( ( api > DefenderInvalidTopic ) && ( api < DefenderMaxTopic ) ); switch( api ) { case DefenderJsonReportPublish: ( void ) memcpy( ( void * ) pBuffer, ( const void * ) pDefenderApiJsonFormat, ( size_t ) DEFENDER_API_LENGTH_JSON_FORMAT ); break; case DefenderJsonReportAccepted: ( void ) memcpy( ( void * ) pBuffer, ( const void * ) pDefenderApiJsonFormat, ( size_t ) DEFENDER_API_LENGTH_JSON_FORMAT ); ( void ) memcpy( ( void * ) &( pBuffer[ DEFENDER_API_LENGTH_JSON_FORMAT ] ), ( const void * ) pDefenderApiAcceptedSuffix, ( size_t ) DEFENDER_API_LENGTH_ACCEPTED_SUFFIX ); break; case DefenderJsonReportRejected: ( void ) memcpy( ( void * ) pBuffer, ( const void * ) pDefenderApiJsonFormat, DEFENDER_API_LENGTH_JSON_FORMAT ); ( void ) memcpy( ( void * ) &( pBuffer[ DEFENDER_API_LENGTH_JSON_FORMAT ] ), ( const void * ) pDefenderApiRejectedSuffix, ( size_t ) DEFENDER_API_LENGTH_REJECTED_SUFFIX ); break; case DefenderCborReportPublish: ( void ) memcpy( ( void * ) pBuffer, ( const void * ) pDefenderApiCborFormat, ( size_t ) DEFENDER_API_LENGTH_CBOR_FORMAT ); break; case DefenderCborReportAccepted: ( void ) memcpy( ( void * ) pBuffer, ( const void * ) pDefenderApiCborFormat, DEFENDER_API_LENGTH_CBOR_FORMAT ); ( void ) memcpy( ( void * ) &( pBuffer[ DEFENDER_API_LENGTH_CBOR_FORMAT ] ), ( const void * ) pDefenderApiAcceptedSuffix, ( size_t ) DEFENDER_API_LENGTH_ACCEPTED_SUFFIX ); break; /* The default is here just to silence compiler warnings in a way which * does not bring coverage down. The assert at the beginning of this * function ensures that the only api value hitting this case can be * DefenderCborReportRejected. */ case DefenderCborReportRejected: default: ( void ) memcpy( ( void * ) pBuffer, ( const void * ) pDefenderApiCborFormat, ( size_t ) DEFENDER_API_LENGTH_CBOR_FORMAT ); ( void ) memcpy( ( void * ) &( pBuffer[ DEFENDER_API_LENGTH_CBOR_FORMAT ] ), ( const void * ) pDefenderApiRejectedSuffix, ( size_t ) DEFENDER_API_LENGTH_REJECTED_SUFFIX ); break; } } /*-----------------------------------------------------------*/ static DefenderStatus_t matchPrefix( const char * pRemainingTopic, uint16_t remainingTopicLength ) { DefenderStatus_t ret = DefenderNoMatch; assert( pRemainingTopic != NULL ); if( remainingTopicLength >= DEFENDER_API_LENGTH_PREFIX ) { if( strncmp( pRemainingTopic, DEFENDER_API_PREFIX, ( size_t ) DEFENDER_API_LENGTH_PREFIX ) == 0 ) { ret = DefenderSuccess; } } return ret; } /*-----------------------------------------------------------*/ static DefenderStatus_t extractThingNameLength( const char * pRemainingTopic, uint16_t remainingTopicLength, uint16_t * pOutThingNameLength ) { DefenderStatus_t ret = DefenderNoMatch; uint16_t i = 0U; assert( pRemainingTopic != NULL ); assert( pOutThingNameLength != NULL ); /* Find the first forward slash. It marks the end of the thing name. */ for( i = 0U; i < remainingTopicLength; i++ ) { if( pRemainingTopic[ i ] == '/' ) { break; } } /* Zero length thing name is not valid. */ if( i > 0U ) { *pOutThingNameLength = i; ret = DefenderSuccess; } return ret; } /*-----------------------------------------------------------*/ static DefenderStatus_t matchBridge( const char * pRemainingTopic, uint16_t remainingTopicLength ) { DefenderStatus_t ret = DefenderNoMatch; assert( pRemainingTopic != NULL ); if( remainingTopicLength >= DEFENDER_API_LENGTH_BRIDGE ) { if( strncmp( pRemainingTopic, DEFENDER_API_BRIDGE, ( size_t ) DEFENDER_API_LENGTH_BRIDGE ) == 0 ) { ret = DefenderSuccess; } } return ret; } /*-----------------------------------------------------------*/ static DefenderStatus_t matchApi( const char * pRemainingTopic, uint16_t remainingTopicLength, DefenderTopic_t * pOutApi ) { DefenderStatus_t ret = DefenderNoMatch; uint16_t i = 0U; /* Table of defender APIs. */ static const DefenderTopic_t defenderApi[] = { DefenderJsonReportPublish, DefenderJsonReportAccepted, DefenderJsonReportRejected, DefenderCborReportPublish, DefenderCborReportAccepted, DefenderCborReportRejected, }; /* Table of topic API strings in the same order as the above defenderApi table. */ static const char * const defenderApiTopic[] = { DEFENDER_API_JSON_FORMAT, DEFENDER_API_JSON_FORMAT DEFENDER_API_ACCEPTED_SUFFIX, DEFENDER_API_JSON_FORMAT DEFENDER_API_REJECTED_SUFFIX, DEFENDER_API_CBOR_FORMAT, DEFENDER_API_CBOR_FORMAT DEFENDER_API_ACCEPTED_SUFFIX, DEFENDER_API_CBOR_FORMAT DEFENDER_API_REJECTED_SUFFIX, }; /* Table of topic API string lengths in the same order as the above defenderApi table. */ static const uint16_t defenderApiTopicLength[] = { DEFENDER_API_LENGTH_JSON_FORMAT, DEFENDER_API_LENGTH_JSON_FORMAT + DEFENDER_API_LENGTH_ACCEPTED_SUFFIX, DEFENDER_API_LENGTH_JSON_FORMAT + DEFENDER_API_LENGTH_REJECTED_SUFFIX, DEFENDER_API_LENGTH_CBOR_FORMAT, DEFENDER_API_LENGTH_CBOR_FORMAT + DEFENDER_API_LENGTH_ACCEPTED_SUFFIX, DEFENDER_API_LENGTH_CBOR_FORMAT + DEFENDER_API_LENGTH_REJECTED_SUFFIX, }; for( i = 0U; i < sizeof( defenderApi ) / sizeof( defenderApi[ 0 ] ); i++ ) { if( ( remainingTopicLength == defenderApiTopicLength[ i ] ) && ( strncmp( pRemainingTopic, defenderApiTopic[ i ], ( size_t ) defenderApiTopicLength[ i ] ) == 0 ) ) { *pOutApi = defenderApi[ i ]; ret = DefenderSuccess; break; } } return ret; } /*-----------------------------------------------------------*/ DefenderStatus_t Defender_GetTopic( char * pBuffer, uint16_t bufferLength, const char * pThingName, uint16_t thingNameLength, DefenderTopic_t api, uint16_t * pOutLength ) { DefenderStatus_t ret = DefenderSuccess; uint16_t topicLength = 0U, offset = 0U; /* The following variables are to address MISRA Rule 7.4 violation of * passing const char * for const void * param of memcpy. */ const char * pDefenderApiPrefix = DEFENDER_API_PREFIX; const char * pDefenderApiBridge = DEFENDER_API_BRIDGE; if( ( pBuffer == NULL ) || ( pThingName == NULL ) || ( thingNameLength == 0U ) || ( thingNameLength > DEFENDER_THINGNAME_MAX_LENGTH ) || ( api <= DefenderInvalidTopic ) || ( api >= DefenderMaxTopic ) || ( pOutLength == NULL ) ) { ret = DefenderBadParameter; LogError( ( "Invalid input parameter. pBuffer: %p, bufferLength: %u, " "pThingName: %p, thingNameLength: %u, api: %d, pOutLength: %p.", ( void * ) pBuffer, ( unsigned int ) bufferLength, ( void * ) pThingName, ( unsigned int ) thingNameLength, api, ( void * ) pOutLength ) ); } if( ret == DefenderSuccess ) { topicLength = getTopicLength( thingNameLength, api ); if( bufferLength < topicLength ) { ret = DefenderBufferTooSmall; LogError( ( "The buffer is too small to hold the topic string. " "Provided buffer size: %u, Required buffer size: %u.", ( unsigned int ) bufferLength, ( unsigned int ) topicLength ) ); } } if( ret == DefenderSuccess ) { /* At this point, it is certain that we have a large enough buffer to * write the topic string into. */ /* Write prefix first. */ ( void ) memcpy( ( void * ) &( pBuffer[ offset ] ), ( const void * ) pDefenderApiPrefix, ( size_t ) DEFENDER_API_LENGTH_PREFIX ); offset += DEFENDER_API_LENGTH_PREFIX; /* Write thing name next. */ ( void ) memcpy( ( void * ) &( pBuffer[ offset ] ), ( const void * ) pThingName, ( size_t ) thingNameLength ); offset += thingNameLength; /* Write bridge next. */ ( void ) memcpy( ( void * ) &( pBuffer[ offset ] ), ( const void * ) pDefenderApiBridge, ( size_t ) DEFENDER_API_LENGTH_BRIDGE ); offset += DEFENDER_API_LENGTH_BRIDGE; /* Write report format and suffix. */ writeFormatAndSuffix( &( pBuffer[ offset ] ), api ); *pOutLength = topicLength; } return ret; } /*-----------------------------------------------------------*/ DefenderStatus_t Defender_MatchTopic( const char * pTopic, uint16_t topicLength, DefenderTopic_t * pOutApi, const char ** ppOutThingName, uint16_t * pOutThingNameLength ) { DefenderStatus_t ret = DefenderSuccess; uint16_t remainingTopicLength = 0U, consumedTopicLength = 0U, thingNameLength = 0U; if( ( pTopic == NULL ) || ( pOutApi == NULL ) ) { ret = DefenderBadParameter; LogError( ( "Invalid input parameter. pTopic: %p, pOutApi: %p.", ( void * ) pTopic, ( void * ) pOutApi ) ); } /* Nothing is consumed yet. */ remainingTopicLength = topicLength; consumedTopicLength = 0; if( ret == DefenderSuccess ) { ret = matchPrefix( &( pTopic[ consumedTopicLength ] ), remainingTopicLength ); if( ret == DefenderSuccess ) { remainingTopicLength -= DEFENDER_API_LENGTH_PREFIX; consumedTopicLength += DEFENDER_API_LENGTH_PREFIX; } else { LogDebug( ( "The topic does not contain defender prefix $aws/things/." ) ); } } if( ret == DefenderSuccess ) { ret = extractThingNameLength( &( pTopic[ consumedTopicLength ] ), remainingTopicLength, &( thingNameLength ) ); if( ret == DefenderSuccess ) { remainingTopicLength -= thingNameLength; consumedTopicLength += thingNameLength; } else { LogDebug( ( "The topic does not contain a valid thing name." ) ); } } if( ret == DefenderSuccess ) { ret = matchBridge( &( pTopic[ consumedTopicLength ] ), remainingTopicLength ); if( ret == DefenderSuccess ) { remainingTopicLength -= DEFENDER_API_LENGTH_BRIDGE; consumedTopicLength += DEFENDER_API_LENGTH_BRIDGE; } else { LogDebug( ( "The topic does not contain the defender bridge /defender/metrics/." ) ); } } if( ret == DefenderSuccess ) { ret = matchApi( &( pTopic[ consumedTopicLength ] ), remainingTopicLength, pOutApi ); if( ret != DefenderSuccess ) { LogDebug( ( "The topic does not contain valid report format or suffix " " needed to be a valid defender topic." ) ); } } /* Update the out parameters for thing name and thing length location, if we * successfully matched the topic. */ if( ret == DefenderSuccess ) { if( ppOutThingName != NULL ) { /* Thing name comes after the defender prefix. */ *ppOutThingName = &( pTopic[ DEFENDER_API_LENGTH_PREFIX ] ); } if( pOutThingNameLength != NULL ) { *pOutThingNameLength = thingNameLength; } } /* Update the output parameter for API if the topic did not match. In case * of a match, it is updated in the matchApi function. */ if( ret == DefenderNoMatch ) { *pOutApi = DefenderInvalidTopic; } return ret; } /*-----------------------------------------------------------*/