/* * AWS IoT Fleet Provisioning v1.1.0 * Copyright (C) 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * SPDX-License-Identifier: MIT * * 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 fleet_provisioning.c * @brief Implementation of the AWS IoT Fleet Provisioning Library. */ /* Standard includes. */ #include #include #include /* Fleet Provisioning API include. */ #include "fleet_provisioning.h" /** * @brief Identifier for which of the topic suffixes for a given format and * Fleet Provisioning MQTT API. */ typedef enum { TopicPublish, TopicAccepted, TopicRejected, TopicInvalidSuffix } TopicSuffix_t; /** * @brief Identifier for which of the topics in each Fleet Provisioning MQTT API. */ typedef enum { TopicJsonPublish, TopicJsonAccepted, TopicJsonRejected, TopicCborPublish, TopicCborAccepted, TopicCborRejected, TopicInvalidFormatSuffix } TopicFormatSuffix_t; /** * @brief Get the topic length for a given RegisterThing topic. * * @param[in] templateNameLength the length of the template name registered with * AWS IoT. * @param[in] format The RegisterThing API format to use. * @param[in] topic The RegisterThing API format to use. * * @return the template length for the given RegisterThing topic. */ static uint16_t getRegisterThingTopicLength( uint16_t templateNameLength, FleetProvisioningFormat_t format, FleetProvisioningApiTopics_t topic ); /** * @brief Write the given piece of the topic to the remaining buffer and advance * the remaining buffer pointer. * * The caller is responsible for assuring that there is enough space remaining * in the buffer to write the given string. * * @param[in,out] pBufferCursor Pointer to the remaining buffer. * @param[in] fragment The piece of the topic string to write. * @param[in] length The length of @p fragment. */ static void writeTopicFragmentAndAdvance( char ** pBufferCursor, const char * fragment, uint16_t length ); /** * @brief Check the parameters for FleetProvisioning_GetRegisterThingTopic(). * * @param[in] pTopicBuffer The buffer to write the topic string into. * @param[in] format The desired RegisterThing format. * @param[in] topic The desired RegisterThing topic. * @param[in] pTemplateName The name of the provisioning template configured * with AWS IoT. * @param[in] templateNameLength The length of @p pTemplateName. * @param[in] pOutLength The length of the topic string written to * the buffer. * * @return FleetProvisioningSuccess if no errors are found with the parameters; * FleetProvisioningBadParameter otherwise. */ static FleetProvisioningStatus_t GetRegisterThingTopicCheckParams( const char * pTopicBuffer, FleetProvisioningFormat_t format, FleetProvisioningApiTopics_t topic, const char * pTemplateName, uint16_t templateNameLength, const uint16_t * pOutLength ); /** * @brief Match the suffix from the remaining topic string and return the * corresponding suffix. * * Suffix: empty, /accepted, or /rejected. * * @param[in] pRemainingTopic The remaining portion of the topic. * @param[in] remainingLength The remaining length of the topic. * * @return The matching #TopicSuffix_t. */ static TopicSuffix_t parseTopicSuffix( const char * pRemainingTopic, uint16_t remainingLength ); /** * @brief Match the format and suffix from the remaining topic string and * return the corresponding format and suffix. * * Format: json or cbor. * Suffix: empty, /accepted, or /rejected. * * @param[in] pRemainingTopic The remaining portion of the topic. * @param[in] remainingLength The remaining length of the topic. * * @return The matching #TopicFormatSuffix_t. */ static TopicFormatSuffix_t parseTopicFormatSuffix( const char * pRemainingTopic, uint16_t remainingLength ); /** * @brief Match a topic string with the CreateCertificateFromCsr topics. * * @param[in] pTopic The topic string to match. * @param[in] topicLength The length of the topic string. * * @return The matching #FleetProvisioningTopic_t if the topic string is a * Fleet Provisioning CreateCertificateFromCsr topic, else * FleetProvisioningInvalidTopic. */ static FleetProvisioningTopic_t parseCreateCertificateFromCsrTopic( const char * pTopic, uint16_t topicLength ); /** * @brief Match a topic string with the CreateKeysAndCertificate topics. * * @param[in] pTopic The topic string to match. * @param[in] topicLength The length of the topic string. * * @return The matching FleetProvisioningTopic_t if the topic string is a * Fleet Provisioning CreateKeysAndCertificate topic, else * FleetProvisioningInvalidTopic. */ static FleetProvisioningTopic_t parseCreateKeysAndCertificateTopic( const char * pTopic, uint16_t topicLength ); /** * @brief Match a topic string with the RegisterThing topics. * * @param[in] pTopic The topic string to match. * @param[in] topicLength The length of the topic string. * * @return The matching #FleetProvisioningTopic_t if the topic string is a * Fleet Provisioning RegisterThing topic, else * FleetProvisioningInvalidTopic. */ static FleetProvisioningTopic_t parseRegisterThingTopic( const char * pTopic, uint16_t topicLength ); /** * @brief Check if the remaining buffer starts with a specified string. If so, * moves the remaining buffer pointer past the matched section and updates the * remaining length. * * @param[in,out] pBufferCursor Pointer to the remaining portion of the buffer. * @param[in,out] pRemainingLength The remaining length of the buffer. * @param[in] matchString The string to match against. * @param[in] matchLength The length of @p matchString. * * @return FleetProvisioningSuccess if the string matches and is skipped over; * FleetProvisioningNoMatch otherwise. */ static FleetProvisioningStatus_t consumeIfMatch( const char ** pBufferCursor, uint16_t * pRemainingLength, const char * matchString, uint16_t matchLength ); /** * @brief Move the remaining topic pointer past the template name in the * unparsed topic so far, and update the remaining topic length. * * The end of thing name is marked by a forward slash. A zero length thing name * is not valid. * * This function extracts the same template name from the following topic strings: * - $aws/provisioning-templates/TEMPLATE_NAME/provision/json/accepted * - $aws/provisioning-templates/TEMPLATE_NAME * The second topic is not a valid Fleet Provisioning topic and the matching * will fail when we try to match the bridge part. * * @param[in,out] pTopicCursor Pointer to the remaining topic string. * @param[in,out] pRemainingLength Pointer to the length of the remaining topic string. * * @return FleetProvisioningSuccess if a valid template name is skipped over; * FleetProvisioningNoMatch otherwise. */ static FleetProvisioningStatus_t consumeTemplateName( const char ** pTopicCursor, uint16_t * pRemainingLength ); /*-----------------------------------------------------------*/ static uint16_t getRegisterThingTopicLength( uint16_t templateNameLength, FleetProvisioningFormat_t format, FleetProvisioningApiTopics_t topic ) { uint16_t topicLength = 0U; assert( ( templateNameLength != 0U ) && ( templateNameLength <= FP_TEMPLATENAME_MAX_LENGTH ) ); assert( ( format == FleetProvisioningJson ) || ( format == FleetProvisioningCbor ) ); assert( ( topic >= FleetProvisioningPublish ) && ( topic <= FleetProvisioningRejected ) ); topicLength = FP_REGISTER_API_LENGTH_PREFIX + templateNameLength + FP_REGISTER_API_LENGTH_BRIDGE; if( format == FleetProvisioningJson ) { topicLength += FP_API_LENGTH_JSON_FORMAT; } if( format == FleetProvisioningCbor ) { topicLength += FP_API_LENGTH_CBOR_FORMAT; } if( topic == FleetProvisioningAccepted ) { topicLength += FP_API_LENGTH_ACCEPTED_SUFFIX; } if( topic == FleetProvisioningRejected ) { topicLength += FP_API_LENGTH_REJECTED_SUFFIX; } return topicLength; } /*-----------------------------------------------------------*/ static void writeTopicFragmentAndAdvance( char ** pBufferCursor, const char * fragment, uint16_t length ) { assert( pBufferCursor != NULL ); assert( *pBufferCursor != NULL ); assert( fragment != NULL ); ( void ) memcpy( ( void * ) *pBufferCursor, ( const void * ) fragment, ( size_t ) length ); *pBufferCursor += length; } /*-----------------------------------------------------------*/ static FleetProvisioningStatus_t GetRegisterThingTopicCheckParams( const char * pTopicBuffer, FleetProvisioningFormat_t format, FleetProvisioningApiTopics_t topic, const char * pTemplateName, uint16_t templateNameLength, const uint16_t * pOutLength ) { FleetProvisioningStatus_t ret = FleetProvisioningError; if( ( pTopicBuffer == NULL ) || ( format < FleetProvisioningJson ) || ( format > FleetProvisioningCbor ) || ( topic < FleetProvisioningPublish ) || ( topic > FleetProvisioningRejected ) || ( pTemplateName == NULL ) || ( templateNameLength == 0U ) || ( templateNameLength > FP_TEMPLATENAME_MAX_LENGTH ) || ( pOutLength == NULL ) ) { ret = FleetProvisioningBadParameter; LogError( ( "Invalid input parameter. pTopicBuffer: %p, format: %d, topic: %d," " pTemplateName: %p, templateNameLength: %u, pOutLength: %p.", ( const void * ) pTopicBuffer, ( int ) format, ( int ) topic, ( const void * ) pTemplateName, ( unsigned int ) templateNameLength, ( const void * ) pOutLength ) ); } else { ret = FleetProvisioningSuccess; } return ret; } /*-----------------------------------------------------------*/ static TopicSuffix_t parseTopicSuffix( const char * pRemainingTopic, uint16_t remainingLength ) { TopicSuffix_t ret = TopicInvalidSuffix; FleetProvisioningStatus_t status = FleetProvisioningNoMatch; const char * pTopicCursor = pRemainingTopic; uint16_t cursorLength = remainingLength; assert( pRemainingTopic != NULL ); /* Check if publish topic */ if( cursorLength == 0U ) { ret = TopicPublish; status = FleetProvisioningSuccess; } /* Check if accepted topic */ if( status == FleetProvisioningNoMatch ) { status = consumeIfMatch( &pTopicCursor, &cursorLength, FP_API_ACCEPTED_SUFFIX, FP_API_LENGTH_ACCEPTED_SUFFIX ); if( status == FleetProvisioningSuccess ) { if( cursorLength == 0U ) { ret = TopicAccepted; } else { status = FleetProvisioningError; } } } /* Check if rejected topic */ if( status == FleetProvisioningNoMatch ) { status = consumeIfMatch( &pTopicCursor, &cursorLength, FP_API_REJECTED_SUFFIX, FP_API_LENGTH_REJECTED_SUFFIX ); if( status == FleetProvisioningSuccess ) { if( cursorLength == 0U ) { ret = TopicRejected; } } } return ret; } /*-----------------------------------------------------------*/ static TopicFormatSuffix_t parseTopicFormatSuffix( const char * pRemainingTopic, uint16_t remainingLength ) { /* Table of JSON format and suffixes in same order as TopicSuffix_t. */ static const TopicFormatSuffix_t jsonSuffixes[] = { TopicJsonPublish, TopicJsonAccepted, TopicJsonRejected, TopicInvalidFormatSuffix }; /* Table of CBOR format and suffixes in same order as TopicSuffix_t. */ static const TopicFormatSuffix_t cborSuffixes[] = { TopicCborPublish, TopicCborAccepted, TopicCborRejected, TopicInvalidFormatSuffix }; TopicFormatSuffix_t ret = TopicInvalidFormatSuffix; TopicSuffix_t suffix = TopicInvalidSuffix; FleetProvisioningStatus_t status = FleetProvisioningNoMatch; const char * pTopicCursor = pRemainingTopic; uint16_t cursorLength = remainingLength; assert( pRemainingTopic != NULL ); /* Check if JSON format */ status = consumeIfMatch( &pTopicCursor, &cursorLength, FP_API_JSON_FORMAT, FP_API_LENGTH_JSON_FORMAT ); if( status == FleetProvisioningSuccess ) { /* Match suffix */ suffix = parseTopicSuffix( pTopicCursor, cursorLength ); ret = jsonSuffixes[ suffix ]; } if( status == FleetProvisioningNoMatch ) { /* Check if CBOR format */ status = consumeIfMatch( &pTopicCursor, &cursorLength, FP_API_CBOR_FORMAT, FP_API_LENGTH_CBOR_FORMAT ); if( status == FleetProvisioningSuccess ) { /* Match suffix */ suffix = parseTopicSuffix( pTopicCursor, cursorLength ); ret = cborSuffixes[ suffix ]; } } return ret; } /*-----------------------------------------------------------*/ static FleetProvisioningTopic_t parseCreateCertificateFromCsrTopic( const char * pTopic, uint16_t topicLength ) { /* Table of topics in the same order as TopicFormatSuffix_t. */ static const FleetProvisioningTopic_t createCertificateFromCsrApi[] = { FleetProvJsonCreateCertFromCsrPublish, FleetProvJsonCreateCertFromCsrAccepted, FleetProvJsonCreateCertFromCsrRejected, FleetProvCborCreateCertFromCsrPublish, FleetProvCborCreateCertFromCsrAccepted, FleetProvCborCreateCertFromCsrRejected, FleetProvisioningInvalidTopic }; FleetProvisioningTopic_t ret = FleetProvisioningInvalidTopic; FleetProvisioningStatus_t status = FleetProvisioningError; TopicFormatSuffix_t rest = TopicInvalidFormatSuffix; const char * pTopicCursor = pTopic; uint16_t cursorLength = topicLength; assert( pTopic != NULL ); /* Check if prefix matches */ status = consumeIfMatch( &pTopicCursor, &cursorLength, FP_CREATE_CERT_API_PREFIX, FP_CREATE_CERT_API_LENGTH_PREFIX ); if( status == FleetProvisioningSuccess ) { /* Match format and suffix */ rest = parseTopicFormatSuffix( pTopicCursor, cursorLength ); ret = createCertificateFromCsrApi[ rest ]; } return ret; } /*-----------------------------------------------------------*/ static FleetProvisioningTopic_t parseCreateKeysAndCertificateTopic( const char * pTopic, uint16_t topicLength ) { /* Table of topics in the same order as TopicFormatSuffix_t. */ static const FleetProvisioningTopic_t createKeysAndCertificateApi[] = { FleetProvJsonCreateKeysAndCertPublish, FleetProvJsonCreateKeysAndCertAccepted, FleetProvJsonCreateKeysAndCertRejected, FleetProvCborCreateKeysAndCertPublish, FleetProvCborCreateKeysAndCertAccepted, FleetProvCborCreateKeysAndCertRejected, FleetProvisioningInvalidTopic }; FleetProvisioningTopic_t ret = FleetProvisioningInvalidTopic; FleetProvisioningStatus_t status = FleetProvisioningError; TopicFormatSuffix_t rest = TopicInvalidFormatSuffix; const char * pTopicCursor = pTopic; uint16_t cursorLength = topicLength; assert( pTopic != NULL ); /* Check if prefix matches */ status = consumeIfMatch( &pTopicCursor, &cursorLength, FP_CREATE_KEYS_API_PREFIX, FP_CREATE_KEYS_API_LENGTH_PREFIX ); if( status == FleetProvisioningSuccess ) { /* Match format and suffix */ rest = parseTopicFormatSuffix( pTopicCursor, cursorLength ); ret = createKeysAndCertificateApi[ rest ]; } return ret; } /*-----------------------------------------------------------*/ static FleetProvisioningTopic_t parseRegisterThingTopic( const char * pTopic, uint16_t topicLength ) { /* Table of topics in the same order as TopicFormatSuffix_t. */ static const FleetProvisioningTopic_t registerThingApi[] = { FleetProvJsonRegisterThingPublish, FleetProvJsonRegisterThingAccepted, FleetProvJsonRegisterThingRejected, FleetProvCborRegisterThingPublish, FleetProvCborRegisterThingAccepted, FleetProvCborRegisterThingRejected, FleetProvisioningInvalidTopic }; FleetProvisioningTopic_t ret = FleetProvisioningInvalidTopic; FleetProvisioningStatus_t status = FleetProvisioningError; TopicFormatSuffix_t rest = TopicInvalidFormatSuffix; const char * pTopicCursor = pTopic; uint16_t cursorLength = topicLength; assert( pTopic != NULL ); /* Check if prefix matches */ status = consumeIfMatch( &pTopicCursor, &cursorLength, FP_REGISTER_API_PREFIX, FP_REGISTER_API_LENGTH_PREFIX ); if( status == FleetProvisioningSuccess ) { /* Skip template name */ status = consumeTemplateName( &pTopicCursor, &cursorLength ); } if( status == FleetProvisioningSuccess ) { /* Check if bridge matches */ status = consumeIfMatch( &pTopicCursor, &cursorLength, FP_REGISTER_API_BRIDGE, FP_REGISTER_API_LENGTH_BRIDGE ); } if( status == FleetProvisioningSuccess ) { /* Match format and suffix */ rest = parseTopicFormatSuffix( pTopicCursor, cursorLength ); ret = registerThingApi[ rest ]; } return ret; } /*-----------------------------------------------------------*/ static FleetProvisioningStatus_t consumeIfMatch( const char ** pBufferCursor, uint16_t * pRemainingLength, const char * matchString, uint16_t matchLength ) { FleetProvisioningStatus_t status = FleetProvisioningError; int32_t cmpVal = -1; assert( pBufferCursor != NULL ); assert( *pBufferCursor != NULL ); assert( pRemainingLength != NULL ); assert( matchString != NULL ); if( *pRemainingLength < matchLength ) { status = FleetProvisioningNoMatch; } else { cmpVal = strncmp( *pBufferCursor, matchString, ( size_t ) matchLength ); if( cmpVal != 0 ) { status = FleetProvisioningNoMatch; } else { status = FleetProvisioningSuccess; *pBufferCursor += matchLength; *pRemainingLength -= matchLength; } } return status; } /*-----------------------------------------------------------*/ static FleetProvisioningStatus_t consumeTemplateName( const char ** pTopicCursor, uint16_t * pRemainingLength ) { FleetProvisioningStatus_t ret = FleetProvisioningNoMatch; uint16_t i = 0U; assert( pTopicCursor != NULL ); assert( *pTopicCursor != NULL ); assert( pRemainingLength != NULL ); /* Find the first forward slash. It marks the end of the template name. */ for( i = 0U; i < *pRemainingLength; i++ ) { if( ( *pTopicCursor )[ i ] == '/' ) { break; } } /* Zero length template name is not valid. */ if( i > 0U ) { ret = FleetProvisioningSuccess; *pTopicCursor += i; *pRemainingLength -= i; } return ret; } /*-----------------------------------------------------------*/ FleetProvisioningStatus_t FleetProvisioning_GetRegisterThingTopic( char * pTopicBuffer, uint16_t bufferLength, FleetProvisioningFormat_t format, FleetProvisioningApiTopics_t topic, const char * pTemplateName, uint16_t templateNameLength, uint16_t * pOutLength ) { FleetProvisioningStatus_t status = FleetProvisioningError; uint16_t topicLength = 0U; char * pBufferCursor = pTopicBuffer; status = GetRegisterThingTopicCheckParams( pTopicBuffer, format, topic, pTemplateName, templateNameLength, pOutLength ); if( status == FleetProvisioningSuccess ) { topicLength = getRegisterThingTopicLength( templateNameLength, format, topic ); if( bufferLength < topicLength ) { status = FleetProvisioningBufferTooSmall; 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( status == FleetProvisioningSuccess ) { /* At this point, it is certain that we have a large enough buffer to * write the topic string into. */ /* Write prefix first. */ writeTopicFragmentAndAdvance( &pBufferCursor, FP_REGISTER_API_PREFIX, FP_REGISTER_API_LENGTH_PREFIX ); /* Write template name next. */ writeTopicFragmentAndAdvance( &pBufferCursor, pTemplateName, templateNameLength ); /* Write bridge next. */ writeTopicFragmentAndAdvance( &pBufferCursor, FP_REGISTER_API_BRIDGE, FP_REGISTER_API_LENGTH_BRIDGE ); /* Write report format. */ if( format == FleetProvisioningJson ) { writeTopicFragmentAndAdvance( &pBufferCursor, FP_API_JSON_FORMAT, FP_API_LENGTH_JSON_FORMAT ); } if( format == FleetProvisioningCbor ) { writeTopicFragmentAndAdvance( &pBufferCursor, FP_API_CBOR_FORMAT, FP_API_LENGTH_CBOR_FORMAT ); } /* Write report suffix. */ if( topic == FleetProvisioningAccepted ) { writeTopicFragmentAndAdvance( &pBufferCursor, FP_API_ACCEPTED_SUFFIX, FP_API_LENGTH_ACCEPTED_SUFFIX ); } if( topic == FleetProvisioningRejected ) { writeTopicFragmentAndAdvance( &pBufferCursor, FP_API_REJECTED_SUFFIX, FP_API_LENGTH_REJECTED_SUFFIX ); } *pOutLength = topicLength; } return status; } /*-----------------------------------------------------------*/ FleetProvisioningStatus_t FleetProvisioning_MatchTopic( const char * pTopic, uint16_t topicLength, FleetProvisioningTopic_t * pOutApi ) { FleetProvisioningStatus_t ret = FleetProvisioningNoMatch; if( ( pTopic == NULL ) || ( pOutApi == NULL ) ) { ret = FleetProvisioningBadParameter; LogError( ( "Invalid input parameter. pTopic: %p, pOutApi: %p.", ( const void * ) pTopic, ( void * ) pOutApi ) ); } else { *pOutApi = parseCreateCertificateFromCsrTopic( pTopic, topicLength ); if( *pOutApi == FleetProvisioningInvalidTopic ) { *pOutApi = parseCreateKeysAndCertificateTopic( pTopic, topicLength ); } if( *pOutApi == FleetProvisioningInvalidTopic ) { *pOutApi = parseRegisterThingTopic( pTopic, topicLength ); } if( *pOutApi != FleetProvisioningInvalidTopic ) { ret = FleetProvisioningSuccess; } } return ret; } /*-----------------------------------------------------------*/