From 06cf49ad3e0bf3e90082f665cdfdfcc797ab810d Mon Sep 17 00:00:00 2001 From: Ilyas Lok-ayba Date: Fri, 25 Jul 2025 16:12:48 +0200 Subject: [PATCH] Deploy greengrass_discovery feature for automatic local Greengrass Core Device connection --- .../greengrass/greengrass_discovery_task.c | 742 ++++++++++++++++++ .../greengrass/greengrass_discovery_task.h | 15 + .../x509_crt_ip_addr_san_verif.patch | 271 +++++++ Common/app/mqtt/mqtt_agent_task.c | 28 +- Common/app/mqtt/mqtt_agent_task.h | 9 + Common/app/mqtt/mqtt_connection_task.c | 144 ++++ Common/app/mqtt/mqtt_connection_task.h | 9 + Common/config/kvstore_config.h | 14 + Common/include/sys_evt.h | 2 + Documentation/Pictures/CLI.png | Bin 0 -> 8949 bytes .../Pictures/mqtt_agent_sequence_diagram.png | Bin 0 -> 25416 bytes ...qtt_agent_sequence_diagram_alternative.png | Bin 0 -> 24085 bytes Documentation/Pictures/sequence_diagram.png | Bin 0 -> 20003 bytes Documentation/Pictures/state_machine.png | Bin 0 -> 73587 bytes Documentation/Readme.md | 104 +++ Documentation/Src/gg_sequence_diagram.puml | 31 + .../Src/mqtt_agent_sequence_diagram.puml | 31 + ...tt_agent_sequence_diagram_alternative.puml | 22 + Documentation/Src/sequence_diagram.puml | 20 + Documentation/Src/state_machine.puml | 70 ++ .../Src/state_machine_sequence_diagram.puml | 44 ++ Projects/b_u585i_iot02a_ntz/.cproject | 180 ++--- Projects/b_u585i_iot02a_ntz/.project | 5 + .../Inc/core_pkcs11_config.h | 3 + .../Inc/mbedtls_config_ntz.h | 14 + .../Inc/tls_transport_config.h | 1 + Projects/b_u585i_iot02a_ntz/Src/app_main.c | 8 +- .../Src/crypto/core_pkcs11_pal_utils.c | 13 + .../Src/crypto/core_pkcs11_pal_utils.h | 1 + 29 files changed, 1676 insertions(+), 105 deletions(-) create mode 100644 Common/app/greengrass/greengrass_discovery_task.c create mode 100644 Common/app/greengrass/greengrass_discovery_task.h create mode 100644 Common/app/greengrass/x509_crt_ip_addr_san_verif.patch create mode 100644 Common/app/mqtt/mqtt_connection_task.c create mode 100644 Common/app/mqtt/mqtt_connection_task.h create mode 100644 Documentation/Pictures/CLI.png create mode 100644 Documentation/Pictures/mqtt_agent_sequence_diagram.png create mode 100644 Documentation/Pictures/mqtt_agent_sequence_diagram_alternative.png create mode 100644 Documentation/Pictures/sequence_diagram.png create mode 100644 Documentation/Pictures/state_machine.png create mode 100644 Documentation/Readme.md create mode 100644 Documentation/Src/gg_sequence_diagram.puml create mode 100644 Documentation/Src/mqtt_agent_sequence_diagram.puml create mode 100644 Documentation/Src/mqtt_agent_sequence_diagram_alternative.puml create mode 100644 Documentation/Src/sequence_diagram.puml create mode 100644 Documentation/Src/state_machine.puml create mode 100644 Documentation/Src/state_machine_sequence_diagram.puml diff --git a/Common/app/greengrass/greengrass_discovery_task.c b/Common/app/greengrass/greengrass_discovery_task.c new file mode 100644 index 000000000..1c7dd527f --- /dev/null +++ b/Common/app/greengrass/greengrass_discovery_task.c @@ -0,0 +1,742 @@ +/** + ****************************************************************************** + * @file : greengrass_discovery_task.c + * @brief : Task implementation for AWS Greengrass discovery + ****************************************************************************** + * @attention + * + * Copyright (c) 2025 STMicroelectronics. + * All rights reserved. + * + * This software is licensed under terms that can be found in the LICENSE file + * in the root directory of this software component. + * If no LICENSE file comes with this software, it is provided AS-IS. + * + ****************************************************************************** + */ + +#include "greengrass_discovery_task.h" + +#include "logging_levels.h" +#define LOG_LEVEL LOG_INFO +#include "logging.h" + +/* Standard includes. */ +#include +#include +#include + +/* Kernel includes. */ +#include "FreeRTOS.h" +#include "sys_evt.h" +#include "task.h" +#include "kvstore.h" + +/*coreHTTP includes*/ +#include "core_http_client.h" + +/* tls library includes. */ +#include "tls_transport_config.h" +#include "mbedtls_transport.h" + +/* Includes to handle reception errors*/ +#include "mbedtls/net_sockets.h" // for MBEDTLS_ERR_NET_CONN_RESET +#include "mbedtls/ssl.h" // for MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY + +/* Include for parameters*/ +#include "kvstore_config.h" + +/*cJSON parsing library*/ +#include "core_json.h" + +#define HEADER_BUFFER_LENGTH 1024 +#define RESPONSE_BUFFER_LENGTH 2048 +#define TIMEOUT_TRANSPORT_AFTER_FIRST_RCV_MS 200 +#define TIMEOUT_TRANSPORT_FIRST_RCV_MS 5000 +#define DELAY_TRANSPORT_RCV_MS 10 +#define PATH_BUFFER_LENGHT 128 +#define REGION_BUF_SIZE 38 +#define GG_DISCOVERY_URL_SIZE 128 +#define DISCOVERY_PORT 8443 + +/* Greengrass config queries */ +#define HOST_ADDRESS_JSON_QUERY_STR "GGGroups[0].Cores[0].Connectivity[0].HostAddress" //Use the HostAdrress of the first Connectivity info of the first Cores of the first GGroup +#define PORT_JSON_QUERY_STR "GGGroups[0].Cores[0].Connectivity[0].PortNumber" //Use the PortNumber of the first Connectivity of the first Cores of the first GGroup +#define CA_JSON_QUERY_STR "GGGroups[0].CAs[0]" // Use the first CA in CAs of the the first GGGroup + +/* Labels for writing GG config in NVM */ +#define GG_ENDPOINT_LABEL "gg_endpoint" +#define GG_PORT_LABEL "gg_port" + +/* ALPN protocols must be a NULL-terminated list of strings. */ +static const char * pcAlpnProtocols[] = { NULL}; + + +/* The transport interface send function. */ +static int32_t transportSend(NetworkContext_t *pNetworkContext, const void *pBuffer, size_t bytesToSend) +{ + LogDebug( "Http discovery request \n%.*s", bytesToSend, pBuffer ); + return mbedtls_transport_send(pNetworkContext, pBuffer, bytesToSend); +} + +/* The transport interface receive function. +** loop to receive data until having a response */ +static int32_t transportRecv(NetworkContext_t *pNetworkContext, void *pBuffer, size_t bufferSize) +{ + int32_t receivedBytes = 0; + int32_t totalReceived = 0; + int32_t result = 0; + TickType_t xTimeout = pdMS_TO_TICKS(TIMEOUT_TRANSPORT_FIRST_RCV_MS); + TickType_t xStartTime = xTaskGetTickCount(); + TickType_t xElapsedTime = 0; + + while (totalReceived < bufferSize ) + { + receivedBytes = mbedtls_transport_recv(pNetworkContext, pBuffer, bufferSize - totalReceived); + + if (receivedBytes < 0) + { + LogError("mbedtls_transport_recv failed with error code: %d", receivedBytes); + result = receivedBytes; + break; + } + else if (receivedBytes == 0) + { + // No data received, check if timeout has occurred + xElapsedTime = xTaskGetTickCount() - xStartTime; + if (xElapsedTime >= xTimeout) + { + result = totalReceived; + break; // Exit the loop if the maximum delay has been reached + } + vTaskDelay(pdMS_TO_TICKS(DELAY_TRANSPORT_RCV_MS)); + } + else if (receivedBytes > 0) + { + xTimeout = pdMS_TO_TICKS(TIMEOUT_TRANSPORT_AFTER_FIRST_RCV_MS); + xStartTime = xTaskGetTickCount(); + totalReceived += receivedBytes; + pBuffer = (uint8_t *)pBuffer + receivedBytes; + result = totalReceived; + } + } + + return result; +} + +/** + * @brief Extracts the AWS region from the given endpoint and formats a Greengrass endpoint URL. + * + * This function extracts the region from the provided AWS IoT endpoint and constructs + * a Greengrass endpoint URL (GGDisccoveryURL) using the extracted region. The region is dynamically determined + * from the endpoint, avoiding hardcoding the region. + * + * @param[out] GGDisccoveryURL The buffer where the formatted Greengrass endpoint will be stored. + * This buffer should be pre-allocated and large enough to hold the resulting URL. + * @param[in] endpoint The endpoint string from which the region will be extracted. + * This should be a valid AWS IoT endpoint in the format "something.iot..amazonaws.com". + * + * @return HTTPStatus_t The status of the operation. + * - HTTPSuccess if the region is successfully extracted and the Greengrass endpoint is formatted. + * - HTTPInvalidParameter if the region extraction fails. + * - HTTPInsufficientMemory if memory allocation fails. + */ +HTTPStatus_t create_discovery_url_from_endpoint(char * GGDisccoveryURL , const char * endpoint) +{ + HTTPStatus_t xHTTPStatus = HTTPSuccess; + + if (endpoint == NULL || GGDisccoveryURL == NULL) { + LogError("Endpoint or GGDisccoveryURL cannot be NULL"); + xHTTPStatus = HTTPInvalidParameter; + } + else + { + const char* sep = "-ats.iot."; + char * subStr = strstr ( endpoint, sep ); + + if (subStr == NULL) { + LogError("Separator (%s) not found in endpoint (%s).", sep, endpoint); + xHTTPStatus = HTTPInvalidParameter; + } + else + { + subStr += strlen(sep); + + if (strlen(subStr) + strlen("greengrass-ats.iot.") < GG_DISCOVERY_URL_SIZE) { + snprintf(GGDisccoveryURL, GG_DISCOVERY_URL_SIZE, "greengrass-ats.iot.%s", subStr); + } else + { + size_t resultingSize = strlen(subStr) + strlen("greengrass-ats.iot."); + LogError("Resulting GG URL string would be too long. Max size: %d, Actual size: %zu", GG_DISCOVERY_URL_SIZE, resultingSize); + xHTTPStatus = HTTPInsufficientMemory; + } + } + } + return xHTTPStatus; +} + +/** + * @brief Extracts a value from a JSON response based on a specified JSON path. + * + * This function searches for a value in the JSON response using a given path and validates + * that the extracted value matches the expected JSON type. + * + * @param[in] pResponse The JSON document as a string. + * @param[in] responseLen The length of the JSON document. + * @param[in] path The JSON path to the desired value. + * @param[out] pValue A pointer to the extracted value as a string. + * @param[out] pValueLen The length of the extracted value. + * @param[in] expectedType The expected JSON type of the value (e.g., JSONString for CA and HostAddress, JSONNumber for PortNumber). + * + * @return JSONStatus_t Returns JSONSuccess if the value is successfully extracted and matches the expected type. + * Returns JSONNotFound if the value is not found or does not match the expected type. + * + */ +JSONStatus_t extractJsonValue(const char *pResponse, size_t responseLen, const char *path, char **pValue, size_t *pValueLen, JSONTypes_t expectedType) +{ + JSONStatus_t jsonStatus; + JSONTypes_t valueType; + + // Validate the JSON document + jsonStatus = JSON_Validate(pResponse, responseLen); + if (jsonStatus != JSONSuccess) + { + LogError("Failed to validate JSON response (jsonStatus: %d).", jsonStatus); + } + + //Extract the value + if (jsonStatus == JSONSuccess) + { + jsonStatus = JSON_SearchT(pResponse, responseLen, path, strlen(path), pValue, pValueLen, &valueType); + if (jsonStatus != JSONSuccess || valueType != expectedType) + { + LogError("%s not found or type error (jsonStatus: %d, valueType: %d).", path, jsonStatus, valueType); + } + } + + return jsonStatus; +} + +/** + * @brief Parses a JSON response to extract MQTT endpoint, certificate, and port number. + * + * This function takes a JSON response containing information about Greengrass core device, + * parses it to extract the MQTT endpoint, ca certificate, and port number. + * + * @param[in] pResponse Pointer to the JSON response string. + * @param[in] responseLen Length of the JSON response string. + * @param[out] pIpAddress Pointer to a char pointer where the extracted endpoint will be stored. + * @param[out] pCertificate Pointer to a char pointer where the extracted certificate will be stored. + * @param[out] pPort Pointer to a uint16_t where the extracted port number will be stored. + * + * @return JSONStatus_t Returns JSONSuccess on successful parsing and extraction, or an error code + * indicating the type of failure (e.g., parsing error, object not found, memory allocation failure). + */ +JSONStatus_t parseGGDiscoveryResponse(const char *pResponse, + size_t responseLen, + char **pIpAddress, + size_t *pIpAddressLen, + char **pCertificate, + size_t *pCertificateLen, + uint16_t *pPort) +{ + JSONStatus_t jsonStatus = JSONSuccess; + + // Extract HostAddress + jsonStatus = extractJsonValue(pResponse, responseLen, HOST_ADDRESS_JSON_QUERY_STR, pIpAddress, pIpAddressLen, JSONString); + if (jsonStatus != JSONSuccess) + { + LogError("Failed to extract HostAddress from JSON response" ); + } + + // Extract PortNumber + if (jsonStatus == JSONSuccess) + { + const char *portValue; + size_t portSize; + jsonStatus = extractJsonValue(pResponse, responseLen, PORT_JSON_QUERY_STR, &portValue, &portSize, JSONNumber); + if (jsonStatus != JSONSuccess) + { + LogError("Failed to extract PortNumber from JSON response" ); + } + *pPort = (uint16_t)strtol(portValue, NULL, 10); + } + + // Extract CA + if (jsonStatus == JSONSuccess){ + jsonStatus = extractJsonValue(pResponse, responseLen, CA_JSON_QUERY_STR, pCertificate, pCertificateLen, JSONString); + if (jsonStatus != JSONSuccess) + { + LogError("Failed to extract CA from JSON response" ); + } + } + + return jsonStatus; +} + +/** + * @brief Writes a CA certificate, endpoint, and port into NVM under specified labels. + * + * This function stores the provided CA certificate, Greengrass endpoint, and port number + * into Non-Volatile Memory (NVM) using the given labels. + * + * @param[in] CaLabel Pointer to the string containing the label under which the certificate will be stored. + * @param[in] certificate Pointer to the string containing the CA certificate in PEM format. + * @param[in] GGendpoint Pointer to the string containing the Greengrass endpoint. + * @param[in] ggPort The port number for the Greengrass connection. + * + * @return PkiStatus_t Returns PKI_SUCCESS on successful parsing and writing of the certificate, + * endpoint, and port, or an error code indicating the type of failure. + */ +BaseType_t Write_GG_config_into_NVM(const char *certificate, + const char *GGendpoint, + uint16_t ggPort ) +{ + BaseType_t xResult = pdTRUE; + + /* Write the gg certificate to NVM */ + if(xResult == pdTRUE){ + /* init the cert context */ + mbedtls_x509_crt xCertContext; + mbedtls_x509_crt_init(&xCertContext); + + int ret = mbedtls_x509_crt_parse(&xCertContext, + (const unsigned char *)certificate, + strlen(certificate) + 1); + if (ret != 0) + { + LogError("Failed to parse certificate. mbedtls_x509_crt_parse returned -0x%x", -ret); + xResult = pdFALSE; + } + else{ + + PkiStatus_t status = xPkiWriteCertificate(TLS_ROOT_GG_CA_CERT_LABEL, &xCertContext); + if (status == PKI_SUCCESS) + { + LogDebug("Success: CA Certificate loaded to label"); + } + else + { + LogError("Error: Failed to save certificate to label"); + xResult = pdFALSE; + } + } + mbedtls_x509_crt_free(&xCertContext); + } + + /* Write the gg endpoint to NVM */ + if (xResult == pdTRUE) + { + KVStoreKey_t xKey_endpoint = kvStringToKey( GG_ENDPOINT_LABEL ); + + xResult = KVStore_setString( xKey_endpoint, GGendpoint ); + if(xResult == pdTRUE){ + LogDebug("Success: greengrass endpoint loaded in NVM"); + } + else + { + LogError("Error: Failed to save gg endpoint to NVM (code %d)", xResult); + } + } + + /* Write the gg port to NVM */ + if (xResult == pdTRUE) + { + KVStoreKey_t xKey_port = kvStringToKey( GG_PORT_LABEL ); + + xResult = KVStore_setUInt32( xKey_port, ggPort ); + if(xResult == pdTRUE){ + LogDebug("Success: greengrass port loaded in NVM"); + } + else + { + LogError("Error: Failed to save gg port to NVM (code %d)", xResult); + } + } + + /* commit changes to NVM */ + if ( xResult == pdTRUE ) + { + xResult = KVStore_xCommitChanges(); + if( xResult == pdTRUE ) + { + LogDebug( "Configuration saved to NVM." ); + } + else + { + LogError( "Error: Could not save configuration to NVM (code %d).", xResult ); + } + } + + return xResult; +} + +/** + * Replaces all found instances of the passed substring in the passed string. + * + * @param orig The string in which to look + * @param rep The substring with which to replace the found substrings + * @param with The substring to look for + * + * @return A new string with the search/replacement performed + * + * @note The caller of the function should ensure to free the memory + * allocated for the returned string. + **/ +char *str_replace(const char *orig, const char *rep, const char *with) { + char *result = NULL; // the return string + BaseType_t xResult = pdTRUE; + char *ins; // the next insert point + char *tmp; // varies + int len_rep; // length of rep (the string to remove) + int len_with; // length of with (the string to replace rep with) + int len_front; // distance between rep and end of last rep + int count; // number of replacements + + // sanity checks and initialization + if (!orig || !rep){ + LogError("Original string or substring to replace is NULL."); + xResult = pdFALSE; + } + + if (xResult == pdTRUE) + { + len_rep = strlen(rep); + if (len_rep == 0){ + LogError("Substring to replace is empty, cannot proceed."); + xResult = pdFALSE; + } + } + + if (xResult == pdTRUE) + { + if (!with){ + with = ""; + LogWarn("Replacement substring is NULL, using empty string instead."); + } + + len_with = strlen(with); + + // count the number of replacements needed + ins = orig; + for (count = 0; (tmp = strstr(ins, rep)); ++count) { + ins = tmp + len_rep; + } + + tmp = result = pvPortMalloc(strlen(orig) + (len_with - len_rep) * count + 1); + + if (!result){ + LogError("Memory allocation failed. Required size: %zu", (strlen(orig) + (len_with - len_rep) * count + 1)); + xResult = pdFALSE; + } + + if(xResult == pdTRUE){ + // first time through the loop, all the variable are set correctly + // from here on, + // tmp points to the end of the result string + // ins points to the next occurrence of rep in orig + // orig points to the remainder of orig after "end of rep" + while (count--) { + ins = strstr(orig, rep); + len_front = ins - orig; + tmp = strncpy(tmp, orig, len_front) + len_front; + tmp = strcpy(tmp, with) + len_with; + orig += len_front + len_rep; // move to next "end of rep" + } + strcpy(tmp, orig); + } + } + + return result; +} + +/** + * @brief Formats the parsed certificate. + * + * This function replaces escaped newlines (\\n) in the certificate with newlines (\n) to ensure proper + * formatting. + * + * @param parsedCertificate Pointer to the buffer containing the parsed certificate. + * @return Pointer to a copy of the formatted certificate. + * + * @note The caller of the function should ensure to free the memory + * allocated for the returned certificate. + * + */ +char * copyAndFormatCertificateForNVM(const char *parsedCertificate) { + + char * formatedCertificate = str_replace(parsedCertificate, "\\n", "\n"); + return formatedCertificate; + +} + +/* Discovery Task*/ +void vGGDiscoveryTask( void * pvParameters ) +{ + HTTPStatus_t xHTTPStatus = HTTPSuccess; + TlsTransportStatus_t xTlsStatus = TLS_TRANSPORT_CONNECT_FAILURE; + size_t uxTempSize = 0; + + NetworkContext_t * pxNetworkContext = NULL; + LogInfo("Starting discovery"); + + PkiObject_t xPrivateKey = xPkiObjectFromLabel( TLS_KEY_PRV_LABEL ); + PkiObject_t xClientCertificate = xPkiObjectFromLabel( TLS_CERT_LABEL ); + PkiObject_t pxRootCaChain[ 1 ] = { xPkiObjectFromLabel( TLS_ROOT_CA_CERT_LABEL ) }; + + const char *endpoint = KVStore_getStringHeap( CS_CORE_MQTT_ENDPOINT, &uxTempSize ); + uint16_t discovery_port = DISCOVERY_PORT; + + GGDiscoveryContext *pGGContext = ( GGDiscoveryContext *) pvParameters; + + /* Extract region from endPoint */ + char *GGDisccoveryURL = (char *)pvPortMalloc(GG_DISCOVERY_URL_SIZE); + if (GGDisccoveryURL == NULL) + { + LogError( "Failed to allocate memory for GGDisccoveryURL." ); + xHTTPStatus = HTTPInsufficientMemory; + }else + { + xHTTPStatus = create_discovery_url_from_endpoint(GGDisccoveryURL , endpoint); + } + + pxNetworkContext = mbedtls_transport_allocate(); + + if( pxNetworkContext == NULL ) + { + LogError( "Failed to allocate mbedtls transport context." ); + xHTTPStatus = HTTPInsufficientMemory; + } + + if( xHTTPStatus == HTTPSuccess ) + { + xTlsStatus = mbedtls_transport_configure( pxNetworkContext, + pcAlpnProtocols, + &xPrivateKey, + &xClientCertificate, + pxRootCaChain, + 1 ); + + if( xTlsStatus != TLS_TRANSPORT_SUCCESS ) + { + LogError( "Failed to configure mbedtls transport." ); + xHTTPStatus = HTTPInvalidParameter; + } + } + + xTlsStatus = TLS_TRANSPORT_UNKNOWN_ERROR; + + if( xHTTPStatus == HTTPSuccess ) + { + + /* Block until the network interface is connected */ + ( void ) xEventGroupWaitBits( xSystemEvents, + EVT_MASK_NET_CONNECTED, + 0x00, + pdTRUE, + portMAX_DELAY ); + + LogInfo( "Connecting to Greengrass discovery server %s:%d.", + GGDisccoveryURL, discovery_port ); + + xTlsStatus = mbedtls_transport_connect( pxNetworkContext, + GGDisccoveryURL, + discovery_port, + 0, 0 ); + if( xTlsStatus == TLS_TRANSPORT_SUCCESS ){ + LogInfo( "Connected to HTTP server." ); + + HTTPRequestInfo_t requestInfo; + HTTPRequestHeaders_t requestHeaders; + HTTPResponse_t response; + uint8_t *responseBuffer = NULL; + TransportInterface_t transportInterface; + const char *thing_name; + + ( void ) memset( &response, 0, sizeof( response ) ); + + /* Allocate memory for the header buffer on the heap. */ + uint8_t *headerBuffer = ( uint8_t * ) pvPortMalloc( HEADER_BUFFER_LENGTH ); + if ( headerBuffer == NULL ) + { + LogError( "Failed to allocate memory for header buffer." ); + xHTTPStatus = HTTPInsufficientMemory; + } + + /* Initialize the HTTP request information. */ + if ( xHTTPStatus == HTTPSuccess ) + { + /* Get thing name from KVStore*/ + thing_name = KVStore_getStringHeap( CS_CORE_THING_NAME, &uxTempSize ); + if( thing_name == NULL || uxTempSize == 0 ) + { + LogError( "Invalid client identifier read from KVStore." ); + xHTTPStatus = HTTPInvalidParameter; + } + } + + /* Initialize the HTTP request headers. */ + if ( xHTTPStatus == HTTPSuccess ) + { + char pathBuffer[PATH_BUFFER_LENGHT]; + snprintf(pathBuffer, sizeof(pathBuffer), "/greengrass/discover/thing/%s", thing_name); + + requestInfo.pHost = GGDisccoveryURL; + requestInfo.hostLen = strlen( GGDisccoveryURL ); + requestInfo.pMethod = HTTP_METHOD_GET; + requestInfo.methodLen = strlen( HTTP_METHOD_GET ); + requestInfo.pPath = pathBuffer; + requestInfo.pathLen = strlen(pathBuffer); + + requestHeaders.pBuffer = headerBuffer; + requestHeaders.bufferLen = HEADER_BUFFER_LENGTH; + + xHTTPStatus = HTTPClient_InitializeRequestHeaders( &requestHeaders, &requestInfo ); + if ( xHTTPStatus != HTTPSuccess ) + { + LogError( "Failed to initialize HTTP request headers." ); + } + } + + /* Allocate memory for the response buffer on the heap. */ + if ( xHTTPStatus == HTTPSuccess ) + { + responseBuffer = ( uint8_t * ) pvPortMalloc( RESPONSE_BUFFER_LENGTH ); + if ( responseBuffer == NULL ) + { + LogError( "Failed to allocate memory for response buffer." ); + xHTTPStatus = HTTPInsufficientMemory; + } + } + + /* Send the HTTP GET request. */ + if ( xHTTPStatus == HTTPSuccess ) + { + /* Initialize the transport interface. */ + transportInterface.pNetworkContext = pxNetworkContext; + transportInterface.send = transportSend; + transportInterface.recv = transportRecv; + + /* Initialize the HTTP response. */ + response.pBuffer = responseBuffer; + response.bufferLen = RESPONSE_BUFFER_LENGTH; + + xHTTPStatus = HTTPClient_Send( &transportInterface, &requestHeaders, + NULL, 0, &response, 0 ); + if ( xHTTPStatus != HTTPSuccess ) + { + LogError( ( "Failed to send HTTP request: Error=%s.", + HTTPClient_strerror( xHTTPStatus ) ) ); + } + } + + /* Parse and write JSON reposne into the NVM */ + if ( xHTTPStatus == HTTPSuccess ) + { + /* Print the HTTP response. */ + LogInfo( "HTTP Response: %.*s\n", ( int ) response.bodyLen, response.pBody ); + + /* Parse the JSON response */ + char *p_parsedEndPoint = NULL; + char *p_parsedCertificate = NULL; + size_t parsedEndPointLen; + size_t parsedCertificateLen; + uint16_t mqttPort = 0; + BaseType_t xStatus = pdTRUE; + + JSONStatus_t parseResult = parseGGDiscoveryResponse((const char *)response.pBody, + response.bodyLen, + &p_parsedEndPoint, + &parsedEndPointLen, + &p_parsedCertificate, + &parsedCertificateLen, + &mqttPort); + + if (parseResult == JSONSuccess) + { + LogDebug("Extracted mqtt endpoint: %.*s", parsedEndPointLen, p_parsedEndPoint); + LogDebug("Extracted mqtt broker port: %d", mqttPort); + LogDebug("Extracted certificate: %.*s", parsedCertificateLen, p_parsedCertificate); + + LogInfo("Successfully Parsed Json response"); + + // Allocate memory and copy the parsed values + char *endPointCopy = (char *)pvPortMalloc(parsedEndPointLen); + char *certificateCopy = NULL; + + if (endPointCopy == NULL ) + { + LogError("Memory allocation failed for endpoint."); + xStatus = pdFALSE; + } + + if( xStatus == pdTRUE ){ + + /* Copy the parsed values to ensure the original JSON document remains unaltered if needed later */ + strncpy(endPointCopy, p_parsedEndPoint, parsedEndPointLen); + + /* Replace \\n newlines with actual \n newlines in certificate */ + certificateCopy = copyAndFormatCertificateForNVM(p_parsedCertificate); + + /* Write received GreenGrass config params into NVM */ + xStatus = Write_GG_config_into_NVM(certificateCopy, + endPointCopy, + mqttPort); + } + + if(xStatus == pdTRUE) + { + LogInfo("Successfully stored GG config into NVM"); + pGGContext->ggDiscoverySuccess = true; + } + else{ + LogError("Failed to store GG config into NVM"); + pGGContext->ggDiscoverySuccess = false; + } + + vPortFree(endPointCopy); + + if(certificateCopy != NULL){ + vPortFree(certificateCopy); + } + } + else + { + LogError("Failed to parse JSON response with error code: %d", parseResult); + } + } + + /* Free the allocated memory for the header buffer. */ + if (headerBuffer != NULL) { + vPortFree(headerBuffer); + } + /* Free the allocated memory for the response buffer. */ + if (responseBuffer != NULL) + { + vPortFree( responseBuffer ); + } + } + else + { + LogError( "Failed to connect to http server." ); + } + } + if( pxNetworkContext != NULL ) + { + + mbedtls_transport_disconnect( pxNetworkContext ); + mbedtls_transport_free( pxNetworkContext ); + pxNetworkContext = NULL; + ( void ) xEventGroupClearBits( xSystemEvents, EVT_MASK_MQTT_AGENT_FINISHED ); + } + if(GGDisccoveryURL != NULL){ + vPortFree (GGDisccoveryURL); + } + LogInfo("Greengrass Discovery done"); + + xEventGroupSetBits(xSystemEvents, EVT_MASK_GG_DISCOVERY_PERFORMED); + + LogInfo("Terminating GGDiscovery Task."); + + vTaskDelete( NULL ); +} diff --git a/Common/app/greengrass/greengrass_discovery_task.h b/Common/app/greengrass/greengrass_discovery_task.h new file mode 100644 index 000000000..c7adb75ca --- /dev/null +++ b/Common/app/greengrass/greengrass_discovery_task.h @@ -0,0 +1,15 @@ +#ifndef _GREENGRASS_DISCOVERY_TASK_H_ +#define _GREENGRASS_DISCOVERY_TASK_H_ + +#include "FreeRTOS.h" +#include + +typedef struct +{ + bool ggDiscoverySuccess; /* Boolean flag indicating if the GG Discovery was successful */ +} GGDiscoveryContext; + +void vGGDiscoveryTask( void * pvParameters ); + + +#endif /* ifndef _GREENGRASS_DISCOVERY_TASK_H_ */ diff --git a/Common/app/greengrass/x509_crt_ip_addr_san_verif.patch b/Common/app/greengrass/x509_crt_ip_addr_san_verif.patch new file mode 100644 index 000000000..7cfdd68e8 --- /dev/null +++ b/Common/app/greengrass/x509_crt_ip_addr_san_verif.patch @@ -0,0 +1,271 @@ +diff --git a/Middleware/ARM/mbedtls/library/x509_crt.c b/Middleware/ARM/mbedtls/library/x509_crt.c +index c8654445dd..7b90a4858f 100644 +--- a/Middleware/ARM/mbedtls/library/x509_crt.c ++++ b/Middleware/ARM/mbedtls/library/x509_crt.c +@@ -2996,9 +2996,258 @@ static int x509_crt_check_cn( const mbedtls_x509_buf *name, + return( -1 ); + } + ++#ifdef MBEDTLS_CUSTOM_SAN_IP_VERIF ++ /* Use whether or not AF_INET6 is defined to indicate whether or not to use ++ * the platform inet_pton() or a local implementation (below). The local ++ * implementation may be used even in cases where the platform provides ++ * inet_pton(), e.g. when there are different includes required and/or the ++ * platform implementation requires dependencies on additional libraries. ++ * Specifically, Windows requires custom includes and additional link ++ * dependencies, and Solaris requires additional link dependencies. ++ * Also, as a coarse heuristic, use the local implementation if the compiler ++ * does not support __has_include(), or if the definition of AF_INET6 is not ++ * provided by headers included (or not) via __has_include() above. ++ * MBEDTLS_TEST_SW_INET_PTON is a bypass define to force testing of this code //no-check-names ++ * despite having a platform that has inet_pton. */ ++#if !defined(AF_INET6) || defined(MBEDTLS_TEST_SW_INET_PTON) //no-check-names ++/* Definition located further below to possibly reduce compiler inlining */ ++static int x509_inet_pton_ipv4(const char *src, void *dst); ++ ++#define li_cton(c, n) \ ++ (((n) = (c) - '0') <= 9 || (((n) = ((c)&0xdf) - 'A') <= 5 ? ((n) += 10) : 0)) ++ ++static int x509_inet_pton_ipv6(const char *src, void *dst) ++{ ++ const unsigned char *p = (const unsigned char *) src; ++ int nonzero_groups = 0, num_digits, zero_group_start = -1; ++ uint16_t addr[8]; ++ do { ++ /* note: allows excess leading 0's, e.g. 1:0002:3:... */ ++ uint16_t group = num_digits = 0; ++ for (uint8_t digit; num_digits < 4; num_digits++) { ++ if (li_cton(*p, digit) == 0) { ++ break; ++ } ++ group = (group << 4) | digit; ++ p++; ++ } ++ if (num_digits != 0) { ++ MBEDTLS_PUT_UINT16_BE(group, addr, nonzero_groups); ++ nonzero_groups++; ++ if (*p == '\0') { ++ break; ++ } else if (*p == '.') { ++ /* Don't accept IPv4 too early or late */ ++ if ((nonzero_groups == 0 && zero_group_start == -1) || ++ nonzero_groups >= 7) { ++ break; ++ } ++ ++ /* Walk back to prior ':', then parse as IPv4-mapped */ ++ int steps = 4; ++ do { ++ p--; ++ steps--; ++ } while (*p != ':' && steps > 0); ++ ++ if (*p != ':') { ++ break; ++ } ++ p++; ++ nonzero_groups--; ++ if (x509_inet_pton_ipv4((const char *) p, ++ addr + nonzero_groups) != 0) { ++ break; ++ } ++ ++ nonzero_groups += 2; ++ p = (const unsigned char *) ""; ++ break; ++ } else if (*p != ':') { ++ return -1; ++ } ++ } else { ++ /* Don't accept a second zero group or an invalid delimiter */ ++ if (zero_group_start != -1 || *p != ':') { ++ return -1; ++ } ++ zero_group_start = nonzero_groups; ++ ++ /* Accept a zero group at start, but it has to be a double colon */ ++ if (zero_group_start == 0 && *++p != ':') { ++ return -1; ++ } ++ ++ if (p[1] == '\0') { ++ ++p; ++ break; ++ } ++ } ++ ++p; ++ } while (nonzero_groups < 8); ++ ++ if (*p != '\0') { ++ return -1; ++ } ++ ++ if (zero_group_start != -1) { ++ if (nonzero_groups > 6) { ++ return -1; ++ } ++ int zero_groups = 8 - nonzero_groups; ++ int groups_after_zero = nonzero_groups - zero_group_start; ++ ++ /* Move the non-zero part to after the zeroes */ ++ if (groups_after_zero) { ++ memmove(addr + zero_group_start + zero_groups, ++ addr + zero_group_start, ++ groups_after_zero * sizeof(*addr)); ++ } ++ memset(addr + zero_group_start, 0, zero_groups * sizeof(*addr)); ++ } else { ++ if (nonzero_groups != 8) { ++ return -1; ++ } ++ } ++ memcpy(dst, addr, sizeof(addr)); ++ return 0; ++} ++ ++static int x509_inet_pton_ipv4(const char *src, void *dst) ++{ ++ const unsigned char *p = (const unsigned char *) src; ++ uint8_t *res = (uint8_t *) dst; ++ uint8_t digit, num_digits = 0; ++ uint8_t num_octets = 0; ++ uint16_t octet; ++ ++ do { ++ octet = num_digits = 0; ++ do { ++ digit = *p - '0'; ++ if (digit > 9) { ++ break; ++ } ++ ++ /* Don't allow leading zeroes. These might mean octal format, ++ * which this implementation does not support. */ ++ if (octet == 0 && num_digits > 0) { ++ return -1; ++ } ++ ++ octet = octet * 10 + digit; ++ num_digits++; ++ p++; ++ } while (num_digits < 3); ++ ++ if (octet >= 256 || num_digits > 3 || num_digits == 0) { ++ return -1; ++ } ++ *res++ = (uint8_t) octet; ++ num_octets++; ++ } while (num_octets < 4 && *p++ == '.'); ++ return num_octets == 4 && *p == '\0' ? 0 : -1; ++} ++ ++#else ++ ++static int x509_inet_pton_ipv6(const char *src, void *dst) ++{ ++ return inet_pton(AF_INET6, src, dst) == 1 ? 0 : -1; ++} ++ ++static int x509_inet_pton_ipv4(const char *src, void *dst) ++{ ++ return inet_pton(AF_INET, src, dst) == 1 ? 0 : -1; ++} ++ ++#endif /* !AF_INET6 || MBEDTLS_TEST_SW_INET_PTON */ //no-check-names ++ ++size_t mbedtls_x509_crt_parse_cn_inet_pton(const char *cn, void *dst) ++{ ++ return strchr(cn, ':') == NULL ++ ? x509_inet_pton_ipv4(cn, dst) == 0 ? 4 : 0 ++ : x509_inet_pton_ipv6(cn, dst) == 0 ? 16 : 0; ++} ++ ++static int x509_crt_check_san_ip(const mbedtls_x509_sequence *san, ++ const char *cn, size_t cn_len) ++{ ++ uint32_t ip[4]; ++ cn_len = mbedtls_x509_crt_parse_cn_inet_pton(cn, ip); ++ if (cn_len == 0) { ++ return -1; ++ } ++ ++ for (const mbedtls_x509_sequence *cur = san; cur != NULL; cur = cur->next) { ++ const unsigned char san_type = (unsigned char) cur->buf.tag & ++ MBEDTLS_ASN1_TAG_VALUE_MASK; ++ if (san_type == MBEDTLS_X509_SAN_IP_ADDRESS && ++ cur->buf.len == cn_len && memcmp(cur->buf.p, ip, cn_len) == 0) { ++ return 0; ++ } ++ } ++ ++ return -1; ++} ++ ++static int x509_crt_check_san_uri(const mbedtls_x509_sequence *san, ++ const char *cn, size_t cn_len) ++{ ++ for (const mbedtls_x509_sequence *cur = san; cur != NULL; cur = cur->next) { ++ const unsigned char san_type = (unsigned char) cur->buf.tag & ++ MBEDTLS_ASN1_TAG_VALUE_MASK; ++ if (san_type == MBEDTLS_X509_SAN_UNIFORM_RESOURCE_IDENTIFIER && ++ cur->buf.len == cn_len && memcmp(cur->buf.p, cn, cn_len) == 0) { ++ return 0; ++ } ++ } ++ ++ return -1; ++} ++ + /* + * Check for SAN match, see RFC 5280 Section 4.2.1.6 + */ ++ ++static int x509_crt_check_san(const mbedtls_x509_sequence *san, ++ const char *cn, size_t cn_len) ++{ ++ int san_ip = 0; ++ int san_uri = 0; ++ /* Prioritize DNS name over other subtypes due to popularity */ ++ for (const mbedtls_x509_sequence *cur = san; cur != NULL; cur = cur->next) { ++ switch ((unsigned char) cur->buf.tag & MBEDTLS_ASN1_TAG_VALUE_MASK) { ++ case MBEDTLS_X509_SAN_DNS_NAME: ++ if (x509_crt_check_cn(&cur->buf, cn, cn_len) == 0) { ++ return 0; ++ } ++ break; ++ case MBEDTLS_X509_SAN_IP_ADDRESS: ++ san_ip = 1; ++ break; ++ case MBEDTLS_X509_SAN_UNIFORM_RESOURCE_IDENTIFIER: ++ san_uri = 1; ++ break; ++ /* (We may handle other types here later.) */ ++ default: /* Unrecognized type */ ++ break; ++ } ++ } ++ if (san_ip) { ++ if (x509_crt_check_san_ip(san, cn, cn_len) == 0) { ++ return 0; ++ } ++ } ++ if (san_uri) { ++ if (x509_crt_check_san_uri(san, cn, cn_len) == 0) { ++ return 0; ++ } ++ } ++ ++ return -1; ++} ++#else + static int x509_crt_check_san( const mbedtls_x509_buf *name, + const char *cn, size_t cn_len ) + { +@@ -3014,6 +3263,7 @@ static int x509_crt_check_san( const mbedtls_x509_buf *name, + /* Unrecognized type */ + return( -1 ); + } ++#endif /* MBEDTLS_CUSTOM_SAN_IP_VERIF */ + + /* + * Verify the requested CN - only call this if cn is not NULL! diff --git a/Common/app/mqtt/mqtt_agent_task.c b/Common/app/mqtt/mqtt_agent_task.c index 10e2deb34..56146e7fb 100644 --- a/Common/app/mqtt/mqtt_agent_task.c +++ b/Common/app/mqtt/mqtt_agent_task.c @@ -119,6 +119,8 @@ static_assert( ( ( uint64_t ) RETRY_BACKOFF_MULTIPLIER * ( uint64_t ) RETRY_MAX_ #define MUTEX_IS_OWNED( xHandle ) ( xTaskGetCurrentTaskHandle() == xSemaphoreGetMutexHolder( xHandle ) ) +#define GG_DISCOVERY_TIMEOUT_MS 20000 + struct MQTTAgentMessageContext { QueueHandle_t xQueue; @@ -822,7 +824,9 @@ static void prvFreeAgentTaskCtx( MQTTAgentTaskCtx_t * pxCtx ) static MQTTStatus_t prvConfigureAgentTaskCtx( MQTTAgentTaskCtx_t * pxCtx, NetworkContext_t * pxNetworkContext, uint8_t * pucNetworkBuffer, - size_t uxNetworkBufferLen ) + size_t uxNetworkBufferLen, + KVStoreKey_t endpointLabel, + KVStoreKey_t portLabel) { BaseType_t xSuccess = pdTRUE; MQTTStatus_t xStatus = MQTTSuccess; @@ -910,7 +914,7 @@ static MQTTStatus_t prvConfigureAgentTaskCtx( MQTTAgentTaskCtx_t * pxCtx, if( xStatus == MQTTSuccess ) { - pxCtx->pcMqttEndpoint = KVStore_getStringHeap( CS_CORE_MQTT_ENDPOINT, + pxCtx->pcMqttEndpoint = KVStore_getStringHeap( endpointLabel, &( pxCtx->uxMqttEndpointLen ) ); if( ( pxCtx->uxMqttEndpointLen == 0 ) || @@ -923,7 +927,7 @@ static MQTTStatus_t prvConfigureAgentTaskCtx( MQTTAgentTaskCtx_t * pxCtx, if( xStatus == MQTTSuccess ) { - pxCtx->ulMqttPort = KVStore_getUInt32( CS_CORE_MQTT_PORT, &( xSuccess ) ); + pxCtx->ulMqttPort = KVStore_getUInt32( portLabel, &( xSuccess ) ); if( ( pxCtx->ulMqttPort == 0 ) || ( xSuccess == pdFALSE ) ) @@ -965,10 +969,13 @@ void vMQTTAgentTask( void * pvParameters ) uint8_t * pucNetworkBuffer = NULL; NetworkContext_t * pxNetworkContext = NULL; uint16_t usNextRetryBackOff = 0U; + MQTTConnectionContext_t *connectionParams = (MQTTConnectionContext_t *)pvParameters; PkiObject_t xPrivateKey = xPkiObjectFromLabel( TLS_KEY_PRV_LABEL ); PkiObject_t xClientCertificate = xPkiObjectFromLabel( TLS_CERT_LABEL ); - PkiObject_t pxRootCaChain[ 1 ] = { xPkiObjectFromLabel( TLS_ROOT_CA_CERT_LABEL ) }; + PkiObject_t pxRootCaChain[ 1 ] = { xPkiObjectFromLabel( connectionParams->caLabel ) }; + KVStoreKey_t portLabel = connectionParams->portLabel; + KVStoreKey_t endpointLabel = connectionParams->endpointLabel; ( void ) pvParameters; @@ -1019,7 +1026,9 @@ void vMQTTAgentTask( void * pvParameters ) { xMQTTStatus = prvConfigureAgentTaskCtx( pxCtx, pxNetworkContext, pucNetworkBuffer, - MQTT_AGENT_NETWORK_BUFFER_SIZE ); + MQTT_AGENT_NETWORK_BUFFER_SIZE, + endpointLabel, + portLabel); } else { @@ -1051,7 +1060,6 @@ void vMQTTAgentTask( void * pvParameters ) else { ( void ) xEventGroupSetBits( xSystemEvents, EVT_MASK_MQTT_INIT ); - xDefaultInstanceHandle = &( pxCtx->xAgentContext ); } } @@ -1083,7 +1091,7 @@ void vMQTTAgentTask( void * pvParameters ) BackoffAlgorithm_InitializeParams( &xReconnectParams, RETRY_BACKOFF_BASE, RETRY_MAX_BACKOFF_DELAY, - BACKOFF_ALGORITHM_RETRY_FOREVER ); + connectionParams->maxBackoffAttempts); xTlsStatus = TLS_TRANSPORT_UNKNOWN_ERROR; @@ -1148,6 +1156,7 @@ void vMQTTAgentTask( void * pvParameters ) if( ( xMQTTStatus == MQTTSuccess ) && ( pxCtx->xConnectInfo.cleanSession == false ) ) { + connectionParams->mqttAgentConnected = true; configASSERT_CONTINUE( MUTEX_IS_OWNED( pxCtx->xSubMgrCtx.xMutex ) ); LogInfo( "Resuming persistent MQTT Session." ); @@ -1177,6 +1186,7 @@ void vMQTTAgentTask( void * pvParameters ) } else if( xMQTTStatus == MQTTSuccess ) { + connectionParams->mqttAgentConnected = true; configASSERT_CONTINUE( MUTEX_IS_OWNED( pxCtx->xSubMgrCtx.xMutex ) ); LogInfo( "Starting a clean MQTT Session." ); @@ -1187,6 +1197,7 @@ void vMQTTAgentTask( void * pvParameters ) } else { + connectionParams->mqttAgentConnected = false; LogError( "Failed to connect to mqtt broker." ); } @@ -1203,6 +1214,7 @@ void vMQTTAgentTask( void * pvParameters ) if( xMQTTStatus == MQTTSuccess ) { + xDefaultInstanceHandle = &( pxCtx->xAgentContext ); ( void ) xEventGroupSetBits( xSystemEvents, EVT_MASK_MQTT_CONNECTED ); /* Reset backoff timer */ @@ -1275,7 +1287,7 @@ void vMQTTAgentTask( void * pvParameters ) } ( void ) xEventGroupClearBits( xSystemEvents, EVT_MASK_MQTT_INIT | EVT_MASK_MQTT_CONNECTED ); - + xEventGroupSetBits(xSystemEvents, EVT_MASK_MQTT_AGENT_FINISHED); LogError( "Terminating MqttAgentTask." ); vTaskDelete( NULL ); diff --git a/Common/app/mqtt/mqtt_agent_task.h b/Common/app/mqtt/mqtt_agent_task.h index ec553d521..6156b797d 100644 --- a/Common/app/mqtt/mqtt_agent_task.h +++ b/Common/app/mqtt/mqtt_agent_task.h @@ -3,9 +3,18 @@ #include "FreeRTOS.h" #include +#include "kvstore_config.h" struct MQTTAgentTaskCtx; typedef struct MQTTAgentContext * MQTTAgentHandle_t; +typedef struct +{ + KVStoreKey_t endpointLabel; /* Label for the MQTT endpoint */ + const char *caLabel; /* Label for the Certificate Authority */ + KVStoreKey_t portLabel; /* Label for the port used for the MQTT connection */ + uint32_t maxBackoffAttempts; /* Maximum number of backoff attempts for reconnection */ + bool mqttAgentConnected; /* Boolean flag indicating if the MQTT agent is connected */ +} MQTTConnectionContext_t; MQTTAgentHandle_t xGetMqttAgentHandle( void ); diff --git a/Common/app/mqtt/mqtt_connection_task.c b/Common/app/mqtt/mqtt_connection_task.c new file mode 100644 index 000000000..38798a4d9 --- /dev/null +++ b/Common/app/mqtt/mqtt_connection_task.c @@ -0,0 +1,144 @@ +/** + ****************************************************************************** + * @file : mqtt_connection_task.c + * @brief : Task implementation of the MQTT connection state machine + ****************************************************************************** + * @attention + * + * Copyright (c) 2025 STMicroelectronics. + * All rights reserved. + * + * This software is licensed under terms that can be found in the LICENSE file + * in the root directory of this software component. + * If no LICENSE file comes with this software, it is provided AS-IS. + * + ****************************************************************************** + */ +#include "logging_levels.h" +#define LOG_LEVEL LOG_INFO +#include "logging.h" + +#include "mqtt_agent_task.h" +#include "greengrass_discovery_task.h" +#include "mqtt_connection_task.h" +#include "tls_transport_config.h" +#include "sys_evt.h" +#include "backoff_algorithm.h" + +/** + * Maximum number of attempts to reconnect to the GreenGrass core device + * before attempting a Greengrass discovery. + */ +#define MAX_GG_FIRST_CONNECTION_ATTEMPTS 2 + +/** + * Maximum number of attempts to reconnect to the GreenGrass core device + * before connecting to the cloud. + */ +#define MAX_GG_CONNECTION_ATTEMPTS 5 + +#define MQTT_AGENT_TASK_PRIORITY 11 +#define GG_DISCOVERY_TASK_PRIORITY 11 + +extern void vMQTTAgentTask( void * pvParameters); +extern void vGGDiscoveryTask( void * pvParameters); + +static void setGGConnectionContext( MQTTConnectionContext_t *ConnectionConext, uint32_t maxBackOffAttempts ) +{ + ConnectionConext->endpointLabel = CS_CORE_GG_ENDPOINT; + ConnectionConext->portLabel = CS_CORE_GG_PORT; + ConnectionConext->caLabel = TLS_ROOT_GG_CA_CERT_LABEL; + ConnectionConext->maxBackoffAttempts = maxBackOffAttempts; + ConnectionConext->mqttAgentConnected = false; +} + +static void setCloudConnectionContext( MQTTConnectionContext_t *ConnectionConext, uint32_t maxBackOffAttempts ) +{ + ConnectionConext->endpointLabel = CS_CORE_MQTT_ENDPOINT; + ConnectionConext->portLabel = CS_CORE_MQTT_PORT; + ConnectionConext->caLabel = TLS_ROOT_CA_CERT_LABEL; + ConnectionConext->maxBackoffAttempts = maxBackOffAttempts; + ConnectionConext->mqttAgentConnected = false; +} + +void vMQTTConnectionTask( void * pvParameters ){ + BaseType_t xResult; + + MQTTConnectionContext_t ConnectionCtx; + GGDiscoveryContext xGGContext; + + setGGConnectionContext(&ConnectionCtx, MAX_GG_FIRST_CONNECTION_ATTEMPTS); + + LogInfo("Connecting to Greengrass Core device using NVM config"); + xResult = xTaskCreate( vMQTTAgentTask, "MQTTAgent", 1024, &ConnectionCtx, MQTT_AGENT_TASK_PRIORITY, NULL ); + configASSERT( xResult == pdTRUE ); + + ( void ) xEventGroupWaitBits( xSystemEvents, + EVT_MASK_MQTT_AGENT_FINISHED, + 0x00, + pdTRUE, + portMAX_DELAY ); + + /* Attempt Greengrass discovery if direct connection fails */ + if (ConnectionCtx.mqttAgentConnected == false) + { + LogWarn( "Connection to Greengrass Core device using NVM config failed. Initiating GreenGrass discovery." ); + + /* Attempt Greengrass discovery connection */ + xResult = xTaskCreate( vGGDiscoveryTask, "GGDiscovery", 1024, &xGGContext, GG_DISCOVERY_TASK_PRIORITY , NULL ); + configASSERT( xResult == pdTRUE ); + + ( void ) xEventGroupWaitBits( xSystemEvents, + EVT_MASK_GG_DISCOVERY_PERFORMED, + 0x00, + pdTRUE, + portMAX_DELAY ); + + /* If GG Discovery successful, attempt connection to the GG core device */ + if( xGGContext.ggDiscoverySuccess == true ){ + LogInfo("Greengrass Discovery succeeded. Connecting to Greengrass Core device"); + setGGConnectionContext(&ConnectionCtx, MAX_GG_CONNECTION_ATTEMPTS); + + xResult = xTaskCreate( vMQTTAgentTask, "MQTTAgent", 1024, &ConnectionCtx, MQTT_AGENT_TASK_PRIORITY, NULL ); + configASSERT( xResult == pdTRUE ); + + ( void ) xEventGroupWaitBits( xSystemEvents, + EVT_MASK_MQTT_AGENT_FINISHED, + 0x00, + pdTRUE, + portMAX_DELAY ); + + /* If connection to the GG Core device failed, connect to Cloud */ + if (ConnectionCtx.mqttAgentConnected == false){ + LogWarn("Connection to Greengrass failed. Connecting to Cloud"); + setCloudConnectionContext(&ConnectionCtx, BACKOFF_ALGORITHM_RETRY_FOREVER); + + xResult = xTaskCreate( vMQTTAgentTask, "MQTTAgent", 1024, &ConnectionCtx, MQTT_AGENT_TASK_PRIORITY, NULL ); + configASSERT( xResult == pdTRUE ); + + ( void ) xEventGroupWaitBits( xSystemEvents, + EVT_MASK_MQTT_AGENT_FINISHED, + 0x00, + pdTRUE, + portMAX_DELAY ); + } + } + /* If GG Discovery failed, connect to Cloud */ + else + { + LogWarn("Greengrass Discovery failed. Connecting to Cloud"); + setCloudConnectionContext(&ConnectionCtx, BACKOFF_ALGORITHM_RETRY_FOREVER); + + xResult = xTaskCreate( vMQTTAgentTask, "MQTTAgent", 1024, &ConnectionCtx, MQTT_AGENT_TASK_PRIORITY, NULL ); + configASSERT( xResult == pdTRUE ); + + ( void ) xEventGroupWaitBits( xSystemEvents, + EVT_MASK_MQTT_AGENT_FINISHED, + 0x00, + pdTRUE, + portMAX_DELAY ); + } + } + LogInfo( "Terminating MqttConnecionTask." ); + vTaskDelete(NULL); +} diff --git a/Common/app/mqtt/mqtt_connection_task.h b/Common/app/mqtt/mqtt_connection_task.h new file mode 100644 index 000000000..8e4069410 --- /dev/null +++ b/Common/app/mqtt/mqtt_connection_task.h @@ -0,0 +1,9 @@ +#ifndef _MQTT_CONNECTION_TASK_H_ +#define _MQTT_CONNECTION_TASK_H_ + +#include "FreeRTOS.h" + +void vMQTTConnectionTask( void * pvParameters ); + + +#endif /* ifndef _MQTT_CONNECTION_TASK_H_ */ diff --git a/Common/config/kvstore_config.h b/Common/config/kvstore_config.h index 442ea88bd..75d9ff0fa 100644 --- a/Common/config/kvstore_config.h +++ b/Common/config/kvstore_config.h @@ -35,7 +35,9 @@ typedef enum KvStoreEnum { CS_CORE_THING_NAME, CS_CORE_MQTT_ENDPOINT, + CS_CORE_GG_ENDPOINT, CS_CORE_MQTT_PORT, + CS_CORE_GG_PORT, CS_WIFI_SSID, CS_WIFI_CREDENTIAL, CS_TIME_HWM_S_1970, @@ -81,10 +83,18 @@ typedef enum KvStoreEnum #define MQTT_ENDPOINT_DFLT "" #endif /* !defined ( MQTT_ENDPOINT_DFLT ) */ +#if !defined( GG_ENDPOINT_DFLT ) +#define GG_ENDPOINT_DFLT "" +#endif /* !defined ( GG_ENDPOINT_DFLT ) */ + #if !defined( MQTT_PORT_DFLT ) #define MQTT_PORT_DFLT 8883 #endif /* !defined ( MQTT_PORT_DFLT ) */ +#if !defined( GG_PORT_DFLT ) +#define GG_PORT_DFLT 8883 +#endif /* !defined ( GG_PORT_DFLT ) */ + #if !defined( WIFI_SSID_DFLT ) #define WIFI_SSID_DFLT "" #endif /* !defined ( WIFI_SSID_DFLT ) */ @@ -103,7 +113,9 @@ typedef enum KvStoreEnum { \ "thing_name", \ "mqtt_endpoint", \ + "gg_endpoint", \ "mqtt_port", \ + "gg_port", \ "wifi_ssid", \ "wifi_credential", \ "time_hwm" \ @@ -113,7 +125,9 @@ typedef enum KvStoreEnum { \ KV_DFLT( KV_TYPE_STRING, THING_NAME_DFLT ), /* CS_CORE_THING_NAME */ \ KV_DFLT( KV_TYPE_STRING, MQTT_ENDPOINT_DFLT ), /* CS_CORE_MQTT_ENDPOINT */ \ + KV_DFLT( KV_TYPE_STRING, GG_ENDPOINT_DFLT ), /* CS_CORE_GG_ENDPOINT */ \ KV_DFLT( KV_TYPE_UINT32, MQTT_PORT_DFLT ), /* CS_CORE_MQTT_PORT */ \ + KV_DFLT( KV_TYPE_UINT32, GG_PORT_DFLT ), /* CS_CORE_GG_PORT */ \ KV_DFLT( KV_TYPE_STRING, WIFI_SSID_DFLT ), /* CS_WIFI_SSID */ \ KV_DFLT( KV_TYPE_STRING, WIFI_PASSWORD_DFLT ), /* CS_WIFI_CREDENTIAL */ \ KV_DFLT( KV_TYPE_UINT32, 0 ), /* CS_TIME_HWM_S_1970 */ \ diff --git a/Common/include/sys_evt.h b/Common/include/sys_evt.h index b1e6cebad..c766e7e92 100644 --- a/Common/include/sys_evt.h +++ b/Common/include/sys_evt.h @@ -35,6 +35,8 @@ #define EVT_MASK_NET_CONNECTED 0x04 #define EVT_MASK_MQTT_INIT 0x08 #define EVT_MASK_MQTT_CONNECTED 0x10 +#define EVT_MASK_GG_DISCOVERY_PERFORMED 0X100 +#define EVT_MASK_MQTT_AGENT_FINISHED 0X200 extern EventGroupHandle_t xSystemEvents; diff --git a/Documentation/Pictures/CLI.png b/Documentation/Pictures/CLI.png new file mode 100644 index 0000000000000000000000000000000000000000..bcc17d50c502faf02dd9bc0ac74d75ee1288895c GIT binary patch literal 8949 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

