6.8. EdgeLock 2GO Agent Examples

The usage of EdgeLock 2GO agent is demonstrated with the following example demo source code. The source code example is taken from the following file <SE05X_root_folder>/simw-top/nxp_iot_agent/ex/src/iot_agent_demo.c.

6.8.1. Initializaton of context and updating device configuration

Initialization of contexts of EdgeLock 2GO agent its keystores and datastores

    agent_status = iot_agent_init(&iot_agent_context);
    AGENT_SUCCESS_OR_EXIT();

	// print the uid of the device
	agent_status = iot_agent_print_uid((sss_se05x_session_t*)&pCtx->session);
	AGENT_SUCCESS_OR_EXIT();

    /************* Register keystore*************/

	agent_status = iot_agent_keystore_sss_se05x_init(&keystore, EDGELOCK2GO_KEYSTORE_ID, pCtx, true);
	AGENT_SUCCESS_OR_EXIT();

	agent_status = iot_agent_register_keystore(&iot_agent_context, &keystore);
    AGENT_SUCCESS_OR_EXIT();

    /************* Register datastore*************/

	// This is the datastore that holds connection information on how to connect to
	// the EdgeLock 2GO cloud service itself. Typically this would be a persistent
	// datastore which supports transactions to allow for atomic updates of
	// the URL/port/etc. If a filesystem is available, here, we use a file-based
	// datastore.
	// Note, that the ID of the datastore is a given.
#if DS_FS
	agent_status = iot_agent_datastore_fs_init(&edgelock2go_datastore,
		nxp_iot_DatastoreIdentifiers_DATASTORE_EDGELOCK2GO_ID, gszEdgeLock2GoDatastoreFilename,
		&iot_agent_service_is_configuration_data_valid);
#elif DS_PLAIN
	agent_status = iot_agent_datastore_plain_init(&edgelock2go_datastore,
		nxp_iot_DatastoreIdentifiers_DATASTORE_EDGELOCK2GO_ID);
#endif
	AGENT_SUCCESS_OR_EXIT();

	// If the contents of the datastore for the connectin to the EdgeLock 2O datastore
	// are not valid (e.g. on the first boot), fill the datastore with contents
	// from the settings contained in nxp_iot_agent_config.h
	if (!iot_agent_service_is_configuration_data_valid(&edgelock2go_datastore)) {
		iot_agent_utils_write_edgelock2go_datastore(&keystore, &edgelock2go_datastore,
			EDGELOCK2GO_HOSTNAME, EDGELOCK2GO_PORT, iot_agent_trusted_root_ca_certificates);
	}

	// For connecting to the EdgeLock 2GO cloud service, we also need to register the
	// datastore that contains the information on how to connect there.
	agent_status = iot_agent_set_edgelock2go_datastore(&iot_agent_context, &edgelock2go_datastore);
	AGENT_SUCCESS_OR_EXIT();

#if DS_FS
	agent_status = iot_agent_datastore_fs_init(&datastore, 0U, gszDatastoreFilename,
		&iot_agent_service_is_configuration_data_valid);
	AGENT_SUCCESS_OR_EXIT();
#endif
#if DS_PLAIN
	agent_status = iot_agent_datastore_plain_init(&datastore, 0U);
	AGENT_SUCCESS_OR_EXIT();
#endif
	agent_status = iot_agent_register_datastore(&iot_agent_context, &datastore);
    AGENT_SUCCESS_OR_EXIT();

Update device configuration:

	agent_status = iot_agent_update_device_configuration(&iot_agent_context, &status_report);

	// We have a status report which can have some details on the operations that happened.
	// This gives the opportunity to programmatically react on errors/failures. For demonstration
	// purpose, print that information to the console here.
	if ((agent_status == IOT_AGENT_SUCCESS || agent_status == IOT_AGENT_UPDATE_FAILED) && status_report.has_status) {
		print_status_report(&status_report);
	}

	AGENT_SUCCESS_OR_EXIT();

Iterate over configured services and access credentials and service configuration data:

    if (!iot_agent_is_service_configuration_data_valid(&iot_agent_context))
    {
        EXIT_STATUS_MSG(IOT_AGENT_FAILURE, "Not all configuration data is valid");
    }

    // get total number of services
    number_of_services = iot_agent_get_number_of_services(&iot_agent_context);
    IOT_AGENT_INFO("Found configuration data for %d services.", (int) number_of_services);

