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 000000000..bcc17d50c Binary files /dev/null and b/Documentation/Pictures/CLI.png differ diff --git a/Documentation/Pictures/mqtt_agent_sequence_diagram.png b/Documentation/Pictures/mqtt_agent_sequence_diagram.png new file mode 100644 index 000000000..47802d888 Binary files /dev/null and b/Documentation/Pictures/mqtt_agent_sequence_diagram.png differ diff --git a/Documentation/Pictures/mqtt_agent_sequence_diagram_alternative.png b/Documentation/Pictures/mqtt_agent_sequence_diagram_alternative.png new file mode 100644 index 000000000..0ab97684c Binary files /dev/null and b/Documentation/Pictures/mqtt_agent_sequence_diagram_alternative.png differ diff --git a/Documentation/Pictures/sequence_diagram.png b/Documentation/Pictures/sequence_diagram.png new file mode 100644 index 000000000..49fa41181 Binary files /dev/null and b/Documentation/Pictures/sequence_diagram.png differ diff --git a/Documentation/Pictures/state_machine.png b/Documentation/Pictures/state_machine.png new file mode 100644 index 000000000..bf6ec38d1 Binary files /dev/null and b/Documentation/Pictures/state_machine.png differ diff --git a/Documentation/Readme.md b/Documentation/Readme.md new file mode 100644 index 000000000..9a1a9c28a --- /dev/null +++ b/Documentation/Readme.md @@ -0,0 +1,104 @@ +# Greengrass Discovery + +## Overview + +The Greengrass Discovery feature is designed to implement the discovery and establish a connection to a Greengrass core device. It implements the necessary functionalities: +- sending HTTP discovery request +- parsing JSON response +- storing parsed values in Non-Volatile Memory (NVM) +- establishing a connection to the Greengrass core device using the stored configuration. + +## Project File Structure + +Below is the file structure of the added files related to the AWS Greengrass discovery feature: + +Common/ +├── app +│ ├── greengrass +│ │ ├── greengrass_discovery_task.c +│ │ ├── greengrass_discovery_task.h +│ │ ├── x509_crt_ip_addr_san_verif.patch +│ ├── mqtt +│ │ ├── mqtt_connection_task.c +│ │ ├── mqtt_connection_task.h + +## Global Sequence Diagram +The global sequence diagram below illustrates the overall process: + +![alt text](Pictures/sequence_diagram.png) + + +## Certificate Generation and Import Process + +To successfully establish a secure connection with AWS Greengrass, it is essential to generate a Certificate Signing Request (CSR) using the `pki generate csr