G8kWnYxh9teIbWiwUsNfyW%x>5Q+6@oq@87AI z=co^z8LA>w_OEIg$jSe+)F{&_@Faxi&L6S#f2n4chr&(vEum|E+SLbF5>1}+iL9nX5+=#f(9^EO#!<;K*ui%)<+#V_@HSf->M5NL2GT|V2 z#SE*jf%gY{2FWm8rK1WOrC?wSvXNGUV~o(FK}+-`L7_5~)Q{X}nz|qwcycfq=e6`NKLHNf~SfqtM~_$aqGm6LhBZa#59F}$=q!xKJvW$9C6`>{%LCm-aL%s zBI~uy*=+DT7H-%cl{+TN5<`k2rC#<>7@G@g-5eD0N8ywTGvIln!rohF?{oVs$Vht& zJPbXqOz2>v<2#t5#zwcFcI?eQ;5YD0*oL?aao%p|`;$N}oe`|}-LbH}lAz@!wKHW< zu$6Wr$H#}gbR?yCp6&;WAx&Ss=(C8M^<1*@&ofy=XO$U4G^C>zYuBVWR#q?MW=y<> z?&U_F4Yi4lL>qS;9opNa1Ihm-=)^#Se$qRFE(&AtCb~tg_sn4!G-DFRD|Z}Lu=AABA!Zh~exy@0rw(^{8>3r^Cec^i*qcA$;-=^`9K`YyP(UPK5GtTK`-%Y4gvVa8TT72g_*Lnnwb+BlJpL zu3fv7kmyE(cN>ZUh8W<@QND-Zbn5!4kW+n?qT3w$MB)53B#YgRcwe)#E+T;8EWWvo!thVHQNo zNPaIX93TDt&Eu<>uG2*GD}2YFa5m@=!4tgPl@hVnlvF8_P)LAev)jtn_OrdpgTzGB zBqlvBY;tljZ9AL0UYNT4-ZO{=mT9&lfNu-dvB!|BmF1ME?K4g>scJAo%=QM z8aPp3bikO*Q>${?qM-g1$SLG}Cok-5Dh!NPDzbPR4~FiTU!W#Q7Vulv9F{ ztyegVuedhz&&2drf2B68Y>5?>+f~Bn;QN*)`MN^&*n*gqk6+kRkG8z&SZu*5_m-8f zax>mH?oUfQ8610cFdQqOkO2^@3Y=c=tHq&kHGfJYN>#A{r{wE7^-f8x*QC{r>s$Y= zryU#=PCPSN`Nwbt0JUP4?kISSHsZX_UYz>MuIgO?(5&iP-YZk6DbKa>TU$F2;uaCU z1*%2&pU|vNLu*TIadu)SqbhmSeCCbd#}}K_tYsq3Zj~J1buS@s$!s{!u4blF&rkK` ztNq`*^6UNm7@3_kj8FW7&GMAUO0V>>#s(5I!>8-kgDc&Rt2kw*exUHR%!m7a4ztlG)L=8)_sa|AdXf_U+aFcn(5V!^Bf4yQm@_J{4r=OX;v zfuJ|u6<)#9`7wGFm(@c-olR8w7*HQ>q-|>|t(7q{{Ip5;5W+9g3gYuZ<39w*5H>hm zZ<+soO*%^xlqvtfbp)rjW5~iNYYBs3#bi-kTN#z%{fir+{`njM+=u$|rwNa@p_Rt?VZGH5nXJB~8!}UJ+3a02@jgIcglodYp;B%=nUH+;~1kSwm$!ieX!Wce|@0L`OVIb1@KtZ zEu^8)CLJ5b-JSmN`GrFh7X<)?HRCrFZehXJMmCWFEE~I#gRd{1;A58O_}J1i#;D!= ztKwC!#UH_27`S)te6mI+yGxf9dk`O-zO;i~rGxOM!~AQ%iM1Rq`QZ8@gKAigN)g2+ z(wHifU-jx(Dx({b3HAP-?J=XgZn?E9c_ht8{_~@WC!FQAW98x_hJDfnnt?0TmdGe{ z%|Nb6U*>&w-$dT0*3GL-zJ;--6W$4EAbWB*i+78glArzL$`SY6A}`L{s3(5KXh3jWbSkTf-#-CY?La85@;o?8|a( zb3Mzh<(1uGIext)TNMX-;!Vo_OKyWAcFH&XimgSosKD@h(Yi~^u`+i5M8v*;xYUFM zEk%wW`+Ht)*NrgD3Fbt9XO?xjPj38;IC}xe9lA3FD{T%PG(TfR;h{&}>cd+;mEUpB zZH!KReJh7Bt7jKE5@Wt|#X+xN_vpUWT@2{E(Y_yjtE}zxh2r#ahx*ZI-sL;dZD{oq z*O_nb5)g{*=Par&_HY5j8U3T^)O7nUTr+36=a+`7L9fkkj0YktjBvND4E0`z$6SL&>^4;_|!>sXgrD{Yvh4(i4 z$GQ!E)2W>9IIpsrlJ(ifP`UQsXtpYva^b`87Dtyai6y<_cCs$t#hQ7ouzruE!>-^D zghPEfqmf58_@dWII zOr5i(@j2J7T|cJ}Eudey{B*fzJJ3=})&H@rGf^?#+OF;eLi5^HR)BZRO3?Ey-J}FX zG2nZ9XL`8L&I=j7<9JJ(#uJ3*?NXNun6fA3Kk39&cvJMmYaf!)JiTe>98OujPs0bk z=wYO1yFdMsQ-pqyA|=}?91=(5)}ksj)!X;FvGj|8Za(fQpDTH#7DwJ}BRihc&wg*wA>~B=?6rFbi|ylg^zFiEg(TbZ!u*BI zE&dN)*F0B6Rzfv>5FilEALfnj4b&@gGee1TL{o%I76CbVpV+Tbf%Fu(OHUkcu z&pPl@ilet>ibPV;gbqk%Y6<=b6px@VeD81c2?CE<}g@hru|YfwLK z3w55ar*gVKaS`rw;@#NCVxk_7ZcLwwJyu0f){Rlofd#fnoCaUJEavTxt3DK+-OZ4b zPGn9hp?2iDt&O6laM#WzzGwKEffc|-(tUQ7e$(iwW*XMIyfyZk2`|jD0aokHty?;d z&%M*H10`@I-1ON2>4v97(W>aQPB~-eug)TyAgS?}rEISu!pt4!1mu^Nk8mdHT=S?r zUa{7zjkdnKt2Y^6Qu}C*x=@@~wG~q%ou(+_hp!Esl(RlnPSa@b%P4Oc> zZ}drp3m-#fmDv-sKNR3G!O;TzT|eI0cDmWO9o@;Ezr-I`^R;pfD;cmy5E}eRI{n z88vxw>A+a%_u60I>V}awfo^o+e{i!F9@;bQ+6CwX44+&*unQ$#InQ0q8N^*%T`Z=a{L6B#Ihk>c0H}w?E03D Xnf9fJ_M{Zxyi*zI7;6`3o(=gQ$KABg literal 0 HcmV?d00001 diff --git a/Documentation/Pictures/sequence_diagram.png b/Documentation/Pictures/sequence_diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..49fa41181f85e7e75a150996a640fc40b78f769f GIT binary patch literal 20003 zcmc$`cQjnx`!=qQ2}X-3qeM;gK8O;12!e=5^b*}@(Q9-edWn)mCn82CdbAk5ixSa- zsL=_cyxa5neAoN_{`#%=uQzK2Yi6H)_Bm(o`@Zh$y7r0E(olvG!U?gkuwai=6t%Ii zux-HqRVWVl5l&^waS@E!6kYF-~}2w!BN%0<7r^Sq}{$1ox{vD@&DJ2okJU$<~8ne-1{= z(ZpY4#V_7#^c$ZFbs!q#ON3nKR8mGt|83@wYckhUpU}4%p!oax=<#6M<+j&w@u!VX zWt*SMu2RU}Kkyj(^H(~1_s`#Aw@v!S_k8>;vUriQG?i?fBo;aJ z6_!3Ly@|tPyvQH|RP))dcO?&6=KT)`ZTW@;?>+GFAnx20G&q3MVRwq@FIRSd@mvT9 z^jzujayuIi_;Wd95g)+I^J0ZA*+@bnRqmLA_d#52(2{8z-1*DdubT$F$u_PRH%;}i zqP~X-5vH$9&jpb1iW|7zrF+@g8ide&_AH+M%qd&erulCo>w>YdaqUcdxizIgZ+*YE z2>*W@-bbB|b~PP0#K%y&3H|+aIfe{j{ZqLl=3fQTXB{RQdl=Qr@7M!h8^8DGhtI)E z#-g}*d{@I5huN5o{c63ROU=qd(Tj-T2FwHBy))+>R@UwZ3)dqTpd0r8bQ3{p_`YN) z!15^D;6dtQny6*0+u7*vIQa;??sgxZp*KkEWcs3jYz3mY)F0gu3wsZy#Fa@OUJrsJ zGM=67?4*fO@HE83T9=X${?k8=JisXy>=xG6FUBI!FZg8Y?)m6{G_2goWFCBl^&dhu| zdY>&5mfGLnO&A{XL`Eji)~PH`^uA`6U0r;d>S|Y4kw%tOzS4uZ7nVXlrmE5Sa4tjpJvZRkhn}! zeZ>kj?IO=_QQA!Mlq_q-GD;VB#i8N5aAHHHNbIPdjEsH{vq(*bZt@%|>gns17U^%j0AxbTrDE|^eNgUv4oE5aeP^S9!tTBpf)zblj3Qg45pbW}`X zjMI?rWNozAT`ka4C@Rxi*0-p}Y&&>3m6fp~F`ODg1NeAQ@=<)fCs-`sV?dAYIQg+p zpmCu@w4Imz{6ZJ$j7Y-h-!s)yFX#o;WpF(fe?AIXAt!w3vPk07!Svy>C5!|y9htyfNy&?^Sk&ipw&CV>H54`SF!4(nB!#nm}5&hCQ zb#ajkZfLB?xN8ADHEMbp;ZB>3BMqCp8aEFX72?A-YD=>;lE%Faaal>wrKgXK6D%K1@et3Vd;{7XQO<*brJ8t7mHl!`@+auhz z@DL4l1+*hY%_+NDIT~B;K&0AK?E9^tA`TWLM`s&((kLV1yqB{V#Pyi{{QOu}r!#Ex z^2Z^poo28B(%=}+h-tfoO!VB|7`4~h8Y%5Ejt~T83xA{hO7}Ih1s~S~&PCgfZ$DzE zVCzoQzVX=8)%B;8_i%(B3+O4-(BP)2wj?Qsf@M>slBkrCK(2$O&bdg=Vlj1;DS~SA zqRFX@+{#7--IBu3$U?Q@a4U0;$e7v{LMGE)|N}J(%a03lAuuze-m}R_mIU>UL z(Vx_G5rXL(G(nM6lAm&o`87P)nUX?u?+?a6w4<;_Xnfz|8Nck@wJS6KOG+gh%pmxk z_5d$g1bMMBs!F?i0itl`q39Ri3%#aubKVc<*ye{ydxZGI(k-@0XNihY*oiNd3kl*L z%R!Bd{8i)oY2u{ule-e7b4TP0c*Y4A%ULni6hmDU$h8VV+2Q-IoLD^xjlPSP8srx+ z5URN=2g|FEB|xepbg>6ujKPhIwj1FHce5=ao#`jBhFKNEgWm+D@g&#fHmc(I=Exg3 z@5$xpGVB=?-Jc}W*JHHUDD%Lpjv%KsGrfhQ@6U2s!_vT>Xue!xp~A?DkSkT_M(u`7?re8eT91MR*jH;0W!*v{4Rdc8o?fa zTAUDiuI-N)UdozQkr$x|0co+Ha
|Hz=9;U@Ti*y#HYQQ>|?1eI7b7*=E(ypsr@ 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 DdCkg8 literal 0 HcmV?d00001 diff --git a/Documentation/Pictures/state_machine.png b/Documentation/Pictures/state_machine.png new file mode 100644 index 0000000000000000000000000000000000000000..bf6ec38d156aafbaff6b0b9bc5705096eef74ed7 GIT binary patch literal 73587 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 command, have it signed by AWS, and import the resulting certificate into the H5 device. Using a self-signed certificate will result in a `close_notify` error during the TLS connection. + +## mbedTLS Middleware Modifications + +To avoid the bad_certificate error while connecting to the Greengrass core device, a patch was made to the mbedTLS middleware, specifically in the `x509_crt.c` file. The existing mbedTLS middleware (version 3.1.1) lacked support for verifying IP addresses in the SAN field of x509 certificates. These changes enable custom Subject Alternative Name (SAN) verification for IP addresses, providing a solution that accommodates the current version of the middleware and avoids conflicts with newer API changes. +This modification ensures that IP addresses are correctly validated, thereby preventing `bad_certificate` errors during the TLS handshake with Greengrass core devices. +- A macro `MBEDTLS_CUSTOM_SAN_IP_VERIF` was added to the mbedTLS configuration file to activate the custom SAN verification feature. +The current implementation is inspired by mbedTLS v3.5.0 + +## mbedTLS configuration file +Modifications were made to the `mbedtls_config_ntz.h` configuration file to enable RSA cipher suites. This change was crucial to prevent the `no cipher suites in common` error during the TLS handshake with the Greengrass core device. The modified parts are marked with the comment "Enable for greengrass" to make it easier to enable them. + +The RSA cipher suites are necessary for both the Discovery and the connection to the Greengrass core device. + +Additionally, it is essential to use the `Amazon Root CA 1` certificate, which provides 2048-bit RSA security. This certificate should be imported into the `root_ca_cert` to ensure connection with the cipher suite used. + +## Features + +### Discovery Request + +- **HTTP Discovery Request:** Utilizes the HTTP client `coreHTTP` library to send HTTP discovery requests. + - **Submodule Integration:** The `llhttp` is a necessary dependency that was added as a submodule to the project. To initialize and fetch the submodule after cloning the repository, use the following command: `git submodule update --init`. + - **Endpoint Construction:** Constructs the discovery URL from the existing AWS endpoint stored in NVM. For example: + - AWS endpoint: `**********-ats.iot.region.amazonaws.com` + - Constructed Discovery endpoint: `greengrass-ats.iot.region.amazonaws.com` + - This approach eliminates the need to store another endpoint for the Greengrass discovery feature, as only the region is required for the discovery URL. + +- **TLS Connection Establishment:** + - Establishes a secure TLS connection to `greengrass-ats.iot.region.amazonaws.com` on port `8443`. + - Sends the HTTP discovery request to `https://greengrass-ats.iot.region.amazonaws.com:8443/greengrass/discover/thing/thing-name` once the connection is established. + +- **Response Handling:** The `transportRecv` function is designed to receive responses from the Greengrass server. It implements a loop with a timeout, using the `mbedtls_transport_recv` function. This approach prevents the `No response received` error caused by the delay in the Greengrass server's response time. + +**Multiple Core Devices Limitation:** + +The current implementation does not support handling multiple Greengrass core devices or multiple CA certificates returned in the discovery response. The lack of support comes from the design of the discovery response processing logic, which only precesses the first core device and the first CA certificate. + +### JSON Parsing and NVM Storage + +The parsing of the JSON response is done by using the CoreJSON library which is already integrated into the project. +- **JSON Response Parsing:** The function `parseGGDiscoveryResponse` parses the JSON response received from the Greengrass core device, it returns pointers for the parsed endpoint, port, and CA. +- **Certificate Formatting:** The parsed certificate requires formatting to replace escaped newline sequences (`\\n`) with actual newline characters (`\n`). +- **NVM Storage:** Writes the parsed configuration values into Non-Volatile Memory (NVM) and commits changes for persistent storage and future use, The following labels and files are defined for storage: + - **Greengrass CA:** Stored in the file `corePKCS11_GG_CA_Certificate.dat` under the label `root_gg_ca_cert`. + - **Greengrass Endpoint:** Stored under the label `gg_endpoint`, it can be either an IP address or a common name (CN). + - **Port:** Stored under the label `gg_port`. + +![alt text](Pictures/CLI.png) + +### Connection to Greengrass Core + +The connection is detailed in the state machine below: + +![alt text](Pictures/state_machine.png) + +- **Connection Process:** The `vMQTTAgentTask` is responsible for establishing a connection to the Greengrass core device. Initially, it attempts to connect using the existing Greengrass configuration stored in Non-Volatile Memory (NVM). If this attempt fails, the task performs a Greengrass Discovery to retrieve the necessary configuration, updates the NVM, and retry the connection. If all attempts are exhausted without success, the system connects to the AWS cloud. + +- After successfully establishing a connection with the Greengrass core device, the system proceeds with its default operations. This includes subscribing to relevant topics, calling additional tasks, and publishing data to the given MQTT topic. + +#### **Modification in MQTT Agent Task** + +A modification was made to the `vMQTTAgentTask` to delay the initialization of the `mqttAgentHandler` pointer until the MQTT agent is successfully connected. This change was necessary because other tasks rely on the `mqttAgentHandler` pointer once it is initialized. In scenarios where the MQTT agent task fails to connect and restarts, the pointer remains initialized in those dependent tasks which causes assertion faults. + +By delaying the initialization of the `mqttAgentHandler` pointer until the MQTT agent is connected, this issue is avoided. + +**Alternative Solutions:** +- Another possible solution would be to terminate the tasks that are using the `mqttAgentHandler` pointer if the MQTT agent task fails and restart them when the MQTT agent task is retried. However, the current approach of delaying the pointer initialization is simpler, and involves fewer modifications to other tasks, and avoids unnecessary task management. +- An improved approach consists in starting the `vMQTTAgentTask` only once with a pointer to the agent handler. If the connection attempt fails, instead of restarting the entire task with different parameters, the connection parameters are updated dynamically and the connection retried within the same task instance. + +![alt text](Pictures/mqtt_agent_sequence_diagram.png) ![alt text](Pictures/mqtt_agent_sequence_diagram_alternative.png) + + diff --git a/Documentation/Src/gg_sequence_diagram.puml b/Documentation/Src/gg_sequence_diagram.puml new file mode 100644 index 000000000..04c3d7854 --- /dev/null +++ b/Documentation/Src/gg_sequence_diagram.puml @@ -0,0 +1,31 @@ +@startuml + +!pragma teoz true +participant STM32H5 as H5_Board +participant "GreenGrass STM32MPU" as GG +participant "IoT Core" as Cloud + +== GreenGrass Discovery== + +H5_Board -> Cloud : HTTP Discovery request +H5_Board <- Cloud : JSON response + +== MQTT Session == + +H5_Board <-> GG : MQTT Connect + +H5_Board -> GG : Subscribe to Topics + +H5_Board -> GG : Publish Data + +group MQTT bridge + GG -> Cloud : Publish Data + +end + + +== Disconnect == + +H5_Board <-> GG : Close connection + +@enduml \ No newline at end of file diff --git a/Documentation/Src/mqtt_agent_sequence_diagram.puml b/Documentation/Src/mqtt_agent_sequence_diagram.puml new file mode 100644 index 000000000..fb60052d0 --- /dev/null +++ b/Documentation/Src/mqtt_agent_sequence_diagram.puml @@ -0,0 +1,31 @@ +@startuml +title Sequence diagram of the current implementation + +participant mqtt_connection_task +participant mqtt_agent_task +participant mqtt_broker + +activate mqtt_connection_task + +loop while not connected to broker + mqtt_connection_task -> mqtt_connection_task : set params X + mqtt_connection_task -> mqtt_agent_task++ : Start task with params X + mqtt_agent_task -> mqtt_broker : connect with params X + + alt connection successful + +mqtt_broker -> mqtt_agent_task : connection successful + + mqtt_agent_task -> mqtt_connection_task : notify success + else connection failed + mqtt_broker -> mqtt_agent_task : connection failed + mqtt_agent_task -> mqtt_connection_task : notify failure + mqtt_agent_task -> mqtt_agent_task : task end + deactivate mqtt_agent_task + end + +end + +deactivate mqtt_connection_task + +@enduml \ No newline at end of file diff --git a/Documentation/Src/mqtt_agent_sequence_diagram_alternative.puml b/Documentation/Src/mqtt_agent_sequence_diagram_alternative.puml new file mode 100644 index 000000000..1807dafbd --- /dev/null +++ b/Documentation/Src/mqtt_agent_sequence_diagram_alternative.puml @@ -0,0 +1,22 @@ +@startuml +title Sequence diagram of the alternative solution + +activate mqtt_connection_task +mqtt_connection_task -> mqtt_agent_task++ : Start task +loop while not connected to broker +mqtt_connection_task -> mqtt_connection_task : set params X +mqtt_connection_task -> mqtt_agent_task : apply params X +mqtt_agent_task -> mqtt_broker : connect with params X + + alt connection successful + +mqtt_broker -> mqtt_agent_task : connection successful + + mqtt_agent_task -> mqtt_connection_task : notify success + else connection failed + mqtt_broker -> mqtt_agent_task : connection failed + mqtt_agent_task -> mqtt_connection_task : notify failure + end +end + +@enduml \ No newline at end of file diff --git a/Documentation/Src/sequence_diagram.puml b/Documentation/Src/sequence_diagram.puml new file mode 100644 index 000000000..fd67d31b1 --- /dev/null +++ b/Documentation/Src/sequence_diagram.puml @@ -0,0 +1,20 @@ +@startuml +participant STM32H5 as H5_Board +participant "IoT Core" as Cloud + +== Establishing network connection == + +Cloud <-> H5_Board : TLS Connection + +== MQTT Session == + +H5_Board -> Cloud : MQTT Connect + +H5_Board -> Cloud : Subscribe to Topics + +H5_Board -> Cloud : Publish Data +== Disconnect == + +H5_Board <-> Cloud : Close connection + +@enduml \ No newline at end of file diff --git a/Documentation/Src/state_machine.puml b/Documentation/Src/state_machine.puml new file mode 100644 index 000000000..6938ed31a --- /dev/null +++ b/Documentation/Src/state_machine.puml @@ -0,0 +1,70 @@ +@startuml +hide empty description + +state "Connect to Greengrass Core Device" as Connect_to_GG #LightGreen +state "Read Greengrass configuration from Non-Volatile Memory" as Read_gg_config_nvm #LightGreen +state "Perform Greengrass discovery request" as GG_discovery #LightGreen +state "Write Greengrass config in Non-Volatile Memory" as Write_gg_config_nvm #LightGreen +state "Connect to IoT Cloud" as Connect_to_Cloud #LightBlue +state "Connected to Greengrass Core Device" as Connected_to_GG #LightGreen +state "Read Cloud configuration from Non-Volatile Memory" as Read_cloud_config_nvm #LightBlue +state "Connected to Cloud" as Connected_to_Cloud #LightBlue +state "Delay (back off mechanism)" as Back_Off #LightBlue +state "Delay (back off mechanism)" as Back_Off_GG #LightGreen + +state Start <> +state End_success <> #Green +state End_failure <> #Red + +Back_Off_GG : Max GG connection attempts +Back_Off : Unlimited connection attempts + +Start -down-> Read_gg_config_nvm +Read_gg_config_nvm: Greengrass URL +Read_gg_config_nvm: Greengrass port +Read_gg_config_nvm: Greengrass Certificate + +Read_gg_config_nvm -down-> GG_discovery : Failure +Read_gg_config_nvm -down-> Connect_to_GG : Success + + +Read_cloud_config_nvm : MQTT endpoint +Read_cloud_config_nvm : MQTT port +Read_cloud_config_nvm : Root CA cert +state GG_state <> +state Cloud_state <> + +Connect_to_GG -down-> GG_state + +GG_state --> Connected_to_GG : Success +GG_state --> Back_Off_GG : Failure +Back_Off_GG --> Connect_to_GG : First attempt < Attempts < Max GG Connection Attempts +Back_Off_GG --> GG_discovery : Attempts = First attempt +Back_Off_GG --> Read_cloud_config_nvm : Attempts >= Max_GG_Connection_Attempts + +Connected_to_GG --> End_success + +GG_discovery --> Write_gg_config_nvm : Success + +Write_gg_config_nvm -up-> Read_gg_config_nvm + +Write_gg_config_nvm: Greengrass URL +Write_gg_config_nvm: Greengrass port +Write_gg_config_nvm: Greengrass Certificate + +GG_discovery --> Read_cloud_config_nvm : Failure +Read_cloud_config_nvm --> Connect_to_Cloud : Success +Read_cloud_config_nvm --> End_failure : Failure + +Connect_to_Cloud --> Cloud_state +Cloud_state --> Connected_to_Cloud : Success +Cloud_state --> Back_Off : Failure +Back_Off --> Connect_to_Cloud +Connected_to_Cloud --> End_success + +legend +Color Legend: +- **LightGreen**: Added Greengrass connection features +- **LightBlue**: Original Cloud connection feature +end legend +@enduml \ No newline at end of file diff --git a/Documentation/Src/state_machine_sequence_diagram.puml b/Documentation/Src/state_machine_sequence_diagram.puml new file mode 100644 index 000000000..f33e5680d --- /dev/null +++ b/Documentation/Src/state_machine_sequence_diagram.puml @@ -0,0 +1,44 @@ +@startuml +title Sequence diagram of the current implementation + +participant NVM +participant mqtt_connection_task +participant discovery_task +participant mqtt_agent_task +participant mqtt_broker + +activate mqtt_connection_task + +alt + mqtt_connection_task -> discovery_task++ : start task + discovery_task -> NVM : write connectivity info + deactivate discovery_task +end + +loop while not connected to broker +alt Connect to greengrass + mqtt_connection_task -> mqtt_agent_task++ : Start task (NVM ID = GG ) +else Connect to IoT Core + mqtt_connection_task -> mqtt_agent_task : Start task (NVM ID = IoT Core ) +end + mqtt_agent_task -> NVM : get connectivity info (NVM ID) + mqtt_agent_task <- NVM : connectivity info + mqtt_agent_task -> mqtt_broker : connect(connectivity info) + + alt connection successful + +mqtt_broker -> mqtt_agent_task : connection successful + + mqtt_agent_task -> mqtt_connection_task : notify success + else connection failed + mqtt_broker -> mqtt_agent_task : connection failed + mqtt_agent_task -> mqtt_connection_task : notify failure + mqtt_agent_task -> mqtt_agent_task : task end + deactivate mqtt_agent_task + end + +end + +deactivate mqtt_connection_task + +@enduml \ No newline at end of file diff --git a/Projects/b_u585i_iot02a_ntz/.cproject b/Projects/b_u585i_iot02a_ntz/.cproject index 7f810035e..4f7fbbe40 100644 --- a/Projects/b_u585i_iot02a_ntz/.cproject +++ b/Projects/b_u585i_iot02a_ntz/.cproject @@ -20,7 +20,7 @@ - + - - - + - + - + - \ No newline at end of file diff --git a/Projects/b_u585i_iot02a_ntz/.project b/Projects/b_u585i_iot02a_ntz/.project index ec6d1762f..221e7fc4b 100644 --- a/Projects/b_u585i_iot02a_ntz/.project +++ b/Projects/b_u585i_iot02a_ntz/.project @@ -111,6 +111,11 @@ 2 WORKSPACE_LOC/Middleware/FreeRTOS/coreJSON/source + + Libraries/coreHTTP + 2 + WORKSPACE_LOC/Middleware/FreeRTOS/coreHTTP/source + Libraries/coreMQTT 2 diff --git a/Projects/b_u585i_iot02a_ntz/Inc/core_pkcs11_config.h b/Projects/b_u585i_iot02a_ntz/Inc/core_pkcs11_config.h index 02a899724..f29736253 100644 --- a/Projects/b_u585i_iot02a_ntz/Inc/core_pkcs11_config.h +++ b/Projects/b_u585i_iot02a_ntz/Inc/core_pkcs11_config.h @@ -187,4 +187,7 @@ #define pkcs11_ROOT_CA_CERT_LABEL "root_ca_cert" #define pkcs11configLABEL_ROOT_CERTIFICATE ( pkcs11_ROOT_CA_CERT_LABEL ) +#define pkcs11_ROOT_GG_CA_CERT_LABEL "root_gg_ca_cert" +#define pkcs11configLABEL_ROOT_GG_CERTIFICATE ( pkcs11_ROOT_GG_CA_CERT_LABEL ) + #endif /* _CORE_PKCS11_CONFIG_H_ */ diff --git a/Projects/b_u585i_iot02a_ntz/Inc/mbedtls_config_ntz.h b/Projects/b_u585i_iot02a_ntz/Inc/mbedtls_config_ntz.h index 534a86b61..685955726 100644 --- a/Projects/b_u585i_iot02a_ntz/Inc/mbedtls_config_ntz.h +++ b/Projects/b_u585i_iot02a_ntz/Inc/mbedtls_config_ntz.h @@ -3269,6 +3269,20 @@ void mbedtls_platform_free( void * ptr ); */ /*#define MBEDTLS_ECDH_VARIANT_EVEREST_ENABLED */ +/** + * @brief Enables custom Subject Alternative Name (SAN) verification for IP addresses. + * + * This macro activates a custom implementation of the x509 certificate SAN verification + * function, which includes support for IP address and URI verification in addition to + * DNS names. When defined, the verification function prioritizes DNS names but also + * checks for IP addresses and URIs in the SAN field of the certificate. + * + * @note This macro should be defined when IP address and URI verification are required + * in addition to DNS name verification in the SAN field of x509 certificates. + * Enable the greengrass connection + */ +#define MBEDTLS_CUSTOM_SAN_IP_VERIF + /* \} name SECTION: Customization configuration options */ #include "mbedtls/check_config.h" diff --git a/Projects/b_u585i_iot02a_ntz/Inc/tls_transport_config.h b/Projects/b_u585i_iot02a_ntz/Inc/tls_transport_config.h index d12b5f5ae..432084a7e 100644 --- a/Projects/b_u585i_iot02a_ntz/Inc/tls_transport_config.h +++ b/Projects/b_u585i_iot02a_ntz/Inc/tls_transport_config.h @@ -31,6 +31,7 @@ #define TLS_KEY_PUB_LABEL pkcs11_TLS_KEY_PUB_LABEL #define TLS_CERT_LABEL pkcs11_TLS_CERT_LABEL #define TLS_ROOT_CA_CERT_LABEL pkcs11_ROOT_CA_CERT_LABEL +#define TLS_ROOT_GG_CA_CERT_LABEL pkcs11_ROOT_GG_CA_CERT_LABEL #define OTA_SIGNING_KEY_LABEL pkcs11configLABEL_CODE_VERIFICATION_KEY #define TRANSPORT_USE_CTR_DRBG 1 diff --git a/Projects/b_u585i_iot02a_ntz/Src/app_main.c b/Projects/b_u585i_iot02a_ntz/Src/app_main.c index a29175e43..26c3e6496 100644 --- a/Projects/b_u585i_iot02a_ntz/Src/app_main.c +++ b/Projects/b_u585i_iot02a_ntz/Src/app_main.c @@ -183,7 +183,7 @@ static void vHeartbeatTask( void * pvParameters ) } extern void net_main( void * pvParameters ); -extern void vMQTTAgentTask( void * ); +extern void vMQTTConnectionTask( void * ); extern void vMotionSensorsPublish( void * ); extern void vEnvironmentSensorPublishTask( void * ); extern void vShadowDeviceTask( void * ); @@ -240,7 +240,7 @@ void vInitTask( void * pvArgs ) xResult = xTaskCreate( run_qualification_main, "QualTest", 4096, NULL, 10, NULL ); configASSERT( xResult == pdTRUE ); #else - xResult = xTaskCreate( vMQTTAgentTask, "MQTTAgent", 2048, NULL, 10, NULL ); + xResult = xTaskCreate( vMQTTConnectionTask, "MQTTConnection", 1024, NULL, 12, NULL ); configASSERT( xResult == pdTRUE ); xResult = xTaskCreate( vOTAUpdateTask, "OTAUpdate", 4096, NULL, tskIDLE_PRIORITY + 1, NULL ); @@ -267,7 +267,9 @@ void vInitTask( void * pvArgs ) static uint32_t ulCsrFlags = 0; -static void vDetermineResetSource() +void vDetermineResetSource(); + +void vDetermineResetSource() { const char * pcResetSource = NULL; diff --git a/Projects/b_u585i_iot02a_ntz/Src/crypto/core_pkcs11_pal_utils.c b/Projects/b_u585i_iot02a_ntz/Src/crypto/core_pkcs11_pal_utils.c index 3ef727c00..5af17da7a 100644 --- a/Projects/b_u585i_iot02a_ntz/Src/crypto/core_pkcs11_pal_utils.c +++ b/Projects/b_u585i_iot02a_ntz/Src/crypto/core_pkcs11_pal_utils.c @@ -51,6 +51,7 @@ #define pkcs11palFILE_NAME_CLAIM_CERTIFICATE "corePKCS11_Claim_Certificate.dat" /**< The file name of the Provisioning Claim Certificate object. */ #define pkcs11palFILE_NAME_CLAIM_KEY "corePKCS11_Claim_Key.dat" /**< The file name of the Provisioning Claim Key object. */ #define pkcs11palFILE_NAME_CA_CERTIFICATE "corePKCS11_CA_Certificate.dat" /**< The file name of the CA Certificate object. */ +#define pkcs11palFILE_NAME_GG_CA_CERTIFICATE "corePKCS11_GG_CA_Certificate.dat" /**< The file name of the GG CA Certificate object. */ void PAL_UTILS_LabelToFilenameHandle( const char * pcLabel, @@ -122,6 +123,13 @@ void PAL_UTILS_LabelToFilenameHandle( const char * pcLabel, *pcFileName = pkcs11palFILE_NAME_CA_CERTIFICATE; *pHandle = ( CK_OBJECT_HANDLE ) eAwsCaCertificate; } + else if( 0 == strncmp( pkcs11_ROOT_GG_CA_CERT_LABEL, + pcLabel, + sizeof( pkcs11_ROOT_GG_CA_CERT_LABEL ) ) ) + { + *pcFileName = pkcs11palFILE_NAME_GG_CA_CERTIFICATE; + *pHandle = ( CK_OBJECT_HANDLE ) eAwsGGCaCertificate; + } else { *pcFileName = NULL; @@ -198,6 +206,11 @@ CK_RV PAL_UTILS_HandleToFilename( CK_OBJECT_HANDLE xHandle, *pcFileName = pkcs11palFILE_NAME_CA_CERTIFICATE; *pIsPrivate = CK_FALSE; break; + + case eAwsGGCaCertificate: + *pcFileName = pkcs11palFILE_NAME_GG_CA_CERTIFICATE; + *pIsPrivate = CK_FALSE; + break; default: xReturn = CKR_KEY_HANDLE_INVALID; diff --git a/Projects/b_u585i_iot02a_ntz/Src/crypto/core_pkcs11_pal_utils.h b/Projects/b_u585i_iot02a_ntz/Src/crypto/core_pkcs11_pal_utils.h index dff102e28..fed912691 100644 --- a/Projects/b_u585i_iot02a_ntz/Src/crypto/core_pkcs11_pal_utils.h +++ b/Projects/b_u585i_iot02a_ntz/Src/crypto/core_pkcs11_pal_utils.h @@ -52,6 +52,7 @@ enum eObjectHandles eAwsClaimPrivateKey, /**< Provisioning Claim Private Key. */ eAwsClaimCertificate, /**< Provisioning Claim Certificate. */ eAwsCaCertificate, + eAwsGGCaCertificate, /**< GreenGrass CA certificate. */ };