6.8.2. MQTT connection tests with Cloud Onboarding Provisioning

The example shows how to trigger the MQTT connection tests to the services when they are provisioned through the Cloud Onboaring Provisioning; the connection is triggered from following file: <SE05X_root_folder>/simw-top/nxp_iot_agent/ex/src/iot_agent_demo.c

#if (AX_EMBEDDED && defined(USE_RTOS) && USE_RTOS == 1) || (SSS_HAVE_HOSTCRYPTO_OPENSSL)
    agent_status = iot_agent_verify_mqtt_connection(&iot_agent_context);
    AGENT_SUCCESS_OR_EXIT();
#endif

In case of FreeRTOS platform as LPC55S or FRDM_K64F the MQTT FreeRTOS client will be used for connection; implementation details can be found under <SE05X_root_folder>/simw-top/nxp_iot_agent/ex/src/utils/iot_agent_mqtt_freertos.c:

iot_agent_status_t iot_agent_verify_mqtt_connection_for_service(iot_agent_context_t* iot_agent_context,
        const nxp_iot_ServiceDescriptor* service_descriptor)
{
    iot_agent_status_t agent_status = IOT_AGENT_SUCCESS;
    agent_status = pubSub(iot_agent_context, service_descriptor);
    AGENT_SUCCESS_OR_EXIT_MSG("MQTT connection test failed");
exit:
    return agent_status;
}

iot_agent_status_t iot_agent_verify_mqtt_connection(iot_agent_context_t* iot_agent_context)
{
    iot_agent_status_t agent_status = IOT_AGENT_SUCCESS;
    size_t number_of_services = 0U;
	nxp_iot_ServiceDescriptor service_descriptor = nxp_iot_ServiceDescriptor_init_default;

    number_of_services = iot_agent_get_number_of_services(iot_agent_context);
    for (size_t i = 0U; i < number_of_services; i++)
    {
		agent_status = iot_agent_select_service_by_index(iot_agent_context, i, &service_descriptor);
		AGENT_SUCCESS_OR_EXIT();

		agent_status = iot_agent_verify_mqtt_connection_for_service(iot_agent_context, &service_descriptor);
		AGENT_SUCCESS_OR_EXIT();
	}
exit:
	iot_agent_free_service_descriptor(&service_descriptor);
    return agent_status;
}

In case of Open SSL platform as iMX6 or iMX8 the MQTT Paho client will be used for connection; implementation details can be found under <SE05X_root_folder>/simw-top/nxp_iot_agent/ex/src/utils/iot_agent_mqtt_paho.c:

iot_agent_status_t iot_agent_verify_mqtt_connection_for_service(iot_agent_context_t* iot_agent_context, const nxp_iot_ServiceDescriptor* service_descriptor)
{
	iot_agent_status_t agent_status = IOT_AGENT_SUCCESS;
	int mkdir_check = 0;

#if IOT_AGENT_MQTT_CONNECTION_TEST_ENABLE
	mqtt_connection_params_t connection_params = { 0 };
	iot_agent_status_t mqtt_status = IOT_AGENT_SUCCESS;
#endif
	//write to files

	char config_filename[256];
	char certificate_filename[256];
	char keyref_filename[256];
	char server_cert_filename[256];
	char metadata_filename[256];

	const char* service_type_str;
	char service_dir[64];

	uint32_t keystore_id = service_descriptor->client_key_sss_ref.endpoint_id;
	iot_agent_keystore_t* keystore = NULL;
	agent_status = iot_agent_get_keystore_by_id(iot_agent_context, keystore_id, &keystore);
	AGENT_SUCCESS_OR_EXIT();

    agent_status = iot_agent_keystore_open_session(keystore);
    AGENT_SUCCESS_OR_EXIT();

	agent_status = iot_agent_service_get_service_type_as_string(service_descriptor, &service_type_str);
	AGENT_SUCCESS_OR_EXIT();

	if (ACCESS(goutput_directory, R_OK) != 0)
	{
		mkdir_check = DO_MKDIR(goutput_directory);
		ASSERT_OR_EXIT(mkdir_check == 0);
	}

	sprintf(service_dir, "%s%c%s%c", goutput_directory, path_separator, service_type_str, path_separator);
	if (ACCESS(service_dir, R_OK) != 0)
	{
		mkdir_check = DO_MKDIR(service_dir);
		ASSERT_OR_EXIT(mkdir_check == 0);
	}

	sprintf(config_filename, "%s" SERVICE_CONFIGURATION_PATTERN, service_dir, service_descriptor->identifier);
	sprintf(metadata_filename, "%s" SERVICE_METADATA_PATTERN, service_dir, service_descriptor->identifier);
	sprintf(keyref_filename, "%s" SERVICE_KEYREF_PATTERN, service_dir, service_descriptor->identifier);
	sprintf(certificate_filename, "%s" SERVICE_CERTIFICATE_PATTERN, service_dir, service_descriptor->identifier);
	sprintf(server_cert_filename, "%s" SERVICE_SERVER_CERT_PATTERN, service_dir, service_type_str, service_descriptor->identifier);

	//create service files
	char relative_certificate_filename[128];
	char relative_keyref_filename[128];
	char relative_server_cert_filename[128];
	sprintf(relative_certificate_filename, SERVICE_CERTIFICATE_PATTERN, service_descriptor->identifier);
	sprintf(relative_keyref_filename, SERVICE_KEYREF_PATTERN, service_descriptor->identifier);
	sprintf(relative_server_cert_filename, SERVICE_SERVER_CERT_PATTERN, service_type_str, service_descriptor->identifier);

	if (IOT_AGENT_SUCCESS != write_service_configuration(service_descriptor, config_filename,
		relative_certificate_filename, relative_keyref_filename, relative_server_cert_filename))
	{
		write_error_logs(config_filename);
	}
	else
	{
		LOG_D("Created service configuration file [%s]", config_filename);
	}

	if (IOT_AGENT_SUCCESS != write_service_metadata(service_descriptor, metadata_filename))
	{
		write_error_logs(metadata_filename);
	}
	else
	{
		LOG_D("Created metadata file [%s]", metadata_filename);
	}

	if (service_descriptor->server_certificate != NULL) {
		if (IOT_AGENT_SUCCESS != iot_agent_utils_write_certificate_pem(service_descriptor->server_certificate->bytes,
			(size_t)service_descriptor->server_certificate->size, server_cert_filename))
		{
			write_error_logs(server_cert_filename);
		}
		else
		{
			LOG_D("Created server root certificate file [%s]", server_cert_filename);
		}
	}

	if (service_descriptor->client_certificate != NULL) {
		if (IOT_AGENT_SUCCESS != iot_agent_utils_write_certificate_pem(service_descriptor->client_certificate->bytes,
			(size_t)service_descriptor->client_certificate->size, certificate_filename))
		{
			write_error_logs(certificate_filename);
		}
		else
		{
			LOG_D("Created service client certificate file [%s]", certificate_filename);
		}
	}

	if (IOT_AGENT_SUCCESS != iot_agent_utils_write_key_ref_service_pem(iot_agent_context, keyref_filename))
	{
		write_error_logs(keyref_filename);
	}
	else
	{
		LOG_D("Created service keyref file [%s]", keyref_filename);
	}
	//write to files - end

#if IOT_AGENT_MQTT_CONNECTION_TEST_ENABLE
	iot_agent_keystore_close_session(keystore);

	connection_params.keypath = keyref_filename;
	connection_params.devcert = certificate_filename;
	connection_params.rootpath = server_cert_filename;
	mqtt_status = iot_agent_mqtt_test(service_descriptor, &connection_params);

	agent_status = iot_agent_keystore_open_session(keystore);
	AGENT_SUCCESS_OR_EXIT();
#endif
exit:
	if (mqtt_status != IOT_AGENT_SUCCESS) {
		return mqtt_status;
	}
	return agent_status;
}

