source/azure_iot_provisioning_client.c (781 lines of code) (raw):

/* Copyright (c) Microsoft Corporation. * Licensed under the MIT License. */ /** * @file azure_iot_provisioning_client.c * @brief Implementation of Azure IoT Provisioning client. * */ #include "azure_iot_provisioning_client.h" /* Kernel includes. */ #include "FreeRTOS.h" #include "task.h" /* azure iot includes. */ #include "azure_iot_mqtt.h" #include "azure_iot_private.h" #include "azure_iot_result.h" /* Azure SDK for Embedded C includes */ #include "azure/az_iot.h" #include "azure/core/az_version.h" #ifndef azureiotprovisioningDEFAULT_TOKEN_TIMEOUT_IN_SEC #define azureiotprovisioningDEFAULT_TOKEN_TIMEOUT_IN_SEC azureiotconfigDEFAULT_TOKEN_TIMEOUT_IN_SEC #endif /* azureiotprovisioningDEFAULT_TOKEN_TIMEOUT_IN_SEC */ #ifndef azureiotprovisioningKEEP_ALIVE_TIMEOUT_SECONDS #define azureiotprovisioningKEEP_ALIVE_TIMEOUT_SECONDS azureiotconfigKEEP_ALIVE_TIMEOUT_SECONDS #endif /* azureiotprovisioningKEEP_ALIVE_TIMEOUT_SECONDS */ #ifndef azureiotprovisioningCONNACK_RECV_TIMEOUT_MS #define azureiotprovisioningCONNACK_RECV_TIMEOUT_MS azureiotconfigCONNACK_RECV_TIMEOUT_MS #endif /* azureiotprovisioningCONNACK_RECV_TIMEOUT_MS */ #ifndef azureiotprovisioningUSER_AGENT #define azureiotprovisioningUSER_AGENT "" #endif /* azureiotprovisioningUSER_AGENT */ #ifndef azureiotprovisioningPROCESS_LOOP_TIMEOUT_MS #define azureiotprovisioningPROCESS_LOOP_TIMEOUT_MS ( 500U ) #endif /* azureiotprovisioningPROCESS_LOOP_TIMEOUT_MS */ /* Define various workflow (WF) states Provisioning can get into */ #define azureiotprovisioningWF_STATE_INIT ( 0x0 ) #define azureiotprovisioningWF_STATE_CONNECT ( 0x1 ) #define azureiotprovisioningWF_STATE_SUBSCRIBE ( 0x2 ) #define azureiotprovisioningWF_STATE_REQUEST ( 0x3 ) #define azureiotprovisioningWF_STATE_RESPONSE ( 0x4 ) #define azureiotprovisioningWF_STATE_SUBSCRIBING ( 0x5 ) #define azureiotprovisioningWF_STATE_REQUESTING ( 0x6 ) #define azureiotprovisioningWF_STATE_WAITING ( 0x7 ) #define azureiotprovisioningWF_STATE_COMPLETE ( 0x8 ) #define azureiotPrvGetMaxInt( a, b ) ( ( a ) > ( b ) ? ( a ) : ( b ) ) #define azureiotprovisioningREQUEST_PAYLOAD_LABEL "payload" #define azureiotprovisioningREQUEST_REGISTRATION_ID_LABEL "registrationId" #define azureiotprovisioningHMACBufferLength ( 48 ) /*-----------------------------------------------------------*/ /** * * State transitions : * CONNECT -> { SUBSCRIBE | COMPLETE } -> { SUBSCRIBING | COMPLETE } -> { REQUEST | COMPLETE } -> * REQUESTING -> { RESPONSE | COMPLETE } -> { WAITING | COMPLETE } -> { REQUEST | COMPLETE } * * Note : At COMPLETE state, we will check the status of lastOperation to see if WorkFlow( WF ) * finshed without error. * **/ static void prvProvClientUpdateState( AzureIoTProvisioningClient_t * pxAzureProvClient, uint32_t ulActionResult ) { uint32_t ulState = pxAzureProvClient->_internal.ulWorkflowState; pxAzureProvClient->_internal.ulLastOperationResult = ulActionResult; switch( ulState ) { case azureiotprovisioningWF_STATE_CONNECT: pxAzureProvClient->_internal.ulWorkflowState = ulActionResult == eAzureIoTSuccess ? azureiotprovisioningWF_STATE_SUBSCRIBE : azureiotprovisioningWF_STATE_COMPLETE; break; case azureiotprovisioningWF_STATE_SUBSCRIBE: pxAzureProvClient->_internal.ulWorkflowState = ulActionResult == eAzureIoTSuccess ? azureiotprovisioningWF_STATE_SUBSCRIBING : azureiotprovisioningWF_STATE_COMPLETE; break; case azureiotprovisioningWF_STATE_SUBSCRIBING: pxAzureProvClient->_internal.ulWorkflowState = ulActionResult == eAzureIoTSuccess ? azureiotprovisioningWF_STATE_REQUEST : azureiotprovisioningWF_STATE_COMPLETE; break; case azureiotprovisioningWF_STATE_REQUEST: pxAzureProvClient->_internal.ulWorkflowState = ulActionResult == eAzureIoTSuccess ? azureiotprovisioningWF_STATE_REQUESTING : azureiotprovisioningWF_STATE_COMPLETE; break; case azureiotprovisioningWF_STATE_REQUESTING: pxAzureProvClient->_internal.ulWorkflowState = ulActionResult == eAzureIoTSuccess ? azureiotprovisioningWF_STATE_RESPONSE : azureiotprovisioningWF_STATE_COMPLETE; break; case azureiotprovisioningWF_STATE_RESPONSE: pxAzureProvClient->_internal.ulWorkflowState = ulActionResult == eAzureIoTErrorPending ? azureiotprovisioningWF_STATE_WAITING : azureiotprovisioningWF_STATE_COMPLETE; break; case azureiotprovisioningWF_STATE_WAITING: pxAzureProvClient->_internal.ulWorkflowState = ulActionResult == eAzureIoTSuccess ? azureiotprovisioningWF_STATE_REQUEST : azureiotprovisioningWF_STATE_COMPLETE; break; case azureiotprovisioningWF_STATE_COMPLETE: AZLogInfo( ( "AzureIoTProvisioning state 'Complete'" ) ); break; default: AZLogError( ( "AzureIoTProvisioning unknown state: [%u]", ( uint16_t ) ulState ) ); configASSERT( false ); break; } AZLogDebug( ( "AzureIoTProvisioning updated state from [%u] -> [%u]", ( uint16_t ) ulState, ( uint16_t ) pxAzureProvClient->_internal.ulWorkflowState ) ); } /*-----------------------------------------------------------*/ /** * * Implementation of the connect action. This action is only allowed in azureiotprovisioningWF_STATE_CONNECT * * */ static void prvProvClientConnect( AzureIoTProvisioningClient_t * pxAzureProvClient ) { AzureIoTMQTTConnectInfo_t xConnectInfo = { 0 }; AzureIoTResult_t xResult; AzureIoTMQTTResult_t xMQTTResult; bool xSessionPresent; uint32_t ulPasswordLength = 0; size_t xMQTTUsernameLength; az_result xCoreResult; if( pxAzureProvClient->_internal.ulWorkflowState != azureiotprovisioningWF_STATE_CONNECT ) { AZLogError( ( "AzureIoTProvisioning connect action called in wrong state: [%u]", ( uint16_t ) pxAzureProvClient->_internal.ulWorkflowState ) ); return; } xConnectInfo.pcUserName = pxAzureProvClient->_internal.pucScratchBuffer; xConnectInfo.pcPassword = xConnectInfo.pcUserName + azureiotconfigUSERNAME_MAX; if( az_result_failed( xCoreResult = az_iot_provisioning_client_get_user_name( &pxAzureProvClient->_internal.xProvisioningClientCore, ( char * ) xConnectInfo.pcUserName, azureiotconfigUSERNAME_MAX, &xMQTTUsernameLength ) ) ) { AZLogError( ( "AzureIoTProvisioning failed to get username: core error=0x%08x", ( uint16_t ) xCoreResult ) ); xResult = AzureIoT_TranslateCoreError( xCoreResult ); } /* Check if token refresh is set, then generate password */ else if( ( pxAzureProvClient->_internal.pxTokenRefresh ) && ( pxAzureProvClient->_internal.pxTokenRefresh( pxAzureProvClient, pxAzureProvClient->_internal.xGetTimeFunction() + azureiotprovisioningDEFAULT_TOKEN_TIMEOUT_IN_SEC, pxAzureProvClient->_internal.pucSymmetricKey, pxAzureProvClient->_internal.ulSymmetricKeyLength, ( uint8_t * ) xConnectInfo.pcPassword, azureiotconfigPASSWORD_MAX, &ulPasswordLength ) ) ) { AZLogError( ( "AzureIoTProvisioning failed to generate auth token" ) ); xResult = eAzureIoTErrorTokenGenerationFailed; } else { xConnectInfo.xCleanSession = true; xConnectInfo.pcClientIdentifier = pxAzureProvClient->_internal.pucRegistrationID; xConnectInfo.usClientIdentifierLength = ( uint16_t ) pxAzureProvClient->_internal.ulRegistrationIDLength; xConnectInfo.usUserNameLength = ( uint16_t ) xMQTTUsernameLength; xConnectInfo.usKeepAliveSeconds = azureiotprovisioningKEEP_ALIVE_TIMEOUT_SECONDS; xConnectInfo.usPasswordLength = ( uint16_t ) ulPasswordLength; if( ( xMQTTResult = AzureIoTMQTT_Connect( &( pxAzureProvClient->_internal.xMQTTContext ), &xConnectInfo, NULL, azureiotprovisioningCONNACK_RECV_TIMEOUT_MS, &xSessionPresent ) ) != eAzureIoTMQTTSuccess ) { AZLogError( ( "AzureIoTProvisioning failed to establish MQTT connection: Server=%.*s, MQTT error=0x%08x", ( int16_t ) pxAzureProvClient->_internal.ulEndpointLength, pxAzureProvClient->_internal.pucEndpoint, ( uint16_t ) xMQTTResult ) ); xResult = eAzureIoTErrorServerError; } else { /* Successfully established and MQTT connection with the broker. */ AZLogInfo( ( "AzureIoTProvisioning established an MQTT connection with %.*s", ( int16_t ) pxAzureProvClient->_internal.ulEndpointLength, pxAzureProvClient->_internal.pucEndpoint ) ); xResult = eAzureIoTSuccess; } } prvProvClientUpdateState( pxAzureProvClient, xResult ); } /*-----------------------------------------------------------*/ /** * * Implementation of subscribe action, this action is only allowed in azureiotprovisioningWF_STATE_SUBSCRIBE * * */ static void prvProvClientSubscribe( AzureIoTProvisioningClient_t * pxAzureProvClient ) { AzureIoTMQTTSubscribeInfo_t xMQTTSubscription = { 0 }; AzureIoTMQTTResult_t xMQTTResult; AzureIoTResult_t xResult; uint16_t usSubscribePacketIdentifier; if( pxAzureProvClient->_internal.ulWorkflowState != azureiotprovisioningWF_STATE_SUBSCRIBE ) { AZLogWarn( ( "AzureIoTProvisioning subscribe action called in wrong state: [%u]", ( uint16_t ) pxAzureProvClient->_internal.ulWorkflowState ) ); } else { xMQTTSubscription.xQoS = eAzureIoTMQTTQoS0; xMQTTSubscription.pcTopicFilter = ( const uint8_t * ) AZ_IOT_PROVISIONING_CLIENT_REGISTER_SUBSCRIBE_TOPIC; xMQTTSubscription.usTopicFilterLength = ( uint16_t ) sizeof( AZ_IOT_PROVISIONING_CLIENT_REGISTER_SUBSCRIBE_TOPIC ) - 1; usSubscribePacketIdentifier = AzureIoTMQTT_GetPacketId( &( pxAzureProvClient->_internal.xMQTTContext ) ); AZLogInfo( ( "AzureIoTProvisioning attempting to subscribe to the MQTT topic: %s", AZ_IOT_HUB_CLIENT_C2D_SUBSCRIBE_TOPIC ) ); if( ( xMQTTResult = AzureIoTMQTT_Subscribe( &( pxAzureProvClient->_internal.xMQTTContext ), &xMQTTSubscription, 1, usSubscribePacketIdentifier ) ) != eAzureIoTMQTTSuccess ) { AZLogError( ( "AzureIoTProvisioning failed to subscribe to MQTT topic: MQTT error=0x%08x", ( uint16_t ) xMQTTResult ) ); xResult = eAzureIoTErrorSubscribeFailed; } else { xResult = eAzureIoTSuccess; } prvProvClientUpdateState( pxAzureProvClient, xResult ); } } /*-----------------------------------------------------------*/ /** * * Implementation of request action. This action is only allowed in azureiotprovisioningWF_STATE_REQUEST * * */ static void prvProvClientRequest( AzureIoTProvisioningClient_t * pxAzureProvClient ) { AzureIoTMQTTResult_t xMQTTResult; AzureIoTResult_t xResult; AzureIoTMQTTPublishInfo_t xMQTTPublishInfo = { 0 }; size_t xMQTTTopicLength; size_t xMQTTPayloadLength = 0; uint16_t usPublishPacketIdentifier; az_result xCoreResult; az_span xCustomPayloadProperty = az_span_create( ( uint8_t * ) pxAzureProvClient->_internal.pucRegistrationPayload, ( int32_t ) pxAzureProvClient->_internal.ulRegistrationPayloadLength ); /* Check the state. */ if( pxAzureProvClient->_internal.ulWorkflowState != azureiotprovisioningWF_STATE_REQUEST ) { AZLogWarn( ( "AzureIoTProvisioning request action called in wrong state: [%u]", ( uint16_t ) pxAzureProvClient->_internal.ulWorkflowState ) ); } else { /* Check if previous this is the 1st request or subsequent query request */ if( pxAzureProvClient->_internal.xLastResponsePayloadLength == 0 ) { xCoreResult = az_iot_provisioning_client_register_get_publish_topic( &pxAzureProvClient->_internal.xProvisioningClientCore, ( char * ) pxAzureProvClient->_internal.pucScratchBuffer, azureiotconfigTOPIC_MAX, &xMQTTTopicLength ); } else { xCoreResult = az_iot_provisioning_client_query_status_get_publish_topic( &pxAzureProvClient->_internal.xProvisioningClientCore, pxAzureProvClient->_internal.xRegisterResponse.operation_id, ( char * ) pxAzureProvClient->_internal.pucScratchBuffer, azureiotconfigTOPIC_MAX, &xMQTTTopicLength ); } if( az_result_failed( xCoreResult ) ) { prvProvClientUpdateState( pxAzureProvClient, eAzureIoTErrorFailed ); return; } xMQTTPayloadLength = pxAzureProvClient->_internal.ulScratchBufferLength - ( uint32_t ) xMQTTTopicLength; xCoreResult = az_iot_provisioning_client_get_request_payload( &pxAzureProvClient->_internal.xProvisioningClientCore, xCustomPayloadProperty, NULL, ( uint8_t * ) ( pxAzureProvClient->_internal.pucScratchBuffer + xMQTTTopicLength ), ( size_t ) xMQTTPayloadLength, &xMQTTPayloadLength ); if( az_result_failed( xCoreResult ) ) { prvProvClientUpdateState( pxAzureProvClient, eAzureIoTErrorFailed ); return; } xMQTTPublishInfo.xQOS = eAzureIoTMQTTQoS0; xMQTTPublishInfo.pcTopicName = pxAzureProvClient->_internal.pucScratchBuffer; xMQTTPublishInfo.usTopicNameLength = ( uint16_t ) xMQTTTopicLength; xMQTTPublishInfo.pvPayload = pxAzureProvClient->_internal.pucScratchBuffer + xMQTTTopicLength; xMQTTPublishInfo.xPayloadLength = xMQTTPayloadLength; usPublishPacketIdentifier = AzureIoTMQTT_GetPacketId( &( pxAzureProvClient->_internal.xMQTTContext ) ); if( ( xMQTTResult = AzureIoTMQTT_Publish( &( pxAzureProvClient->_internal.xMQTTContext ), &xMQTTPublishInfo, usPublishPacketIdentifier ) ) != eAzureIoTMQTTSuccess ) { AZLogError( ( "AzureIoTProvisioning failed to publish prov request: MQTT error=0x%08x", ( uint16_t ) xMQTTResult ) ); xResult = eAzureIoTErrorPublishFailed; } else { xResult = eAzureIoTSuccess; } prvProvClientUpdateState( pxAzureProvClient, xResult ); } } /*-----------------------------------------------------------*/ /** * * Implementation of parsing response action, this action is only allowed in azureiotprovisioningWF_STATE_RESPONSE * * */ static void prvProvClientParseResponse( AzureIoTProvisioningClient_t * pxAzureProvClient ) { az_result xCoreResult; az_span xTopic = az_span_create( pxAzureProvClient->_internal.ucProvisioningLastResponse, ( int32_t ) pxAzureProvClient->_internal.usLastResponseTopicLength ); az_span xPayload = az_span_create( pxAzureProvClient->_internal.ucProvisioningLastResponse + pxAzureProvClient->_internal.usLastResponseTopicLength, ( int32_t ) pxAzureProvClient->_internal.xLastResponsePayloadLength ); /* Check the state. */ if( pxAzureProvClient->_internal.ulWorkflowState != azureiotprovisioningWF_STATE_RESPONSE ) { AZLogWarn( ( "AzureIoTProvisioning parse response action called in wrong state: [%u]", ( uint16_t ) pxAzureProvClient->_internal.ulWorkflowState ) ); return; } if( ( pxAzureProvClient->_internal.usLastResponseTopicLength == 0 ) || ( pxAzureProvClient->_internal.xLastResponsePayloadLength == 0 ) ) { AZLogError( ( "AzureIoTProvisioning client failed with invalid server response" ) ); prvProvClientUpdateState( pxAzureProvClient, eAzureIoTErrorInvalidResponse ); return; } xCoreResult = az_iot_provisioning_client_parse_received_topic_and_payload( &pxAzureProvClient->_internal.xProvisioningClientCore, xTopic, xPayload, &pxAzureProvClient->_internal.xRegisterResponse ); if( xCoreResult == AZ_ERROR_IOT_TOPIC_NO_MATCH ) { AZLogInfo( ( "AzureIoTProvisioning ignoring unknown topic." ) ); /* Maintaining the same state. */ return; } else if( az_result_failed( xCoreResult ) ) { AZLogError( ( "AzureIoTProvisioning client failed to parse packet: core error=0x%08x", ( uint16_t ) xCoreResult ) ); prvProvClientUpdateState( pxAzureProvClient, eAzureIoTErrorFailed ); return; } if( az_iot_provisioning_client_operation_complete( pxAzureProvClient->_internal.xRegisterResponse.operation_status ) ) { switch( pxAzureProvClient->_internal.xRegisterResponse.operation_status ) { case AZ_IOT_PROVISIONING_STATUS_ASSIGNED: prvProvClientUpdateState( pxAzureProvClient, eAzureIoTSuccess ); break; case AZ_IOT_PROVISIONING_STATUS_FAILED: AZLogError( ( "AzureIoTProvisioning client registration failed with error %u: TrackingID: [%.*s] \"%.*s\"", ( uint16_t ) pxAzureProvClient->_internal.xRegisterResponse.registration_state.extended_error_code, ( int16_t ) az_span_size( pxAzureProvClient->_internal.xRegisterResponse.registration_state.error_tracking_id ), az_span_ptr( pxAzureProvClient->_internal.xRegisterResponse.registration_state.error_tracking_id ), ( int16_t ) az_span_size( pxAzureProvClient->_internal.xRegisterResponse.registration_state.error_message ), az_span_ptr( pxAzureProvClient->_internal.xRegisterResponse.registration_state.error_message ) ) ); prvProvClientUpdateState( pxAzureProvClient, eAzureIoTErrorServerError ); break; case AZ_IOT_PROVISIONING_STATUS_DISABLED: AZLogError( ( "AzureIoTProvisioning client: device is disabled." ) ); prvProvClientUpdateState( pxAzureProvClient, eAzureIoTErrorServerError ); break; default: AZLogError( ( "AzureIoTProvisioning unexpected operation status %d", pxAzureProvClient->_internal.xRegisterResponse.operation_status ) ); } } else /* Operation is not complete. */ { if( pxAzureProvClient->_internal.xRegisterResponse.retry_after_seconds == 0 ) { pxAzureProvClient->_internal.xRegisterResponse.retry_after_seconds = azureiotconfigPROVISIONING_POLLING_INTERVAL_S; } pxAzureProvClient->_internal.ullRetryAfter = pxAzureProvClient->_internal.xGetTimeFunction() + pxAzureProvClient->_internal.xRegisterResponse.retry_after_seconds; prvProvClientUpdateState( pxAzureProvClient, eAzureIoTErrorPending ); } } /*-----------------------------------------------------------*/ /** * * Implementation of wait check action. this action is only allowed in azureiotprovisioningWF_STATE_WAITING * * */ static void prvProvClientCheckTimeout( AzureIoTProvisioningClient_t * pxAzureProvClient ) { if( pxAzureProvClient->_internal.ulWorkflowState == azureiotprovisioningWF_STATE_WAITING ) { if( pxAzureProvClient->_internal.xGetTimeFunction() > pxAzureProvClient->_internal.ullRetryAfter ) { pxAzureProvClient->_internal.ullRetryAfter = 0; prvProvClientUpdateState( pxAzureProvClient, eAzureIoTSuccess ); } } } /*-----------------------------------------------------------*/ /** * Trigger state machine action base on the state. * **/ static void prvProvClientTriggerAction( AzureIoTProvisioningClient_t * pxAzureProvClient ) { switch( pxAzureProvClient->_internal.ulWorkflowState ) { case azureiotprovisioningWF_STATE_CONNECT: prvProvClientConnect( pxAzureProvClient ); break; case azureiotprovisioningWF_STATE_SUBSCRIBE: prvProvClientSubscribe( pxAzureProvClient ); break; case azureiotprovisioningWF_STATE_REQUEST: prvProvClientRequest( pxAzureProvClient ); break; case azureiotprovisioningWF_STATE_RESPONSE: prvProvClientParseResponse( pxAzureProvClient ); break; case azureiotprovisioningWF_STATE_SUBSCRIBING: case azureiotprovisioningWF_STATE_REQUESTING: case azureiotprovisioningWF_STATE_COMPLETE: /* None action taken here, as these states are waiting for receive path. */ break; case azureiotprovisioningWF_STATE_WAITING: prvProvClientCheckTimeout( pxAzureProvClient ); break; default: AZLogError( ( "AzureIoTProvisioning state not handled: [%u]", ( uint16_t ) pxAzureProvClient->_internal.ulWorkflowState ) ); configASSERT( false ); } } /*-----------------------------------------------------------*/ /** * Run the workflow : trigger action on state and process receive path in MQTT loop * */ static AzureIoTResult_t prvProvClientRunWorkflow( AzureIoTProvisioningClient_t * pxAzureProvClient, uint32_t ulTimeoutMilliseconds ) { AzureIoTMQTTResult_t xMQTTResult; AzureIoTResult_t xResult; uint32_t ulWaitTime; do { if( pxAzureProvClient->_internal.ulWorkflowState == azureiotprovisioningWF_STATE_COMPLETE ) { AZLogDebug( ( "AzureIoTProvisioning state is already in complete state" ) ); break; } if( ulTimeoutMilliseconds > azureiotprovisioningPROCESS_LOOP_TIMEOUT_MS ) { ulTimeoutMilliseconds -= azureiotprovisioningPROCESS_LOOP_TIMEOUT_MS; ulWaitTime = azureiotprovisioningPROCESS_LOOP_TIMEOUT_MS; } else { ulWaitTime = ulTimeoutMilliseconds; ulTimeoutMilliseconds = 0; } prvProvClientTriggerAction( pxAzureProvClient ); if( pxAzureProvClient->_internal.ulWorkflowState == azureiotprovisioningWF_STATE_COMPLETE ) { AZLogDebug( ( "AzureIoTProvisioning is in complete state: status=0x%08x", ( uint16_t ) pxAzureProvClient->_internal.ulLastOperationResult ) ); break; } else if( ( xMQTTResult = AzureIoTMQTT_ProcessLoop( &( pxAzureProvClient->_internal.xMQTTContext ), ulWaitTime ) ) != eAzureIoTMQTTSuccess ) { AZLogError( ( "AzureIoTProvisioning failed to process loop: ProcessLoopDuration=%u, MQTT error=0x%08x", ( uint16_t ) ulTimeoutMilliseconds, ( uint16_t ) xMQTTResult ) ); prvProvClientUpdateState( pxAzureProvClient, eAzureIoTErrorFailed ); break; } } while( ulTimeoutMilliseconds ); if( ( pxAzureProvClient->_internal.ulWorkflowState != azureiotprovisioningWF_STATE_COMPLETE ) ) { xResult = eAzureIoTErrorPending; } else { xResult = pxAzureProvClient->_internal.ulLastOperationResult; } return xResult; } /*-----------------------------------------------------------*/ /** * Process MQTT Subscribe Ack from Provisioning Service * */ static void prvProvClientMQTTProcessSubAck( AzureIoTProvisioningClient_t * pxAzureProvClient, AzureIoTMQTTPublishInfo_t * pxPacketInfo ) { ( void ) pxPacketInfo; /* We assume success since IoT Provisioning would disconnect if there was a problem subscribing. */ prvProvClientUpdateState( pxAzureProvClient, eAzureIoTSuccess ); } /*-----------------------------------------------------------*/ /** * Process MQTT Response from Provisioning Service * */ static void prvProvClientMQTTProcessResponse( AzureIoTProvisioningClient_t * pxAzureProvClient, AzureIoTMQTTPublishInfo_t * pxPublishInfo ) { if( pxAzureProvClient->_internal.ulWorkflowState == azureiotprovisioningWF_STATE_REQUESTING ) { if( ( pxPublishInfo->xPayloadLength + pxPublishInfo->usTopicNameLength ) <= sizeof( pxAzureProvClient->_internal.ucProvisioningLastResponse ) ) { /* Copy topic + payload into the response buffer. * ucProvisioningLastResponse = [ topic payload ] */ pxAzureProvClient->_internal.usLastResponseTopicLength = pxPublishInfo->usTopicNameLength; memcpy( pxAzureProvClient->_internal.ucProvisioningLastResponse, pxPublishInfo->pcTopicName, pxPublishInfo->usTopicNameLength ); pxAzureProvClient->_internal.xLastResponsePayloadLength = pxPublishInfo->xPayloadLength; memcpy( pxAzureProvClient->_internal.ucProvisioningLastResponse + pxAzureProvClient->_internal.usLastResponseTopicLength, pxPublishInfo->pvPayload, pxPublishInfo->xPayloadLength ); prvProvClientUpdateState( pxAzureProvClient, eAzureIoTSuccess ); } else { AZLogError( ( "AzureIoTProvisioning process response failed with buffer too small required size is %d bytes", pxPublishInfo->xPayloadLength + pxPublishInfo->usTopicNameLength ) ); prvProvClientUpdateState( pxAzureProvClient, eAzureIoTErrorOutOfMemory ); } } } /*-----------------------------------------------------------*/ /** * Callback that receives all the Event from MQTT * */ static void prvProvClientEventCallback( AzureIoTMQTTHandle_t pxMQTTContext, AzureIoTMQTTPacketInfo_t * pxPacketInfo, AzureIoTMQTTDeserializedInfo_t * pxDeserializedInfo ) { AzureIoTProvisioningClient_t * pxAzureProvClient = ( AzureIoTProvisioningClient_t * ) pxMQTTContext; if( ( pxPacketInfo->ucType & 0xF0U ) == azureiotmqttPACKET_TYPE_SUBACK ) { prvProvClientMQTTProcessSubAck( pxAzureProvClient, pxDeserializedInfo->pxPublishInfo ); } else if( ( pxPacketInfo->ucType & 0xF0U ) == azureiotmqttPACKET_TYPE_PUBLISH ) { prvProvClientMQTTProcessResponse( pxAzureProvClient, pxDeserializedInfo->pxPublishInfo ); } else { AZLogDebug( ( "AzureIoTProvisioning received packet of type: %d", pxPacketInfo->ucType ) ); } } /*-----------------------------------------------------------*/ /** * Generate the SAS token based on : * https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-devguide-security#use-a-shared-access-policy * */ static uint32_t prvProvClientGetToken( AzureIoTProvisioningClient_t * pxAzureProvClient, uint64_t ullExpiryTimeSecs, const uint8_t * ucKey, uint32_t ulKeyLen, uint8_t * pucSASBuffer, uint32_t ulSasBufferLen, uint32_t * pulSaSLength ) { uint8_t * pucHMACBuffer; az_span xSpan = az_span_create( pucSASBuffer, ( int32_t ) ulSasBufferLen ); az_result xCoreResult; uint32_t ulBytesUsed; uint32_t ulSignatureLength; uint32_t ulBufferLeft; size_t xLength; xCoreResult = az_iot_provisioning_client_sas_get_signature( &( pxAzureProvClient->_internal.xProvisioningClientCore ), ullExpiryTimeSecs, xSpan, &xSpan ); if( az_result_failed( xCoreResult ) ) { AZLogError( ( "AzureIoTProvisioning failed to get signature: core error=0x%08x", ( uint16_t ) xCoreResult ) ); return AzureIoT_TranslateCoreError( xCoreResult ); } ulBytesUsed = ( uint32_t ) az_span_size( xSpan ); ulBufferLeft = ulSasBufferLen - ulBytesUsed; if( ulBufferLeft < azureiotprovisioningHMACBufferLength ) { AZLogError( ( "AzureIoTProvisioning token generation failed with insufficient buffer size" ) ); return eAzureIoTErrorOutOfMemory; } /* Calculate HMAC at the end of buffer, so we do less data movement when returning back to caller. */ ulBufferLeft -= azureiotprovisioningHMACBufferLength; pucHMACBuffer = pucSASBuffer + ulSasBufferLen - azureiotprovisioningHMACBufferLength; if( AzureIoT_Base64HMACCalculate( pxAzureProvClient->_internal.xHMACFunction, ucKey, ulKeyLen, pucSASBuffer, ulBytesUsed, pucSASBuffer + ulBytesUsed, ulBufferLeft, pucHMACBuffer, azureiotprovisioningHMACBufferLength, &ulSignatureLength ) ) { AZLogError( ( "AzureIoTProvisioning failed to encoded HMAC hash" ) ); return eAzureIoTErrorFailed; } if( ( ulSasBufferLen <= azureiotprovisioningHMACBufferLength ) ) { AZLogError( ( "AzureIoTProvisioning failed with insufficient buffer size" ) ); return eAzureIoTErrorOutOfMemory; } xSpan = az_span_create( pucHMACBuffer, ( int32_t ) ulSignatureLength ); xCoreResult = az_iot_provisioning_client_sas_get_password( &( pxAzureProvClient->_internal.xProvisioningClientCore ), xSpan, ullExpiryTimeSecs, AZ_SPAN_EMPTY, ( char * ) pucSASBuffer, ulSasBufferLen - azureiotprovisioningHMACBufferLength, &xLength ); if( az_result_failed( xCoreResult ) ) { AZLogError( ( "AzureIoTProvisioning failed to generate token: core error=0x%08x", ( uint16_t ) xCoreResult ) ); return AzureIoT_TranslateCoreError( xCoreResult ); } *pulSaSLength = ( uint32_t ) xLength; return eAzureIoTSuccess; } /*-----------------------------------------------------------*/ /** * Get number of millseconds passed to MQTT stack * */ static uint32_t prvProvClientGetTimeMillseconds( void ) { TickType_t xTickCount = 0; uint32_t ulTimeMs = 0UL; /* Get the current tick count. */ xTickCount = xTaskGetTickCount(); /* Convert the ticks to milliseconds. */ ulTimeMs = ( uint32_t ) xTickCount * azureiotMILLISECONDS_PER_TICK; return ulTimeMs; } /*-----------------------------------------------------------*/ AzureIoTResult_t AzureIoTProvisioningClient_OptionsInit( AzureIoTProvisioningClientOptions_t * pxProvisioningClientOptions ) { AzureIoTResult_t xResult; if( pxProvisioningClientOptions == NULL ) { AZLogError( ( "AzureIoTProvisioningClient_OptionsInit failed: invalid argument" ) ); xResult = eAzureIoTErrorInvalidArgument; } else { pxProvisioningClientOptions->pucUserAgent = ( const uint8_t * ) azureiotprovisioningUSER_AGENT; pxProvisioningClientOptions->ulUserAgentLength = sizeof( azureiotprovisioningUSER_AGENT ) - 1; xResult = eAzureIoTSuccess; } return xResult; } /*-----------------------------------------------------------*/ AzureIoTResult_t AzureIoTProvisioningClient_Init( AzureIoTProvisioningClient_t * pxAzureProvClient, const uint8_t * pucEndpoint, uint32_t ulEndpointLength, const uint8_t * pucIDScope, uint32_t ulIDScopeLength, const uint8_t * pucRegistrationID, uint32_t ulRegistrationIDLength, AzureIoTProvisioningClientOptions_t * pxProvisioningClientOptions, uint8_t * pucBuffer, uint32_t ulBufferLength, AzureIoTGetCurrentTimeFunc_t xGetTimeFunction, const AzureIoTTransportInterface_t * pxTransportInterface ) { AzureIoTResult_t xResult; az_span xEndpoint = az_span_create( ( uint8_t * ) pucEndpoint, ( int32_t ) ulEndpointLength ); az_span xIDScope = az_span_create( ( uint8_t * ) pucIDScope, ( int32_t ) ulIDScopeLength ); az_span xRegistrationID = az_span_create( ( uint8_t * ) pucRegistrationID, ( int32_t ) ulRegistrationIDLength ); az_result xCoreResult; AzureIoTMQTTResult_t xMQTTResult; az_iot_provisioning_client_options xOptions = az_iot_provisioning_client_options_default(); uint8_t * pucNetworkBuffer; uint32_t ulNetworkBufferLength; if( ( pxAzureProvClient == NULL ) || ( pucEndpoint == NULL ) || ( ulEndpointLength == 0 ) || ( pucIDScope == NULL ) || ( ulIDScopeLength == 0 ) || ( pucRegistrationID == NULL ) || ( ulRegistrationIDLength == 0 ) || ( pucBuffer == NULL ) || ( ulBufferLength == 0 ) || ( xGetTimeFunction == NULL ) || ( pxTransportInterface == NULL ) ) { AZLogError( ( "AzureIoTProvisioningClient_Init failed: invalid argument" ) ); xResult = eAzureIoTErrorInvalidArgument; } else if( ( ulBufferLength < ( azureiotconfigTOPIC_MAX + azureiotconfigPROVISIONING_REQUEST_PAYLOAD_MAX ) ) || ( ulBufferLength < ( azureiotconfigUSERNAME_MAX + azureiotconfigPASSWORD_MAX ) ) ) { AZLogError( ( "AzureIoTProvisioningClient_Init failed: insufficient buffer size" ) ); xResult = eAzureIoTErrorOutOfMemory; } else { memset( pxAzureProvClient, 0, sizeof( AzureIoTProvisioningClient_t ) ); /* Setup scratch buffer to be used by middleware */ pxAzureProvClient->_internal.ulScratchBufferLength = azureiotPrvGetMaxInt( ( azureiotconfigUSERNAME_MAX + azureiotconfigPASSWORD_MAX ), ( azureiotconfigTOPIC_MAX + azureiotconfigPROVISIONING_REQUEST_PAYLOAD_MAX ) ); pxAzureProvClient->_internal.pucScratchBuffer = pucBuffer; pucNetworkBuffer = pucBuffer + pxAzureProvClient->_internal.ulScratchBufferLength; ulNetworkBufferLength = ulBufferLength - pxAzureProvClient->_internal.ulScratchBufferLength; pxAzureProvClient->_internal.pucEndpoint = pucEndpoint; pxAzureProvClient->_internal.ulEndpointLength = ulEndpointLength; pxAzureProvClient->_internal.pucIDScope = pucIDScope; pxAzureProvClient->_internal.ulIDScopeLength = ulIDScopeLength; pxAzureProvClient->_internal.pucRegistrationID = pucRegistrationID; pxAzureProvClient->_internal.ulRegistrationIDLength = ulRegistrationIDLength; pxAzureProvClient->_internal.xGetTimeFunction = xGetTimeFunction; if( pxProvisioningClientOptions ) { xOptions.user_agent = az_span_create( ( uint8_t * ) pxProvisioningClientOptions->pucUserAgent, ( int32_t ) pxProvisioningClientOptions->ulUserAgentLength ); } else { xOptions.user_agent = az_span_create( ( uint8_t * ) azureiotprovisioningUSER_AGENT, sizeof( azureiotprovisioningUSER_AGENT ) - 1 ); } xCoreResult = az_iot_provisioning_client_init( &( pxAzureProvClient->_internal.xProvisioningClientCore ), xEndpoint, xIDScope, xRegistrationID, &xOptions ); if( az_result_failed( xCoreResult ) ) { AZLogError( ( "AzureIoTProvisioning initialization failed: core error=0x%08x", ( uint16_t ) xCoreResult ) ); xResult = AzureIoT_TranslateCoreError( xCoreResult ); } else if( ( xMQTTResult = AzureIoTMQTT_Init( &( pxAzureProvClient->_internal.xMQTTContext ), pxTransportInterface, prvProvClientGetTimeMillseconds, prvProvClientEventCallback, pucNetworkBuffer, ulNetworkBufferLength ) ) != eAzureIoTMQTTSuccess ) { AZLogError( ( "AzureIoTProvisioning initialization failed: MQTT error=0x%08x", ( uint16_t ) xMQTTResult ) ); xResult = eAzureIoTErrorInitFailed; } else { xResult = eAzureIoTSuccess; } } return xResult; } /*-----------------------------------------------------------*/ void AzureIoTProvisioningClient_Deinit( AzureIoTProvisioningClient_t * pxAzureProvClient ) { if( pxAzureProvClient == NULL ) { AZLogError( ( "AzureIoTProvisioningClient_Deinit failed: invalid argument" ) ); } } /*-----------------------------------------------------------*/ AzureIoTResult_t AzureIoTProvisioningClient_SetSymmetricKey( AzureIoTProvisioningClient_t * pxAzureProvClient, const uint8_t * pucSymmetricKey, uint32_t ulSymmetricKeyLength, AzureIoTGetHMACFunc_t xHmacFunction ) { AzureIoTResult_t xResult; if( ( pxAzureProvClient == NULL ) || ( pucSymmetricKey == NULL ) || ( ulSymmetricKeyLength == 0 ) || ( xHmacFunction == NULL ) ) { AZLogError( ( "AzureIoTProvisioningClient_SetSymmetricKey failed: invalid argument" ) ); xResult = eAzureIoTErrorInvalidArgument; } else { pxAzureProvClient->_internal.pucSymmetricKey = pucSymmetricKey; pxAzureProvClient->_internal.ulSymmetricKeyLength = ulSymmetricKeyLength; pxAzureProvClient->_internal.pxTokenRefresh = prvProvClientGetToken; pxAzureProvClient->_internal.xHMACFunction = xHmacFunction; xResult = eAzureIoTSuccess; } return xResult; } /*-----------------------------------------------------------*/ AzureIoTResult_t AzureIoTProvisioningClient_Register( AzureIoTProvisioningClient_t * pxAzureProvClient, uint32_t ulTimeoutMilliseconds ) { AzureIoTResult_t xResult; if( pxAzureProvClient == NULL ) { AZLogError( ( "AzureIoTProvisioningClient_Register failed: invalid argument" ) ); xResult = eAzureIoTErrorInvalidArgument; } else { if( pxAzureProvClient->_internal.ulWorkflowState == azureiotprovisioningWF_STATE_INIT ) { pxAzureProvClient->_internal.ulWorkflowState = azureiotprovisioningWF_STATE_CONNECT; } xResult = prvProvClientRunWorkflow( pxAzureProvClient, ulTimeoutMilliseconds ); } return xResult; } /*-----------------------------------------------------------*/ AzureIoTResult_t AzureIoTProvisioningClient_GetDeviceAndHub( AzureIoTProvisioningClient_t * pxAzureProvClient, uint8_t * pucHubHostname, uint32_t * pulHostnameLength, uint8_t * pucDeviceID, uint32_t * pulDeviceIDLength ) { uint32_t ulHostnameLength; uint32_t ulDeviceIDLength; AzureIoTResult_t xResult; az_span * pxHostname; az_span * pxDeviceID; if( ( pxAzureProvClient == NULL ) || ( pucHubHostname == NULL ) || ( pulHostnameLength == NULL ) || ( pucDeviceID == NULL ) || ( pulDeviceIDLength == NULL ) ) { AZLogError( ( "AzureIoTProvisioningClient_GetDeviceAndHub failed: invalid argument" ) ); xResult = eAzureIoTErrorInvalidArgument; } else if( pxAzureProvClient->_internal.ulWorkflowState != azureiotprovisioningWF_STATE_COMPLETE ) { AZLogError( ( "AzureIoTProvisioning client state is not in complete state" ) ); xResult = eAzureIoTErrorFailed; } else if( pxAzureProvClient->_internal.ulLastOperationResult ) { xResult = pxAzureProvClient->_internal.ulLastOperationResult; } else { pxHostname = &pxAzureProvClient->_internal.xRegisterResponse.registration_state.assigned_hub_hostname; pxDeviceID = &pxAzureProvClient->_internal.xRegisterResponse.registration_state.device_id; ulHostnameLength = ( uint32_t ) az_span_size( *pxHostname ); ulDeviceIDLength = ( uint32_t ) az_span_size( *pxDeviceID ); if( ( *pulHostnameLength < ulHostnameLength ) || ( *pulDeviceIDLength < ulDeviceIDLength ) ) { AZLogWarn( ( "AzureIoTProvisioning memory buffer passed is not enough to store hub info" ) ); xResult = eAzureIoTErrorFailed; } else { memcpy( pucHubHostname, az_span_ptr( *pxHostname ), ulHostnameLength ); memcpy( pucDeviceID, az_span_ptr( *pxDeviceID ), ulDeviceIDLength ); *pulHostnameLength = ulHostnameLength; *pulDeviceIDLength = ulDeviceIDLength; xResult = eAzureIoTSuccess; } } return xResult; } /*-----------------------------------------------------------*/ AzureIoTResult_t AzureIoTProvisioningClient_GetExtendedCode( AzureIoTProvisioningClient_t * pxAzureProvClient, uint32_t * pulExtendedErrorCode ) { AzureIoTResult_t xResult; if( ( pxAzureProvClient == NULL ) || ( pulExtendedErrorCode == NULL ) ) { AZLogError( ( "AzureIoTProvisioningClient_GetExtendedCode failed: invalid argument" ) ); xResult = eAzureIoTErrorInvalidArgument; } else if( pxAzureProvClient->_internal.ulWorkflowState != azureiotprovisioningWF_STATE_COMPLETE ) { AZLogError( ( "AzureIoTProvisioning client state is not in complete state" ) ); xResult = eAzureIoTErrorFailed; } else { *pulExtendedErrorCode = pxAzureProvClient->_internal.xRegisterResponse.registration_state.extended_error_code; xResult = eAzureIoTSuccess; } return xResult; } /*-----------------------------------------------------------*/ AzureIoTResult_t AzureIoTProvisioningClient_SetRegistrationPayload( AzureIoTProvisioningClient_t * pxAzureProvClient, const uint8_t * pucPayload, uint32_t ulPayloadLength ) { AzureIoTResult_t xResult; if( ( pxAzureProvClient == NULL ) || ( pucPayload == NULL ) || ( ulPayloadLength == 0 ) ) { AZLogError( ( "AzureIoTProvisioningClient_SetRegistrationPayload failed: invalid argument" ) ); xResult = eAzureIoTErrorInvalidArgument; } else if( pxAzureProvClient->_internal.ulWorkflowState != azureiotprovisioningWF_STATE_INIT ) { AZLogError( ( "AzureIoTProvisioning client state is not in init" ) ); xResult = eAzureIoTErrorFailed; } else { pxAzureProvClient->_internal.pucRegistrationPayload = pucPayload; pxAzureProvClient->_internal.ulRegistrationPayloadLength = ulPayloadLength; xResult = eAzureIoTSuccess; } return xResult; } /*-----------------------------------------------------------*/