cloud/dps_demo/main.c

/*******************************************************************************
* # License
* Copyright 2019 Silicon Laboratories Inc. www.silabs.com
*******************************************************************************
*
* The licensor of this software is Silicon Laboratories Inc. Your use of this
* software is governed by the terms of Silicon Labs Master Software License
* Agreement (MSLA) available at
* www.silabs.com/about-us/legal/master-software-license-agreement. This
* software is distributed to you in Source Code format and is governed by the
* sections of the MSLA applicable to Source Code.
*
******************************************************************************/
#include "gos.h"
#include "dps_demo_common.h"
#include "nvm_settings.h"
#include "example_app_util.h"
// Timeout value in seconds when waiting for a response from the DMS
#define MESSAGE_RESPONSE_TIMEOUT_SEC 5
// Max expected value length
#define MAX_VALUE_LENGTH 2048
dps_demo_settings_t* dps_settings;
/*************************************************************************************************/
void gos_app_init(void)
{
gos_result_t result;
GOS_LOG("\r\nDPS Demo Starting...");
// Retrieve the demo settings from non-volatile memory (NVM)
if (GOS_FAILED(result, GOS_NVM_GET(DPS, DEMO, dps_settings)))
{
GOS_LOG("Error retrieving configuration from NVM, error code: %d", result);
return;
}
// Upon reboot the device will automatically bring-up the WLAN interface and open the DMS websocket
if(GOS_FAILED(result, gos_load_app_settings_once("settings.ini", DPS_DEMO_SETTINGS_MAGIC)))
{
GOS_LOG("Failed to load settings, err:%d", result);
}
// Check the magic number has been set to avoid attempting to initialize using random values
if( dps_settings->magic != DPS_DEMO_SETTINGS_MAGIC )
{
GOS_LOG("NVM settings uninitialized");
return;
}
// Bring up the network interface
{
GOS_LOG( "Exiting example app..." );
return;
}
// Print helpful information
GOS_LOG("\r\nDPS commands description:");
GOS_LOG(" - List all variables : get dps");
GOS_LOG(" - Set variable : set dps.<var> <val>");
GOS_LOG(" - Initiate device provision request (and obtain device cert/key) : dps_provision");
GOS_LOG(" - Verify if provisioning succeeded (TLS using the device credentials) : dps_verify");
GOS_LOG("\r\nDPS variables description:");
GOS_LOG(" - dps.connector : DMS connector code (make sure your device is activated)");
GOS_LOG(" - dps.hostname : Hostname or IP address of the broker/server");
GOS_LOG(" - dps.port : Port of the provisioning server/broker");
GOS_LOG(" - dps.ca_cert : CA cert filename used for TLS verification request");
GOS_LOG(" - dps.key_filename : Filename used to store the client key (if provisioning succeeded)");
GOS_LOG(" - dps.cert_filename : Filename used to store the client certificate (if provisioning succeeded)");
GOS_LOG("\r\n");
}
/*************************************************************************************************/
gos_result_t validate_device_credentials(dps_demo_settings_t* settings)
{
gos_result_t result;
gos_handle_t handle;
{
.host = settings->hostname,
.remote_port = settings->port,
.certs.ca = settings->ca_cert,
.certs.cert = settings->cert_filename,
.certs.key = settings->key_filename,
.use_tls = 1,
.interface = GOS_INTERFACE_WLAN,
.use_secure_element = false
};
GOS_LOG("Attempting to connect to server...");
result = gos_tcp_connect_with_config(&config, &handle);
if(result != GOS_SUCCESS)
{
GOS_LOG("Failed to open TLS connection, err:%d", result);
}
else
{
GOS_LOG("Connected, handle:%d", handle);
GOS_LOG("Verified, disconnecting...");
}
return result;
}
/*************************************************************************************************/
gos_result_t send_provisioning_request(dps_demo_settings_t* settings)
{
gos_file_t cert_file;
gos_file_t key_file;
gos_result_t result;
// Check if cert/key files exist.. if both exist, device is already provisioned earlier
if( GOS_FAILED(result, gos_file_stat(settings->cert_filename, GOS_FILE_LOCATION_ANY, &cert_file, NULL)) ||
GOS_FAILED(result, gos_file_stat(settings->key_filename, GOS_FILE_LOCATION_ANY, &key_file, NULL)))
{
GOS_LOG("Issue the provision request...", settings->connector);
gos_event_issue(send_message_handler, (void*) settings, GOS_EVENT_FLAG_NONE);
return GOS_SUCCESS;
}
else
{
GOS_LOG("=======================================================================");
GOS_LOG("Cert and key files already exist on device file system...");
GOS_LOG("If you want to re-provision please delete then issue the command again!");
GOS_LOG(" > Cert filename: %s", settings->cert_filename);
GOS_LOG(" > Key filename: %s", settings->key_filename);
GOS_LOG("=======================================================================");
return GOS_ERROR;
}
}
/*************************************************************************************************/
static void send_message_handler(void *args)
{
gos_result_t result;
dps_demo_settings_t* settings = (dps_demo_settings_t*) args;
gos_msgpack_context_t *msgpack = NULL;
{
.length = 0,
.response.handler = message_response_handler,
.response.timeout_ms = MESSAGE_RESPONSE_TIMEOUT_SEC * 1000
};
GOS_LOG("Sending message...");
// Initialize the write context
// The context is dynamically allocated and gets freed if gos_dms_message_write_flush
// succeeds, otherwise it needs to be freed by calling gos_dms_message_context_destroy
if(GOS_FAILED(result, gos_dms_message_write_init(&msgpack, &config)))
{
GOS_LOG("Failed to initialize the message context (DMS websocket ready?), err:%d", result);
return;
}
// Write the message data
populate_message(msgpack, settings->connector);
// Send the message to the DMS
{
GOS_LOG("Failed to send message to DMS, err:%d", result);
GOS_LOG("Try again in %d seconds...", MESSAGE_RESPONSE_TIMEOUT_SEC);
gos_event_register_timed(send_message_handler, (void*) settings, MESSAGE_RESPONSE_TIMEOUT_SEC * 1000, GOS_EVENT_FLAG_NONE);
return;
}
GOS_LOG("Message sent. Wait for DMS response...");
// Now wait for the response
}
/*************************************************************************************************/
static gos_result_t populate_message(gos_msgpack_context_t *msgpack, const char* connector_code)
{
GOS_LOG("Populating the request msgpack (connector code: %s)", connector_code);
gos_msgpack_write_dict_str(msgpack, "request", "provision");
gos_msgpack_write_dict_str(msgpack, "code", connector_code);
return GOS_SUCCESS;
}
/*************************************************************************************************/
static void message_response_handler(void *msg)
{
gos_result_t result;
if(msg != NULL)
{
GOS_LOG("DMS response received...");
// Recursively iterate through all elements in the message and print to log
if(GOS_FAILED(result, MSGPACK_FOREACH_RECURSIVE(msg, msgpack_iterator, NULL)))
{
GOS_LOG("Failed to iterate received message, err:%d", result);
}
// Clean up message
}
else
{
GOS_LOG("Response timed-out, please issue the request again!");
}
}
/*************************************************************************************************/
static gos_result_t msgpack_iterator(const gos_msgpack_object_t *key, const gos_msgpack_object_t *value, void *arg)
{
char key_str[128];
if(key != NULL)
{
gos_dynamic_buffer_t value_buffer;
char* value_str;
if (GOS_FAILED(result, gos_dynamic_buffer_alloc(&value_buffer, MAX_VALUE_LENGTH)))
{
GOS_LOG("Failed to allocate buffer, error: %d", result);
return GOS_NO_MEM;
}
value_str = (char*) value_buffer.buffer;
MSGPACK_STR(key, key_str, sizeof(key_str));
MSGPACK_TO_STR(value, value_str, MAX_VALUE_LENGTH);
if(strcmp(key_str, "cert") == 0)
{
if (GOS_FAILED(result, write_client_cert(value_str)))
{
GOS_LOG("Failed with client cert: %d", result);
}
}
else if(strcmp(key_str, "key") == 0)
{
if (GOS_FAILED(result, write_client_key(value_str)))
{
GOS_LOG("Failed with client key: %d", result);
}
}
else if(strcmp(key_str, "deviceId") == 0)
{
// This is only returned in case of Azure Hub
GOS_LOG("Device ID: %s", value_str);
}
else if(strcmp(key_str, "assignedHub") == 0)
{
// This is only returned in case of Azure Hub
GOS_LOG("IoT Hub: %s", value_str);
}
else if(strcmp(key_str, "error") == 0)
{
GOS_LOG("ERROR: %s", value_str);
}
else
{
// Other keys are not handled...
GOS_LOG("%s: <ignored>", key_str);
}
gos_dynamic_buffer_free(&value_buffer);
}
return result;
}
/*************************************************************************************************/
static gos_result_t write_client_cert(char *value_str)
{
gos_result_t result;
gos_handle_t handle;
file.size = strlen(value_str);
strncpy(file.name, dps_settings->cert_filename, MAX_FILENAME_LENGTH);
//GOS_LOG("Cert encoded (%u bytes): %s", bytes_encoded, cert_pem);
GOS_LOG("Certificate PEM (%u bytes):\r\n%.*s...\r\n", strlen(value_str), 200, value_str);
// Delete the file if it already exists
gos_file_delete(dps_settings->cert_filename, GOS_FILE_LOCATION_ANY);
// Create the file
if (GOS_FAILED(result, gos_file_create(&file, &handle)))
{
GOS_LOG("Failed to create client cert file: %d", result);
return result;
}
// Write the data
if (GOS_FAILED(result, gos_file_write(handle, value_str, strlen(value_str))))
{
GOS_LOG("Failed to write client cert data: %d", result);
gos_file_close(handle);
gos_file_delete(dps_settings->cert_filename, GOS_FILE_LOCATION_ANY);
return result;
}
gos_file_close(handle);
return GOS_SUCCESS;
}
/*************************************************************************************************/
static gos_result_t write_client_key(char *value_str)
{
gos_result_t result;
gos_handle_t handle;
file.size = strlen(value_str);
strncpy(file.name, dps_settings->key_filename, MAX_FILENAME_LENGTH);
//GOS_LOG("Key encoded (%u bytes): %s", bytes_encoded, key_pem);
GOS_LOG("Key PEM (%u bytes):\r\n%.*s...\r\n", strlen(value_str), 200, value_str);
// Delete the file if it already exists
gos_file_delete(dps_settings->key_filename, GOS_FILE_LOCATION_ANY);
// Create the file
if (GOS_FAILED(result, gos_file_create(&file, &handle)))
{
GOS_LOG("Failed to create client key file: %d", result);
return result;
}
// Write the data
if (GOS_FAILED(result, gos_file_write(handle, value_str, strlen(value_str))))
{
GOS_LOG("Failed to write client key data: %d", result);
gos_file_close(handle);
gos_file_delete(dps_settings->key_filename, GOS_FILE_LOCATION_ANY);
return result;
}
gos_file_close(handle);
return GOS_SUCCESS;
}