iot_agent_status_t iot_agent_verify_mqtt_connection(iot_agent_context_t* iot_agent_context)
{
    iot_agent_status_t agent_status = IOT_AGENT_SUCCESS;
    size_t number_of_services = 0U;
	nxp_iot_ServiceDescriptor service_descriptor = nxp_iot_ServiceDescriptor_init_default;

    number_of_services = iot_agent_get_number_of_services(iot_agent_context);
	delete_old_service_files_folders(goutput_directory);
    AGENT_SUCCESS_OR_EXIT();

    for (size_t i = 0U; i < number_of_services; i++)
    {
		agent_status = iot_agent_select_service_by_index(iot_agent_context, i, &service_descriptor);
		AGENT_SUCCESS_OR_EXIT();

		agent_status = iot_agent_verify_mqtt_connection_for_service(iot_agent_context, &service_descriptor);
		AGENT_SUCCESS_OR_EXIT();
	}
exit:
	iot_agent_free_service_descriptor(&service_descriptor);
    return agent_status;
}

6.8.3. MQTT connection tests with Remote Trust Provisioning

The example shows how to trigger the MQTT connection tests to the services when they are provisioned through the Remote Trust Provisioning; the connection is triggered from following file: <SE05X_root_folder>/simw-top/nxp_iot_agent/ex/src/iot_agent_demo.c

#if	((AX_EMBEDDED && defined(USE_RTOS) && USE_RTOS == 1) || (SSS_HAVE_HOSTCRYPTO_OPENSSL)) && (IOT_AGENT_COS_OVER_RTP_ENABLE == 1)
	// example code for MQTT conneciton when provisioning services as RTP objects
	iot_agent_cleanup_mqtt_config_files_cos_over_rtp();
	AGENT_SUCCESS_OR_EXIT();
	// use this code for AWS services
	// modify the connection parameters to allow connection to your AWS account
	agent_status = iot_agent_get_mqtt_service_descriptor_for_aws(&iot_agent_context, &aws_service_descriptor);
	AGENT_SUCCESS_OR_EXIT();
	// in case of AWS device auto-registration the MQTT connection will fail on the first connection; in this
	// use case add additional call to the funcion iot_agent_verify_mqtt_connection_cos_over_rtp
	agent_status = iot_agent_verify_mqtt_connection_cos_over_rtp(&iot_agent_context, &aws_service_descriptor);
	AGENT_SUCCESS_OR_EXIT();

	// use this code for Azure services
	agent_status = iot_agent_get_service_descriptor_for_azure(&iot_agent_context, &azure_service_descriptor);
	AGENT_SUCCESS_OR_EXIT();
	agent_status = iot_agent_verify_mqtt_connection_cos_over_rtp(&iot_agent_context, &azure_service_descriptor);
	AGENT_SUCCESS_OR_EXIT();
#endif

To enable the MQTT connection for the RTP use case some manual adjustments needs to be done in the Agent code; the use case can be enabled by setting the variable IOT_AGENT_COS_OVER_RTP_ENABLE to 1 in the file <SE05X_root_folder>/simw-top/nxp_iot_agent/inc/nxp_iot_agent_config.h

The user has to setup some configuration parameters for the service to which he want to connect; this can be done by changing some definitions in the file: <SE05X_root_folder>/simw-top/nxp_iot_agent/ex/inc/iot_agent_config.h

// CHANGE THIS : fill with key pair and device certificate object IDs as defined on EL2GO when generating them
#define AWS_SERVICE_KEY_PAIR_ID		0x83000101
#define AWS_SERVICE_DEVICE_CERT_ID	0x83000102

// CHANGE THIS: the AWS hostname to which the device will connect
#define AWS_HOSTNAME	"aw9969rp3sm22-ats.iot.eu-central-1.amazonaws.com"

// optional: the client ID will be by default set dynamically to the Common Name of the
// device leaf certficate; is possible to hardocode it, by uncommenting the below line and assign
// the desired value
//#define AWS_CLIENT_ID		"awsrtptest-0000000000001e6e-0000"

// CHANGE THIS: set the desired service ID; is used internally by the MQTT client, should be unique
// for every service
#define AWS_SERVICE_ID	101

// CHANGE THIS : fill with key pair and device certificate object IDs as defined on EL2GO when generating them
#define AZURE_SERVICE_KEY_PAIR_ID	0x83000211
#define AZURE_SERVICE_DEVICE_CERT_ID	0x83000212

// CHANGE THIS : set the ID scope and the global endpoint as defined in the Azure DPS account
#define AZURE_ID_SCOPE	"0ne00068F95"
#define AZURE_GLOBAL_DEVICE_ENDPOINT	"global.azure-devices-provisioning.net"

// optional: the registration ID will be by default set dynamically to the Common Name of the
// device leaf certficate; is possible to hardocode it, by uncommenting the below line and assign
// the desired value
//#define AZURE_REGISTRATION_ID	"azurertptest-0000000000001e51-0000"

// CHANGE THIS: set the desired service ID; is used internally by the MQTT client, should be unique
// for every service
#define AZURE_SERVICE_ID	102

Depending on the service type the Service Descriptor should be set to include the correct connection data and object IDs of the Key Pair and X.509 device leaf certificate associated with the service. The following functions should be modified in the file <SE05X_root_folder>/simw-top/nxp_iot_agent/ex/src/iot_agent_demo.c

iot_agent_status_t iot_agent_get_mqtt_service_descriptor_for_aws(iot_agent_context_t* iot_agent_context,
	nxp_iot_ServiceDescriptor* service_descriptor)
{
	iot_agent_status_t agent_status = IOT_AGENT_SUCCESS;

	ASSERT_OR_EXIT_MSG(service_descriptor != NULL, "Service descriptor is null");

	// AWS Service type
	service_descriptor->identifier = AWS_SERVICE_ID;
	service_descriptor->has_service_type = true;
	service_descriptor->service_type = nxp_iot_ServiceType_AWSSERVICE;

	// service key pair
	service_descriptor->has_client_key_sss_ref = true;
	service_descriptor->client_key_sss_ref.object_id = AWS_SERVICE_KEY_PAIR_ID;
	service_descriptor->client_key_sss_ref.has_type = false;
	service_descriptor->client_key_sss_ref.type = nxp_iot_EndpointType_INVALID;
	service_descriptor->client_key_sss_ref.has_endpoint_id = true;
	service_descriptor->client_key_sss_ref.endpoint_id = EDGELOCK2GO_KEYSTORE_ID;
	service_descriptor->client_key_sss_ref.has_object_id = true;

	// client certificate
	service_descriptor->has_client_certificate_sss_ref = true;
	service_descriptor->client_certificate_sss_ref.object_id = AWS_SERVICE_DEVICE_CERT_ID;
	service_descriptor->client_certificate_sss_ref.has_type = false;
	service_descriptor->client_certificate_sss_ref.type = nxp_iot_EndpointType_INVALID;
	service_descriptor->client_certificate_sss_ref.has_endpoint_id = true;
	service_descriptor->client_certificate_sss_ref.endpoint_id = EDGELOCK2GO_KEYSTORE_ID;
	service_descriptor->client_certificate_sss_ref.has_object_id = true;

	// AWS server certificate
	service_descriptor->server_certificate = (pb_bytes_array_t*)&aws_root_server_cert;
	service_descriptor->has_server_certificate_sss_ref = false;

	// AWS MQTT connection parameters
	char hostname[] = AWS_HOSTNAME;
	iot_agent_fill_service_char_array(&service_descriptor->hostname, hostname, sizeof(hostname));

	service_descriptor->has_port = true;
	service_descriptor->port = 8883;
	service_descriptor->has_timeout_ms = true;
	service_descriptor->timeout_ms = 0x4E20;
	service_descriptor->has_protocol = true;
	service_descriptor->protocol = nxp_iot_ServiceProtocolType_MQTTS;

#ifdef AWS_CLIENT_ID
	char client_id[] = AWS_CLIENT_ID;
	iot_agent_fill_service_char_array(&service_descriptor->client_id, client_id, sizeof(client_id));
#else
	service_descriptor->client_id = malloc(COMMON_NAME_MAX_SIZE);
	memset(service_descriptor->client_id, 0, COMMON_NAME_MAX_SIZE);
	agent_status = iot_agent_utils_get_certificate_common_name(iot_agent_context, service_descriptor, service_descriptor->client_id, COMMON_NAME_MAX_SIZE);
	AGENT_SUCCESS_OR_EXIT();
#endif

	service_descriptor->username = NULL;
	service_descriptor->password = NULL;

	// fill metadata if any
	service_descriptor->customer_metadata_json = NULL;

exit:
	return agent_status;
}

iot_agent_status_t iot_agent_get_service_descriptor_for_azure(iot_agent_context_t* iot_agent_context,
	nxp_iot_ServiceDescriptor* service_descriptor)
{
	iot_agent_status_t agent_status = IOT_AGENT_SUCCESS;

	ASSERT_OR_EXIT_MSG(service_descriptor != NULL, "Service descriptor is null");

	// Azure Service type
	service_descriptor->identifier = AZURE_SERVICE_ID;
	service_descriptor->has_service_type = true;
	service_descriptor->service_type = nxp_iot_ServiceType_AZURESERVICE;

	// service key pair
	service_descriptor->has_client_key_sss_ref = true;
	service_descriptor->client_key_sss_ref.object_id = AZURE_SERVICE_KEY_PAIR_ID;
	service_descriptor->client_key_sss_ref.has_type = false;
	service_descriptor->client_key_sss_ref.type = nxp_iot_EndpointType_INVALID;
	service_descriptor->client_key_sss_ref.has_endpoint_id = true;
	service_descriptor->client_key_sss_ref.endpoint_id = EDGELOCK2GO_KEYSTORE_ID;
	service_descriptor->client_key_sss_ref.has_object_id = true;

	// service client certificate
	service_descriptor->has_client_certificate_sss_ref = true;
	service_descriptor->client_certificate_sss_ref.object_id = AZURE_SERVICE_DEVICE_CERT_ID;
	service_descriptor->client_certificate_sss_ref.has_type = false;
	service_descriptor->client_certificate_sss_ref.type = nxp_iot_EndpointType_INVALID;
	service_descriptor->client_certificate_sss_ref.has_endpoint_id = true;
	service_descriptor->client_certificate_sss_ref.endpoint_id = EDGELOCK2GO_KEYSTORE_ID;
	service_descriptor->client_certificate_sss_ref.has_object_id = true;

	// Azure server certificate
	service_descriptor->server_certificate = (pb_bytes_array_t*)&azure_root_server_cert;
	service_descriptor->has_server_certificate_sss_ref = false;

	// Azure MQTT connection parameters
	char azure_id_scope[] = AZURE_ID_SCOPE;
	iot_agent_fill_service_char_array(&service_descriptor->azure_id_scope, azure_id_scope, sizeof(azure_id_scope));

	// the registration ID will be by defualt set dynamically to the Common Name of the device leaf certficate;
	// it is possible to hardocode it, by uncommenting the AZURE_REGISTRATION_ID in the configuration file
#ifdef AZURE_REGISTRATION_ID
	char azure_registration_id[] = AZURE_REGISTRATION_ID;
	iot_agent_fill_service_char_array(&service_descriptor->azure_registration_id, azure_registration_id, sizeof(azure_registration_id));
#else
	service_descriptor->azure_registration_id = malloc(COMMON_NAME_MAX_SIZE);
	memset(service_descriptor->azure_registration_id, 0, COMMON_NAME_MAX_SIZE);
	agent_status = iot_agent_utils_get_certificate_common_name(iot_agent_context, service_descriptor, service_descriptor->azure_registration_id, COMMON_NAME_MAX_SIZE);
	AGENT_SUCCESS_OR_EXIT();
#endif

	char azure_global_device_endpoint[] = AZURE_GLOBAL_DEVICE_ENDPOINT;
	iot_agent_fill_service_char_array(&service_descriptor->azure_global_device_endpoint, azure_global_device_endpoint, sizeof(azure_global_device_endpoint));

	service_descriptor->has_port = false;
	service_descriptor->port = 0;
	service_descriptor->has_timeout_ms = true;
	service_descriptor->timeout_ms = 0x4E20;
	service_descriptor->has_protocol = false;
	service_descriptor->username = NULL;
	service_descriptor->password = NULL;

	// fill metadata if any
	service_descriptor->customer_metadata_json = NULL;

exit:
	return agent_status;
}

In the case of AWS auto-registration the Intermediate certificate used to sign the device leaf certificate needs to be presented to the cloud when doing the registration; EdgeLock 2GO offers the possibility to provision the intermediate CA together with the leaf certificate in one combined object. The MQTT Client on Embedded Linux devices is able to automatically load the combined object and execute the auto-registration. On FreeRTOS devices the dynamic loading is not implemented and the intermediate certificate must be provided in the code; to achieve this, the value of the #define keyJITR_DEVICE_CERTIFICATE_AUTHORITY_PEM in file <SE05X_root_folder>/simw-top/demos/ksdk/common/aws_clientcredential_keys.h must be filled with the value of the intermediate certificate in PEM format.