Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
Dependents: sht15_remote_monitoring RobotArmDemo iothub_client_sample_amqp iothub_client_sample_amqp ... more
Revision 30:20a85b733111, committed 2017-03-10
- Comitter:
- AzureIoTClient
- Date:
- Fri Mar 10 11:46:55 2017 -0800
- Parent:
- 29:7e8852b14e3b
- Child:
- 31:adadaef857c1
- Commit message:
- 1.1.9
Changed in this revision
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/iothubtransport_amqp_cbs_auth.c Fri Mar 10 11:46:55 2017 -0800
@@ -0,0 +1,982 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+#include <stdlib.h>
+#include "iothubtransport_amqp_cbs_auth.h"
+#include "azure_c_shared_utility/optimize_size.h"
+#include "azure_c_shared_utility/gballoc.h"
+#include "azure_c_shared_utility/agenttime.h"
+#include "azure_c_shared_utility/xlogging.h"
+#include "azure_c_shared_utility/sastoken.h"
+
+#define RESULT_OK 0
+#define INDEFINITE_TIME ((time_t)(-1))
+#define SAS_TOKEN_TYPE "servicebus.windows.net:sastoken"
+#define IOTHUB_DEVICES_PATH_FMT "%s/devices/%s"
+#define DEFAULT_CBS_REQUEST_TIMEOUT_SECS UINT32_MAX
+#define DEFAULT_SAS_TOKEN_LIFETIME_SECS 3600
+#define DEFAULT_SAS_TOKEN_REFRESH_TIME_SECS 1800
+
+typedef enum CREDENTIAL_TYPE_TAG
+{
+ CREDENTIAL_TYPE_NONE,
+ DEVICE_PRIMARY_KEY,
+ DEVICE_SECONDARY_KEY,
+ USER_PROVIDED_SAS_TOKEN
+} CREDENTIAL_TYPE;
+
+typedef struct AUTHENTICATION_INSTANCE_TAG
+{
+ STRING_HANDLE device_id;
+ STRING_HANDLE iothub_host_fqdn;
+
+ STRING_HANDLE device_sas_token;
+ STRING_HANDLE device_primary_key;
+ STRING_HANDLE device_secondary_key;
+
+ ON_AUTHENTICATION_STATE_CHANGED_CALLBACK on_state_changed_callback;
+ void* on_state_changed_callback_context;
+
+ ON_AUTHENTICATION_ERROR_CALLBACK on_error_callback;
+ void* on_error_callback_context;
+
+ size_t cbs_request_timeout_secs;
+ size_t sas_token_lifetime_secs;
+ size_t sas_token_refresh_time_secs;
+
+ AUTHENTICATION_STATE state;
+ CBS_HANDLE cbs_handle;
+
+ bool is_cbs_put_token_in_progress;
+ bool is_sas_token_refresh_in_progress;
+
+ time_t current_sas_token_put_time;
+
+ CREDENTIAL_TYPE current_credential_in_use;
+} AUTHENTICATION_INSTANCE;
+
+
+// Helper functions:
+
+static int get_seconds_since_epoch(double *seconds)
+{
+ int result;
+ time_t current_time;
+
+ if ((current_time = get_time(NULL)) == INDEFINITE_TIME)
+ {
+ LogError("Failed getting the current local time (get_time() failed)");
+ result = __FAILURE__;
+ }
+ else
+ {
+ *seconds = get_difftime(current_time, (time_t)0);
+
+ result = RESULT_OK;
+ }
+
+ return result;
+}
+
+static void update_state(AUTHENTICATION_INSTANCE* instance, AUTHENTICATION_STATE new_state)
+{
+ if (new_state != instance->state)
+ {
+ AUTHENTICATION_STATE previous_state = instance->state;
+ instance->state = new_state;
+
+ if (instance->on_state_changed_callback != NULL)
+ {
+ instance->on_state_changed_callback(instance->on_state_changed_callback_context, previous_state, new_state);
+ }
+ }
+}
+
+static void notify_error(AUTHENTICATION_INSTANCE* instance, AUTHENTICATION_ERROR_CODE error_code)
+{
+ if (instance->on_error_callback != NULL)
+ {
+ instance->on_error_callback(instance->on_error_callback_context, error_code);
+ }
+}
+
+static int verify_cbs_put_token_timeout(AUTHENTICATION_INSTANCE* instance, bool* is_timed_out)
+{
+ int result;
+
+ if (instance->current_sas_token_put_time == INDEFINITE_TIME)
+ {
+ result = __FAILURE__;
+ LogError("Failed verifying if cbs_put_token has timed out (current_sas_token_put_time is not set)");
+ }
+ else
+ {
+ time_t current_time;
+
+ if ((current_time = get_time(NULL)) == INDEFINITE_TIME)
+ {
+ result = __FAILURE__;
+ LogError("Failed verifying if cbs_put_token has timed out (get_time failed)");
+ }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_083: [authentication_do_work() shall check for authentication timeout comparing the current time since `instance->current_sas_token_put_time` to `instance->cbs_request_timeout_secs`]
+ else if ((uint32_t)get_difftime(current_time, instance->current_sas_token_put_time) >= instance->cbs_request_timeout_secs)
+ {
+ *is_timed_out = true;
+ result = RESULT_OK;
+ }
+ else
+ {
+ *is_timed_out = false;
+ result = RESULT_OK;
+ }
+ }
+
+ return result;
+}
+
+static int verify_sas_token_refresh_timeout(AUTHENTICATION_INSTANCE* instance, bool* is_timed_out)
+{
+ int result;
+
+ if (instance->current_sas_token_put_time == INDEFINITE_TIME)
+ {
+ result = __FAILURE__;
+ LogError("Failed verifying if SAS token refresh timed out (current_sas_token_put_time is not set)");
+ }
+ else
+ {
+ time_t current_time;
+
+ if ((current_time = get_time(NULL)) == INDEFINITE_TIME)
+ {
+ result = __FAILURE__;
+ LogError("Failed verifying if SAS token refresh timed out (get_time failed)");
+ }
+ else if ((uint32_t)get_difftime(current_time, instance->current_sas_token_put_time) >= instance->sas_token_refresh_time_secs)
+ {
+ *is_timed_out = true;
+ result = RESULT_OK;
+ }
+ else
+ {
+ *is_timed_out = false;
+ result = RESULT_OK;
+ }
+ }
+
+ return result;
+}
+
+static STRING_HANDLE create_devices_path(STRING_HANDLE iothub_host_fqdn, STRING_HANDLE device_id)
+{
+ STRING_HANDLE devices_path;
+
+ if ((devices_path = STRING_new()) == NULL)
+ {
+ LogError("Failed creating devices_path (STRING_new failed)");
+ }
+ else
+ {
+ const char* device_id_c_str = STRING_c_str(device_id);
+ const char* iothub_host_fqdn_c_str = STRING_c_str(iothub_host_fqdn);
+
+ if (STRING_sprintf(devices_path, IOTHUB_DEVICES_PATH_FMT, iothub_host_fqdn_c_str, device_id_c_str) != RESULT_OK)
+ {
+ STRING_delete(devices_path);
+ devices_path = NULL;
+ LogError("Failed creating devices_path (STRING_sprintf failed)");
+ }
+ }
+
+ return devices_path;
+}
+
+
+static bool are_device_keys_used_for_authentication(AUTHENTICATION_INSTANCE* instance)
+{
+ return (instance->current_credential_in_use == DEVICE_PRIMARY_KEY || instance->current_credential_in_use == DEVICE_SECONDARY_KEY);
+}
+
+// @returns 0 there is one more device key to be attempted, !=0 otherwise.
+static int mark_current_device_key_as_invalid(AUTHENTICATION_INSTANCE* instance)
+{
+ int result;
+
+ if (instance->current_credential_in_use == DEVICE_PRIMARY_KEY)
+ {
+ if (instance->device_secondary_key != NULL)
+ {
+ instance->current_credential_in_use = DEVICE_SECONDARY_KEY;
+ LogError("Primary key of device '%s' was marked as invalid. Using secondary key now", STRING_c_str(instance->device_id));
+ result = 0;
+ }
+ else
+ {
+ instance->current_credential_in_use = CREDENTIAL_TYPE_NONE;
+ LogError("Primary key of device '%s' was marked as invalid. No other device keys available", STRING_c_str(instance->device_id));
+ result = __FAILURE__;
+ }
+ }
+ else if (instance->current_credential_in_use == DEVICE_SECONDARY_KEY)
+ {
+ instance->current_credential_in_use = CREDENTIAL_TYPE_NONE;
+ LogError("Secondary key of device '%s' was marked as invalid. No other device keys available", STRING_c_str(instance->device_id));
+ result = __FAILURE__;
+ }
+ else
+ {
+ result = __FAILURE__;
+ }
+
+ return result;
+}
+
+static STRING_HANDLE get_current_valid_device_key(AUTHENTICATION_INSTANCE* instance)
+{
+ STRING_HANDLE device_key;
+
+ switch (instance->current_credential_in_use)
+ {
+ case DEVICE_PRIMARY_KEY:
+ device_key = instance->device_primary_key;
+ break;
+ case DEVICE_SECONDARY_KEY:
+ device_key = instance->device_secondary_key;
+ break;
+ default:
+ device_key = NULL;
+ break;
+ }
+
+ return device_key;
+}
+
+static void on_cbs_put_token_complete_callback(void* context, CBS_OPERATION_RESULT operation_result, unsigned int status_code, const char* status_description)
+{
+#ifdef NO_LOGGING
+ UNUSED(status_code);
+ UNUSED(status_description);
+#endif
+ AUTHENTICATION_INSTANCE* instance = (AUTHENTICATION_INSTANCE*)context;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_095: [`instance->is_sas_token_refresh_in_progress` and `instance->is_cbs_put_token_in_progress` shall be set to FALSE]
+ instance->is_cbs_put_token_in_progress = false;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_091: [If `result` is CBS_OPERATION_RESULT_OK `instance->state` shall be set to AUTHENTICATION_STATE_STARTED and `instance->on_state_changed_callback` invoked]
+ if (operation_result == CBS_OPERATION_RESULT_OK)
+ {
+ update_state(instance, AUTHENTICATION_STATE_STARTED);
+ }
+ else
+ {
+ LogError("CBS reported status code %u, error: '%s' for put-token operation for device '%s'", status_code, status_description, STRING_c_str(instance->device_id));
+
+ if (mark_current_device_key_as_invalid(instance) != 0)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_092: [If `result` is not CBS_OPERATION_RESULT_OK `instance->state` shall be set to AUTHENTICATION_STATE_ERROR and `instance->on_state_changed_callback` invoked]
+ update_state(instance, AUTHENTICATION_STATE_ERROR);
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_094: [If `result` is not CBS_OPERATION_RESULT_OK and `instance->is_sas_token_refresh_in_progress` is TRUE, `instance->on_error_callback`shall be invoked with AUTHENTICATION_ERROR_SAS_REFRESH_FAILED]
+ if (instance->is_sas_token_refresh_in_progress)
+ {
+ notify_error(instance, AUTHENTICATION_ERROR_SAS_REFRESH_FAILED);
+ }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_093: [If `result` is not CBS_OPERATION_RESULT_OK and `instance->is_sas_token_refresh_in_progress` is FALSE, `instance->on_error_callback`shall be invoked with AUTHENTICATION_ERROR_AUTH_FAILED]
+ else
+ {
+ notify_error(instance, AUTHENTICATION_ERROR_AUTH_FAILED);
+ }
+ }
+ }
+
+ instance->is_sas_token_refresh_in_progress = false;
+}
+
+static int put_SAS_token_to_cbs(AUTHENTICATION_INSTANCE* instance, STRING_HANDLE cbs_audience, STRING_HANDLE sas_token)
+{
+ int result;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_043: [authentication_do_work() shall set `instance->is_cbs_put_token_in_progress` to TRUE]
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_057: [authentication_do_work() shall set `instance->is_cbs_put_token_in_progress` to TRUE]
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_075: [authentication_do_work() shall set `instance->is_cbs_put_token_in_progress` to TRUE]
+ instance->is_cbs_put_token_in_progress = true;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_046: [The SAS token provided shall be sent to CBS using cbs_put_token(), using `servicebus.windows.net:sastoken` as token type, `devices_path` as audience and passing on_cbs_put_token_complete_callback]
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_058: [The SAS token shall be sent to CBS using cbs_put_token(), using `servicebus.windows.net:sastoken` as token type, `devices_path` as audience and passing on_cbs_put_token_complete_callback]
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_076: [The SAS token shall be sent to CBS using cbs_put_token(), using `servicebus.windows.net:sastoken` as token type, `devices_path` as audience and passing on_cbs_put_token_complete_callback]
+ const char* sas_token_c_str = STRING_c_str(sas_token);
+ const char* cbs_audience_c_str = STRING_c_str(cbs_audience);
+ if (cbs_put_token(instance->cbs_handle, SAS_TOKEN_TYPE, cbs_audience_c_str, sas_token_c_str, on_cbs_put_token_complete_callback, instance) != RESULT_OK)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_048: [If cbs_put_token() failed, authentication_do_work() shall set `instance->is_cbs_put_token_in_progress` to FALSE, destroy `devices_path` and return]
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_060: [If cbs_put_token() fails, `instance->is_cbs_put_token_in_progress` shall be set to FALSE]
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_078: [If cbs_put_token() fails, `instance->is_cbs_put_token_in_progress` shall be set to FALSE]
+ instance->is_cbs_put_token_in_progress = false;
+ result = __FAILURE__;
+ LogError("Failed putting SAS token to CBS for device '%s' (cbs_put_token failed)", STRING_c_str(instance->device_id));
+ }
+ else
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_047: [If cbs_put_token() succeeds, authentication_do_work() shall set `instance->current_sas_token_put_time` with current time]
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_059: [If cbs_put_token() succeeds, authentication_do_work() shall set `instance->current_sas_token_put_time` with current time]
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_077: [If cbs_put_token() succeeds, authentication_do_work() shall set `instance->current_sas_token_put_time` with the current time]
+ time_t current_time;
+
+ if ((current_time = get_time(NULL)) == INDEFINITE_TIME)
+ {
+ LogError("Failed setting current_sas_token_put_time for device '%s' (get_time() failed)", STRING_c_str(instance->device_id));
+ }
+
+ instance->current_sas_token_put_time = current_time; // If it failed, fear not. `current_sas_token_put_time` shall be checked for INDEFINITE_TIME wherever it is used.
+
+ result = RESULT_OK;
+ }
+
+ return result;
+}
+
+static int create_and_put_SAS_token_to_cbs(AUTHENTICATION_INSTANCE* instance, STRING_HANDLE device_key)
+{
+ int result;
+ double seconds_since_epoch;
+
+ if (get_seconds_since_epoch(&seconds_since_epoch) != RESULT_OK)
+ {
+ result = __FAILURE__;
+ LogError("Failed creating a SAS token (get_seconds_since_epoch() failed)");
+ }
+ else
+ {
+ STRING_HANDLE devices_path = NULL;
+ STRING_HANDLE sasTokenKeyName = NULL;
+ STRING_HANDLE sas_token = NULL;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_052: [The SAS token expiration time shall be calculated adding `instance->sas_token_lifetime_secs` to the current number of seconds since epoch time UTC]
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_070: [The SAS token expiration time shall be calculated adding `instance->sas_token_lifetime_secs` to the current number of seconds since epoch time UTC]
+ size_t sas_token_expiration_time_secs = (size_t)seconds_since_epoch + instance->sas_token_lifetime_secs;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_053: [A STRING_HANDLE, referred to as `devices_path`, shall be created from the following parts: iothub_host_fqdn + "/devices/" + device_id]
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_071: [A STRING_HANDLE, referred to as `devices_path`, shall be created from the following parts: iothub_host_fqdn + "/devices/" + device_id]
+ if ((devices_path = create_devices_path(instance->iothub_host_fqdn, instance->device_id)) == NULL)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_054: [If `devices_path` failed to be created, authentication_do_work() shall fail and return]
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_072: [If `devices_path` failed to be created, authentication_do_work() shall fail and return]
+ result = __FAILURE__;
+ LogError("Failed creating a SAS token (create_devices_path() failed)");
+ }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_115: [An empty STRING_HANDLE, referred to as `sasTokenKeyName`, shall be created using STRING_new()]
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_117: [An empty STRING_HANDLE, referred to as `sasTokenKeyName`, shall be created using STRING_new()]
+ else if ((sasTokenKeyName = STRING_new()) == NULL)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_116: [If `sasTokenKeyName` failed to be created, authentication_do_work() shall fail and return]
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_118: [If `sasTokenKeyName` failed to be created, authentication_do_work() shall fail and return]
+ result = __FAILURE__;
+ LogError("Failed creating a SAS token (STRING_new() failed)");
+ }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_055: [The SAS token shall be created using SASToken_Create(), passing the selected device key, `device_path`, `sasTokenKeyName` and expiration time as arguments]
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_073: [The SAS token shall be created using SASToken_Create(), passing the selected device key, device_path, sasTokenKeyName and expiration time as arguments]
+ else if ((sas_token = SASToken_Create(device_key, devices_path, sasTokenKeyName, (size_t)sas_token_expiration_time_secs)) == NULL)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_056: [If SASToken_Create() fails, authentication_do_work() shall fail and return]
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_074: [If SASToken_Create() fails, authentication_do_work() shall fail and return]
+ result = __FAILURE__;
+ LogError("Failed creating a SAS token (SASToken_Create() failed)");
+ }
+ else if (put_SAS_token_to_cbs(instance, devices_path, sas_token) != RESULT_OK)
+ {
+ result = __FAILURE__;
+ LogError("Failed putting SAS token to CBS");
+ }
+ else
+ {
+ result = RESULT_OK;
+ }
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_081: [authentication_do_work() shall free the memory it allocated for `devices_path`, `sasTokenKeyName` and SAS token]
+ if (devices_path != NULL)
+ STRING_delete(devices_path);
+ if (sasTokenKeyName != NULL)
+ STRING_delete(sasTokenKeyName);
+ if (sas_token != NULL)
+ STRING_delete(sas_token);
+ }
+
+ return result;
+}
+
+
+// ---------- Set/Retrieve Options Helpers ----------//
+
+static void* authentication_clone_option(const char* name, const void* value)
+{
+ void* result;
+
+ if (name == NULL)
+ {
+ LogError("Failed to clone authentication option (name is NULL)");
+ result = NULL;
+ }
+ else if (value == NULL)
+ {
+ LogError("Failed to clone authentication option (value is NULL)");
+ result = NULL;
+ }
+ else
+ {
+ if (strcmp(AUTHENTICATION_OPTION_CBS_REQUEST_TIMEOUT_SECS, name) == 0 ||
+ strcmp(AUTHENTICATION_OPTION_SAS_TOKEN_REFRESH_TIME_SECS, name) == 0 ||
+ strcmp(AUTHENTICATION_OPTION_SAS_TOKEN_LIFETIME_SECS, name) == 0 ||
+ strcmp(AUTHENTICATION_OPTION_SAVED_OPTIONS, name) == 0)
+ {
+ result = (void*)value;
+ }
+ else
+ {
+ LogError("Failed to clone authentication option (option with name '%s' is not suppported)", name);
+ result = NULL;
+ }
+ }
+
+ return result;
+}
+
+static void authentication_destroy_option(const char* name, const void* value)
+{
+ if (name == NULL)
+ {
+ LogError("Failed to destroy authentication option (name is NULL)");
+ }
+ else if (value == NULL)
+ {
+ LogError("Failed to destroy authentication option (value is NULL)");
+ }
+ else
+ {
+ if (strcmp(name, AUTHENTICATION_OPTION_SAVED_OPTIONS) == 0)
+ {
+ OptionHandler_Destroy((OPTIONHANDLER_HANDLE)value);
+ }
+ }
+}
+
+
+// Public APIs:
+
+int authentication_start(AUTHENTICATION_HANDLE authentication_handle, const CBS_HANDLE cbs_handle)
+{
+ int result;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_025: [If authentication_handle is NULL, authentication_start() shall fail and return __FAILURE__ as error code]
+ if (authentication_handle == NULL)
+ {
+ result = __FAILURE__;
+ LogError("authentication_start failed (authentication_handle is NULL)");
+ }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_026: [If `cbs_handle` is NULL, authentication_start() shall fail and return __FAILURE__ as error code]
+ else if (cbs_handle == NULL)
+ {
+ result = __FAILURE__;
+ LogError("authentication_start failed (cbs_handle is NULL)");
+ }
+ else
+ {
+ AUTHENTICATION_INSTANCE* instance = (AUTHENTICATION_INSTANCE*)authentication_handle;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_027: [If authenticate state has been started already, authentication_start() shall fail and return __FAILURE__ as error code]
+ if (instance->state != AUTHENTICATION_STATE_STOPPED)
+ {
+ result = __FAILURE__;
+ LogError("authentication_start failed (messenger has already been started; current state: %d)", instance->state);
+ }
+ else
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_028: [authentication_start() shall save `cbs_handle` on `instance->cbs_handle`]
+ instance->cbs_handle = cbs_handle;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_029: [If no failures occur, `instance->state` shall be set to AUTHENTICATION_STATE_STARTING and `instance->on_state_changed_callback` invoked]
+ update_state(instance, AUTHENTICATION_STATE_STARTING);
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_030: [If no failures occur, authentication_start() shall return 0]
+ result = RESULT_OK;
+ }
+ }
+
+ return result;
+}
+
+int authentication_stop(AUTHENTICATION_HANDLE authentication_handle)
+{
+ int result;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_031: [If `authentication_handle` is NULL, authentication_stop() shall fail and return __FAILURE__]
+ if (authentication_handle == NULL)
+ {
+ result = __FAILURE__;
+ LogError("authentication_stop failed (authentication_handle is NULL)");
+ }
+ else
+ {
+ AUTHENTICATION_INSTANCE* instance = (AUTHENTICATION_INSTANCE*)authentication_handle;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_032: [If `instance->state` is AUTHENTICATION_STATE_STOPPED, authentication_stop() shall fail and return __FAILURE__]
+ if (instance->state == AUTHENTICATION_STATE_STOPPED)
+ {
+ result = __FAILURE__;
+ LogError("authentication_stop failed (messenger is already stopped)");
+ }
+ else
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_033: [`instance->cbs_handle` shall be set to NULL]
+ instance->cbs_handle = NULL;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_034: [`instance->state` shall be set to AUTHENTICATION_STATE_STOPPED and `instance->on_state_changed_callback` invoked]
+ update_state(instance, AUTHENTICATION_STATE_STOPPED);
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_035: [authentication_stop() shall return success code 0]
+ result = RESULT_OK;
+ }
+ }
+
+ return result;
+}
+
+void authentication_destroy(AUTHENTICATION_HANDLE authentication_handle)
+{
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_106: [If authentication_handle is NULL, authentication_destroy() shall return]
+ if (authentication_handle == NULL)
+ {
+ LogError("authentication_destroy failed (authentication_handle is NULL)");
+ }
+ else
+ {
+ AUTHENTICATION_INSTANCE* instance = (AUTHENTICATION_INSTANCE*)authentication_handle;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_107: [If `instance->state` is AUTHENTICATION_STATE_STARTING or AUTHENTICATION_STATE_STARTED, authentication_stop() shall be invoked and its result ignored]
+ if (instance->state != AUTHENTICATION_STATE_STOPPED)
+ {
+ (void)authentication_stop(authentication_handle);
+ }
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_108: [authentication_destroy() shall destroy `instance->device_id` using STRING_delete()]
+ if (instance->device_id != NULL)
+ STRING_delete(instance->device_id);
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_109: [authentication_destroy() shall destroy `instance->device_sas_token` using STRING_delete()]
+ if (instance->device_sas_token != NULL)
+ STRING_delete(instance->device_sas_token);
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_110: [authentication_destroy() shall destroy `instance->device_primary_key` using STRING_delete()]
+ if (instance->device_primary_key != NULL)
+ STRING_delete(instance->device_primary_key);
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_111: [authentication_destroy() shall destroy `instance->device_secondary_key` using STRING_delete()]
+ if (instance->device_secondary_key != NULL)
+ STRING_delete(instance->device_secondary_key);
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_112: [authentication_destroy() shall destroy `instance->iothub_host_fqdn` using STRING_delete()]
+ if (instance->iothub_host_fqdn != NULL)
+ STRING_delete(instance->iothub_host_fqdn);
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_113: [authentication_destroy() shall destroy `instance` using free()]
+ free(instance);
+ }
+}
+
+AUTHENTICATION_HANDLE authentication_create(const AUTHENTICATION_CONFIG* config)
+{
+ AUTHENTICATION_HANDLE result;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_001: [If parameter `config` or `config->device_id` are NULL, authentication_create() shall fail and return NULL.]
+ if (config == NULL)
+ {
+ result = NULL;
+ LogError("authentication_create failed (config is NULL)");
+ }
+ else if (config->device_id == NULL)
+ {
+ result = NULL;
+ LogError("authentication_create failed (config->device_id is NULL)");
+ }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_002: [If device keys and SAS token are NULL, authentication_create() shall fail and return NULL.]
+ else if (config->device_sas_token == NULL && config->device_primary_key == NULL)
+ {
+ result = NULL;
+ LogError("authentication_create failed (either config->device_sas_token or config->device_primary_key must be provided; config->device_secondary_key is optional)");
+ }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_003: [If device keys and SAS token are both provided, authentication_create() shall fail and return NULL.]
+ else if (config->device_sas_token != NULL && (config->device_primary_key != NULL || config->device_secondary_key != NULL))
+ {
+ result = NULL;
+ LogError("authentication_create failed (both config->device_sas_token and device device keys were provided; must provide only one)");
+ }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_004: [If `config->iothub_host_fqdn` is NULL, authentication_create() shall fail and return NULL.]
+ else if (config->iothub_host_fqdn == NULL)
+ {
+ result = NULL;
+ LogError("authentication_create failed (config->iothub_host_fqdn is NULL)");
+ }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_005: [If `config->on_state_changed_callback` is NULL, authentication_create() shall fail and return NULL]
+ else if (config->on_state_changed_callback == NULL)
+ {
+ result = NULL;
+ LogError("authentication_create failed (config->on_state_changed_callback is NULL)");
+ }
+ else
+ {
+ AUTHENTICATION_INSTANCE* instance;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_006: [authentication_create() shall allocate memory for a new authenticate state structure AUTHENTICATION_INSTANCE.]
+ if ((instance = (AUTHENTICATION_INSTANCE*)malloc(sizeof(AUTHENTICATION_INSTANCE))) == NULL)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_007: [If malloc() fails, authentication_create() shall fail and return NULL.]
+ result = NULL;
+ LogError("authentication_create failed (malloc failed)");
+ }
+ else
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_123: [authentication_create() shall initialize all fields of `instance` with 0 using memset().]
+ memset(instance, 0, sizeof(AUTHENTICATION_INSTANCE));
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_008: [authentication_create() shall save a copy of `config->device_id` into the `instance->device_id`]
+ if ((instance->device_id = STRING_construct(config->device_id)) == NULL)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_009: [If STRING_construct() fails, authentication_create() shall fail and return NULL]
+ result = NULL;
+ LogError("authentication_create failed (config->device_id could not be copied; STRING_construct failed)");
+ }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_010: [If `device_config->device_sas_token` is not NULL, authentication_create() shall save a copy into the `instance->device_sas_token`]
+ else if (config->device_sas_token != NULL && (instance->device_sas_token = STRING_construct(config->device_sas_token)) == NULL)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_011: [If STRING_construct() fails, authentication_create() shall fail and return NULL]
+ result = NULL;
+ LogError("authentication_create failed (config->device_sas_token could not be copied; STRING_construct failed)");
+ }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_012: [If provided, authentication_create() shall save a copy of `config->device_primary_key` into the `instance->device_primary_key`]
+ else if (config->device_primary_key != NULL && (instance->device_primary_key = STRING_construct(config->device_primary_key)) == NULL)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_013: [If STRING_construct() fails to copy `config->device_primary_key`, authentication_create() shall fail and return NULL]
+ result = NULL;
+ LogError("authentication_create failed (config->device_primary_key could not be copied; STRING_construct failed)");
+ }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_014: [If provided, authentication_create() shall save a copy of `config->device_secondary_key` into `instance->device_secondary_key`]
+ else if (config->device_secondary_key != NULL && (instance->device_secondary_key = STRING_construct(config->device_secondary_key)) == NULL)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_015: [If STRING_construct() fails to copy `config->device_secondary_key`, authentication_create() shall fail and return NULL]
+ result = NULL;
+ LogError("authentication_create failed (config->device_secondary_key could not be copied; STRING_construct failed)");
+ }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_016: [If provided, authentication_create() shall save a copy of `config->iothub_host_fqdn` into `instance->iothub_host_fqdn`]
+ else if ((instance->iothub_host_fqdn = STRING_construct(config->iothub_host_fqdn)) == NULL)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_017: [If STRING_clone() fails to copy `config->iothub_host_fqdn`, authentication_create() shall fail and return NULL]
+ result = NULL;
+ LogError("authentication_create failed (config->iothub_host_fqdn could not be copied; STRING_construct failed)");
+ }
+ else
+ {
+ instance->state = AUTHENTICATION_STATE_STOPPED;
+
+ if (instance->device_sas_token != NULL)
+ {
+ instance->current_credential_in_use = USER_PROVIDED_SAS_TOKEN;
+ }
+ else
+ {
+ instance->current_credential_in_use = DEVICE_PRIMARY_KEY;
+ }
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_018: [authentication_create() shall save `config->on_state_changed_callback` and `config->on_state_changed_callback_context` into `instance->on_state_changed_callback` and `instance->on_state_changed_callback_context`.]
+ instance->on_state_changed_callback = config->on_state_changed_callback;
+ instance->on_state_changed_callback_context = config->on_state_changed_callback_context;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_019: [authentication_create() shall save `config->on_error_callback` and `config->on_error_callback_context` into `instance->on_error_callback` and `instance->on_error_callback_context`.]
+ instance->on_error_callback = config->on_error_callback;
+ instance->on_error_callback_context = config->on_error_callback_context;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_021: [authentication_create() shall set `instance->cbs_request_timeout_secs` with the default value of UINT32_MAX]
+ instance->cbs_request_timeout_secs = DEFAULT_CBS_REQUEST_TIMEOUT_SECS;
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_022: [authentication_create() shall set `instance->sas_token_lifetime_secs` with the default value of one hour]
+ instance->sas_token_lifetime_secs = DEFAULT_SAS_TOKEN_LIFETIME_SECS;
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_023: [authentication_create() shall set `instance->sas_token_refresh_time_secs` with the default value of 30 minutes]
+ instance->sas_token_refresh_time_secs = DEFAULT_SAS_TOKEN_REFRESH_TIME_SECS;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_024: [If no failure occurs, authentication_create() shall return a reference to the AUTHENTICATION_INSTANCE handle]
+ result = (AUTHENTICATION_HANDLE)instance;
+ }
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_020: [If any failure occurs, authentication_create() shall free any memory it allocated previously]
+ if (result == NULL)
+ {
+ authentication_destroy((AUTHENTICATION_HANDLE)instance);
+ }
+ }
+ }
+
+ return result;
+}
+
+void authentication_do_work(AUTHENTICATION_HANDLE authentication_handle)
+{
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_036: [If authentication_handle is NULL, authentication_do_work() shall fail and return]
+ if (authentication_handle == NULL)
+ {
+ LogError("authentication_do_work failed (authentication_handle is NULL)");
+ }
+ else
+ {
+ AUTHENTICATION_INSTANCE* instance = (AUTHENTICATION_INSTANCE*)authentication_handle;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_038: [If `instance->is_cbs_put_token_in_progress` is TRUE, authentication_do_work() shall only verify the authentication timeout]
+ if (instance->is_cbs_put_token_in_progress)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_084: [If no timeout has occurred, authentication_do_work() shall return]
+
+ bool is_timed_out;
+ if (verify_cbs_put_token_timeout(instance, &is_timed_out) == RESULT_OK && is_timed_out)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_085: [`instance->is_cbs_put_token_in_progress` shall be set to FALSE]
+ instance->is_cbs_put_token_in_progress = false;
+
+ if (mark_current_device_key_as_invalid(instance))
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_086: [`instance->state` shall be updated to AUTHENTICATION_STATE_ERROR and `instance->on_state_changed_callback` invoked]
+ update_state(instance, AUTHENTICATION_STATE_ERROR);
+
+ if (instance->is_sas_token_refresh_in_progress)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_087: [If `instance->is_sas_token_refresh_in_progress` is TRUE, `instance->on_error_callback` shall be invoked with AUTHENTICATION_ERROR_SAS_REFRESH_TIMEOUT]
+ notify_error(instance, AUTHENTICATION_ERROR_SAS_REFRESH_TIMEOUT);
+ }
+ else
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_088: [If `instance->is_sas_token_refresh_in_progress` is FALSE, `instance->on_error_callback` shall be invoked with AUTHENTICATION_ERROR_AUTH_TIMEOUT]
+ notify_error(instance, AUTHENTICATION_ERROR_AUTH_TIMEOUT);
+ }
+ }
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_089: [`instance->is_sas_token_refresh_in_progress` shall be set to FALSE]
+ instance->is_sas_token_refresh_in_progress = false;
+ }
+ }
+ else if (instance->state == AUTHENTICATION_STATE_STARTED)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_040: [If `instance->state` is AUTHENTICATION_STATE_STARTED and user-provided SAS token was used, authentication_do_work() shall return]
+ if (are_device_keys_used_for_authentication(instance))
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_039: [If `instance->state` is AUTHENTICATION_STATE_STARTED and device keys were used, authentication_do_work() shall only verify the SAS token refresh time]
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_065: [The SAS token shall be refreshed if the current time minus `instance->current_sas_token_put_time` equals or exceeds `instance->sas_token_refresh_time_secs`]
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_066: [If SAS token does not need to be refreshed, authentication_do_work() shall return]
+ bool is_timed_out;
+ if (verify_sas_token_refresh_timeout(instance, &is_timed_out) == RESULT_OK && is_timed_out)
+ {
+ STRING_HANDLE device_key;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_119: [authentication_do_work() shall set `instance->is_sas_token_refresh_in_progress` to TRUE]
+ instance->is_sas_token_refresh_in_progress = true;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_067: [authentication_do_work() shall create a SAS token using `instance->device_primary_key`, unless it has failed previously]
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_069: [If using `instance->device_primary_key` has failed previously, a SAS token shall be created using `instance->device_secondary_key`]
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_068: [If using `instance->device_primary_key` has failed previously and `instance->device_secondary_key` is not provided, authentication_do_work() shall fail and return]
+ while ((device_key = get_current_valid_device_key(instance)) != NULL)
+ {
+ if (create_and_put_SAS_token_to_cbs(instance, device_key) == RESULT_OK)
+ {
+ break;
+ }
+ else
+ {
+ LogError("Failed refreshing SAS token (%d)", instance->current_credential_in_use);
+ (void)mark_current_device_key_as_invalid(instance);
+ }
+ }
+
+ if (!instance->is_cbs_put_token_in_progress)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_120: [If cbs_put_token() fails, `instance->is_sas_token_refresh_in_progress` shall be set to FALSE]
+ instance->is_sas_token_refresh_in_progress = false;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_079: [If cbs_put_token() fails, `instance->state` shall be updated to AUTHENTICATION_STATE_ERROR and `instance->on_state_changed_callback` invoked]
+ update_state(instance, AUTHENTICATION_STATE_ERROR);
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_080: [If cbs_put_token() fails, `instance->on_error_callback` shall be invoked with AUTHENTICATION_ERROR_SAS_REFRESH_FAILED]
+ notify_error(instance, AUTHENTICATION_ERROR_SAS_REFRESH_FAILED);
+ }
+ }
+ }
+ }
+ else if (instance->state == AUTHENTICATION_STATE_STARTING)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_041: [If `instance->device_sas_token` is provided, authentication_do_work() shall put it to CBS]
+ if (instance->device_sas_token != NULL)
+ {
+ STRING_HANDLE devices_path;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_044: [A STRING_HANDLE, referred to as `devices_path`, shall be created from the following parts: iothub_host_fqdn + "/devices/" + device_id]
+ if ((devices_path = create_devices_path(instance->iothub_host_fqdn, instance->device_id)) == NULL)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_045: [If `devices_path` failed to be created, authentication_do_work() shall set `instance->is_cbs_put_token_in_progress` to FALSE and return]
+ LogError("Failed authenticating using SAS token (create_devices_path() failed)");
+ }
+ else if (put_SAS_token_to_cbs(instance, devices_path, instance->device_sas_token) != RESULT_OK)
+ {
+ LogError("Failed authenticating using SAS token (put_SAS_token_to_cbs() failed)");
+ }
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_063: [authentication_do_work() shall free the memory it allocated for `devices_path`, `sasTokenKeyName` and SAS token]
+ STRING_delete(devices_path);
+ }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_042: [Otherwise, authentication_do_work() shall use device keys for CBS authentication]
+ else
+ {
+ STRING_HANDLE device_key;
+
+ while ((device_key = get_current_valid_device_key(instance)) != NULL)
+ {
+ if (create_and_put_SAS_token_to_cbs(instance, device_key) == RESULT_OK)
+ {
+ break;
+ }
+ else
+ {
+ LogError("Failed authenticating device '%s' using device keys (%d)", STRING_c_str(instance->device_id), instance->current_credential_in_use);
+ (void)mark_current_device_key_as_invalid(instance);
+ }
+ }
+ }
+
+ if (!instance->is_cbs_put_token_in_progress)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_061: [If cbs_put_token() fails, `instance->state` shall be updated to AUTHENTICATION_STATE_ERROR and `instance->on_state_changed_callback` invoked]
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_121: [If cbs_put_token() fails, `instance->state` shall be updated to AUTHENTICATION_STATE_ERROR and `instance->on_state_changed_callback` invoked]
+ update_state(instance, AUTHENTICATION_STATE_ERROR);
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_062: [If cbs_put_token() fails, `instance->on_error_callback` shall be invoked with AUTHENTICATION_ERROR_AUTH_FAILED]
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_122: [If cbs_put_token() fails, `instance->on_error_callback` shall be invoked with AUTHENTICATION_ERROR_AUTH_FAILED]
+ notify_error(instance, AUTHENTICATION_ERROR_AUTH_FAILED);
+ }
+ }
+ else
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_037: [If `instance->state` is not AUTHENTICATION_STATE_STARTING or AUTHENTICATION_STATE_STARTED, authentication_do_work() shall fail and return]
+ // Nothing to be done.
+ }
+ }
+}
+
+int authentication_set_option(AUTHENTICATION_HANDLE authentication_handle, const char* name, void* value)
+{
+ int result;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_097: [If `authentication_handle` or `name` or `value` is NULL, authentication_set_option shall fail and return a non-zero value]
+ if (authentication_handle == NULL || name == NULL || value == NULL)
+ {
+ LogError("authentication_set_option failed (one of the followin are NULL: authentication_handle=%p, name=%p, value=%p)",
+ authentication_handle, name, value);
+ result = __FAILURE__;
+ }
+ else
+ {
+ AUTHENTICATION_INSTANCE* instance = (AUTHENTICATION_INSTANCE*)authentication_handle;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_098: [If name matches AUTHENTICATION_OPTION_CBS_REQUEST_TIMEOUT_SECS, `value` shall be saved on `instance->cbs_request_timeout_secs`]
+ if (strcmp(AUTHENTICATION_OPTION_CBS_REQUEST_TIMEOUT_SECS, name) == 0)
+ {
+ instance->cbs_request_timeout_secs = *((size_t*)value);
+ result = RESULT_OK;
+ }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_124: [If name matches AUTHENTICATION_OPTION_SAS_TOKEN_REFRESH_TIME_SECS, `value` shall be saved on `instance->sas_token_refresh_time_secs`]
+ else if (strcmp(AUTHENTICATION_OPTION_SAS_TOKEN_REFRESH_TIME_SECS, name) == 0)
+ {
+ instance->sas_token_refresh_time_secs = *((size_t*)value);
+ result = RESULT_OK;
+ }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_125: [If name matches AUTHENTICATION_OPTION_SAS_TOKEN_LIFETIME_SECS, `value` shall be saved on `instance->sas_token_lifetime_secs`]
+ else if (strcmp(AUTHENTICATION_OPTION_SAS_TOKEN_LIFETIME_SECS, name) == 0)
+ {
+ instance->sas_token_lifetime_secs = *((size_t*)value);
+ result = RESULT_OK;
+ }
+ else if (strcmp(AUTHENTICATION_OPTION_SAVED_OPTIONS, name) == 0)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_098: [If name matches AUTHENTICATION_OPTION_SAVED_OPTIONS, `value` shall be applied using OptionHandler_FeedOptions]
+ if (OptionHandler_FeedOptions((OPTIONHANDLER_HANDLE)value, authentication_handle) != OPTIONHANDLER_OK)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_126: [If OptionHandler_FeedOptions fails, authentication_set_option shall fail and return a non-zero value]
+ LogError("authentication_set_option failed (OptionHandler_FeedOptions failed)");
+ result = __FAILURE__;
+ }
+ else
+ {
+ result = RESULT_OK;
+ }
+ }
+ else
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_128: [If name does not match any supported option, authentication_set_option shall fail and return a non-zero value]
+ LogError("authentication_set_option failed (option with name '%s' is not suppported)", name);
+ result = __FAILURE__;
+ }
+ }
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_099: [If no errors occur, authentication_set_option shall return 0]
+ return result;
+}
+
+OPTIONHANDLER_HANDLE authentication_retrieve_options(AUTHENTICATION_HANDLE authentication_handle)
+{
+ OPTIONHANDLER_HANDLE result;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_100: [If `authentication_handle` is NULL, authentication_retrieve_options shall fail and return NULL]
+ if (authentication_handle == NULL)
+ {
+ LogError("Failed to retrieve options from authentication instance (authentication_handle is NULL)");
+ result = NULL;
+ }
+ else
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_101: [An OPTIONHANDLER_HANDLE instance shall be created using OptionHandler_Create]
+ OPTIONHANDLER_HANDLE options = OptionHandler_Create(authentication_clone_option, authentication_destroy_option, (pfSetOption)authentication_set_option);
+
+ if (options == NULL)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_102: [If an OPTIONHANDLER_HANDLE instance fails to be created, authentication_retrieve_options shall fail and return NULL]
+ LogError("Failed to retrieve options from authentication instance (OptionHandler_Create failed)");
+ result = NULL;
+ }
+ else
+ {
+ AUTHENTICATION_INSTANCE* instance = (AUTHENTICATION_INSTANCE*)authentication_handle;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_103: [Each option of `instance` shall be added to the OPTIONHANDLER_HANDLE instance using OptionHandler_AddOption]
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_104: [If OptionHandler_AddOption fails, authentication_retrieve_options shall fail and return NULL]
+ if (OptionHandler_AddOption(options, AUTHENTICATION_OPTION_CBS_REQUEST_TIMEOUT_SECS, (void*)&instance->cbs_request_timeout_secs) != OPTIONHANDLER_OK)
+ {
+ LogError("Failed to retrieve options from authentication instance (OptionHandler_Create failed for option '%s')", AUTHENTICATION_OPTION_CBS_REQUEST_TIMEOUT_SECS);
+ result = NULL;
+ }
+ else if (OptionHandler_AddOption(options, AUTHENTICATION_OPTION_SAS_TOKEN_REFRESH_TIME_SECS, (void*)&instance->sas_token_refresh_time_secs) != OPTIONHANDLER_OK)
+ {
+ LogError("Failed to retrieve options from authentication instance (OptionHandler_Create failed for option '%s')", AUTHENTICATION_OPTION_SAS_TOKEN_REFRESH_TIME_SECS);
+ result = NULL;
+ }
+ else if (OptionHandler_AddOption(options, AUTHENTICATION_OPTION_SAS_TOKEN_LIFETIME_SECS, (void*)&instance->sas_token_lifetime_secs) != OPTIONHANDLER_OK)
+ {
+ LogError("Failed to retrieve options from authentication instance (OptionHandler_Create failed for option '%s')", AUTHENTICATION_OPTION_SAS_TOKEN_LIFETIME_SECS);
+ result = NULL;
+ }
+ else
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_127: [If no failures occur, authentication_retrieve_options shall return the OPTIONHANDLER_HANDLE instance]
+ result = options;
+ }
+
+ if (result == NULL)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_AUTH_09_105: [If authentication_retrieve_options fails, any allocated memory shall be freed]
+ OptionHandler_Destroy(options);
+ }
+ }
+ }
+
+ return result;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/iothubtransport_amqp_cbs_auth.h Fri Mar 10 11:46:55 2017 -0800
@@ -0,0 +1,71 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+#ifndef IOTHUBTRANSPORT_AMQP_CBS_AUTH_H
+#define IOTHUBTRANSPORT_AMQP_CBS_AUTH_H
+
+#include <stdint.h>
+#include "iothub_transport_ll.h"
+#include "azure_uamqp_c/cbs.h"
+#include "azure_c_shared_utility/umock_c_prod.h"
+#include "azure_c_shared_utility/optionhandler.h"
+
+static const char* AUTHENTICATION_OPTION_SAVED_OPTIONS = "saved_authentication_options";
+static const char* AUTHENTICATION_OPTION_CBS_REQUEST_TIMEOUT_SECS = "cbs_request_timeout_secs";
+static const char* AUTHENTICATION_OPTION_SAS_TOKEN_REFRESH_TIME_SECS = "sas_token_refresh_time_secs";
+static const char* AUTHENTICATION_OPTION_SAS_TOKEN_LIFETIME_SECS = "sas_token_lifetime_secs";
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef enum AUTHENTICATION_STATE_TAG
+ {
+ AUTHENTICATION_STATE_STOPPED,
+ AUTHENTICATION_STATE_STARTING,
+ AUTHENTICATION_STATE_STARTED,
+ AUTHENTICATION_STATE_ERROR
+ } AUTHENTICATION_STATE;
+
+ typedef enum AUTHENTICATION_ERROR_TAG
+ {
+ AUTHENTICATION_ERROR_AUTH_TIMEOUT,
+ AUTHENTICATION_ERROR_AUTH_FAILED,
+ AUTHENTICATION_ERROR_SAS_REFRESH_TIMEOUT,
+ AUTHENTICATION_ERROR_SAS_REFRESH_FAILED
+ } AUTHENTICATION_ERROR_CODE;
+
+ typedef void(*ON_AUTHENTICATION_STATE_CHANGED_CALLBACK)(void* context, AUTHENTICATION_STATE previous_state, AUTHENTICATION_STATE new_state);
+ typedef void(*ON_AUTHENTICATION_ERROR_CALLBACK)(void* context, AUTHENTICATION_ERROR_CODE error_code);
+
+ typedef struct AUTHENTICATION_CONFIG_TAG
+ {
+ char* device_id;
+ char* device_primary_key;
+ char* device_secondary_key;
+ char* device_sas_token;
+ char* iothub_host_fqdn;
+
+ ON_AUTHENTICATION_STATE_CHANGED_CALLBACK on_state_changed_callback;
+ void* on_state_changed_callback_context;
+
+ ON_AUTHENTICATION_ERROR_CALLBACK on_error_callback;
+ void* on_error_callback_context;
+ } AUTHENTICATION_CONFIG;
+
+ typedef struct AUTHENTICATION_INSTANCE* AUTHENTICATION_HANDLE;
+
+ MOCKABLE_FUNCTION(, AUTHENTICATION_HANDLE, authentication_create, const AUTHENTICATION_CONFIG*, config);
+ MOCKABLE_FUNCTION(, int, authentication_start, AUTHENTICATION_HANDLE, authentication_handle, const CBS_HANDLE, cbs_handle);
+ MOCKABLE_FUNCTION(, int, authentication_stop, AUTHENTICATION_HANDLE, authentication_handle);
+ MOCKABLE_FUNCTION(, void, authentication_do_work, AUTHENTICATION_HANDLE, authentication_handle);
+ MOCKABLE_FUNCTION(, void, authentication_destroy, AUTHENTICATION_HANDLE, authentication_handle);
+ MOCKABLE_FUNCTION(, int, authentication_set_option, AUTHENTICATION_HANDLE, authentication_handle, const char*, name, void*, value);
+ MOCKABLE_FUNCTION(, OPTIONHANDLER_HANDLE, authentication_retrieve_options, AUTHENTICATION_HANDLE, authentication_handle);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /*IOTHUBTRANSPORT_AMQP_CBS_AUTH_H*/
--- a/iothubtransport_amqp_common.c Fri Feb 24 14:00:00 2017 -0800
+++ b/iothubtransport_amqp_common.c Fri Mar 10 11:46:55 2017 -0800
@@ -9,438 +9,353 @@
#include "azure_c_shared_utility/agenttime.h"
#include "azure_c_shared_utility/gballoc.h"
#include "azure_c_shared_utility/crt_abstractions.h"
+#include "azure_c_shared_utility/singlylinkedlist.h"
#include "azure_c_shared_utility/doublylinkedlist.h"
#include "azure_c_shared_utility/xlogging.h"
#include "azure_c_shared_utility/platform.h"
#include "azure_c_shared_utility/strings.h"
#include "azure_c_shared_utility/urlencode.h"
#include "azure_c_shared_utility/tlsio.h"
-#include "azure_c_shared_utility/vector.h"
+#include "azure_c_shared_utility/optionhandler.h"
#include "azure_uamqp_c/cbs.h"
-#include "azure_uamqp_c/link.h"
+#include "azure_uamqp_c/session.h"
#include "azure_uamqp_c/message.h"
-#include "azure_uamqp_c/amqpvalue.h"
-#include "azure_uamqp_c/message_receiver.h"
-#include "azure_uamqp_c/message_sender.h"
#include "azure_uamqp_c/messaging.h"
-#include "azure_uamqp_c/sasl_mssbcbs.h"
-#include "azure_uamqp_c/saslclientio.h"
-#include "uamqp_messaging.h"
#include "iothub_client_ll.h"
#include "iothub_client_options.h"
#include "iothub_client_private.h"
-#include "iothubtransportamqp_auth.h"
#ifdef WIP_C2D_METHODS_AMQP /* This feature is WIP, do not use yet */
#include "iothubtransportamqp_methods.h"
#endif
#include "iothubtransport_amqp_common.h"
+#include "iothubtransport_amqp_connection.h"
+#include "iothubtransport_amqp_device.h"
#include "iothub_client_version.h"
-#define RESULT_OK 0
+#define RESULT_OK 0
+#define INDEFINITE_TIME ((time_t)(-1))
+#define DEFAULT_CBS_REQUEST_TIMEOUT_SECS 30
+#define DEFAULT_DEVICE_STATE_CHANGE_TIMEOUT_SECS 60
+#define DEFAULT_EVENT_SEND_TIMEOUT_SECS 300
+#define DEFAULT_SAS_TOKEN_LIFETIME_SECS 3600
+#define DEFAULT_SAS_TOKEN_REFRESH_TIME_SECS 1800
+#define MAX_NUMBER_OF_DEVICE_FAILURES 5
-#define INDEFINITE_TIME ((time_t)(-1))
-#define RFC1035_MAX_FQDN_LENGTH 255
-#define DEFAULT_SAS_TOKEN_LIFETIME_MS 3600000
-#define DEFAULT_CBS_REQUEST_TIMEOUT_MS 30000
-#define DEFAULT_CONTAINER_ID "default_container_id"
-#define DEFAULT_INCOMING_WINDOW_SIZE UINT_MAX
-#define DEFAULT_OUTGOING_WINDOW_SIZE 100
-#define MESSAGE_RECEIVER_LINK_NAME_TAG "receiver"
-#define MESSAGE_RECEIVER_TARGET_ADDRESS "target"
-#define MESSAGE_RECEIVER_MAX_LINK_SIZE 65536
-#define MESSAGE_SENDER_LINK_NAME_TAG "sender"
-#define MESSAGE_SENDER_SOURCE_NAME_TAG "source"
-#define MESSAGE_SENDER_MAX_LINK_SIZE UINT64_MAX
-typedef enum RESULT_TAG
-{
- RESULT_SUCCESS,
- RESULT_INVALID_ARGUMENT,
- RESULT_TIME_OUT,
- RESULT_RETRYABLE_ERROR,
- RESULT_CRITICAL_ERROR
-} RESULT;
+// ---------- Data Definitions ---------- //
-typedef struct AMQP_TRANSPORT_STATE_TAG
+typedef enum AMQP_TRANSPORT_AUTHENTICATION_MODE_TAG
{
- // FQDN of the IoT Hub.
- STRING_HANDLE iotHubHostFqdn;
-
- // TSL I/O transport.
- XIO_HANDLE tls_io;
- // Pointer to the function that creates the TLS I/O (internal use only).
- AMQP_GET_IO_TRANSPORT underlying_io_transport_provider;
- // AMQP connection.
- CONNECTION_HANDLE connection;
- // AMQP session.
- SESSION_HANDLE session;
- // All things CBS (and only CBS)
- AMQP_TRANSPORT_CBS_CONNECTION cbs_connection;
+ AMQP_TRANSPORT_AUTHENTICATION_MODE_NOT_SET,
+ AMQP_TRANSPORT_AUTHENTICATION_MODE_CBS,
+ AMQP_TRANSPORT_AUTHENTICATION_MODE_X509
+} AMQP_TRANSPORT_AUTHENTICATION_MODE;
- // Current AMQP connection state;
- AMQP_MANAGEMENT_STATE connection_state;
-
- AMQP_TRANSPORT_CREDENTIAL_TYPE preferred_credential_type;
- // List of registered devices.
- VECTOR_HANDLE registered_devices;
- // Turns logging on and off
- bool is_trace_on;
- // Used to generate unique AMQP link names
- int link_count;
-
- /*here are the options from the xio layer if any is saved*/
- OPTIONHANDLER_HANDLE xioOptions;
+typedef struct AMQP_TRANSPORT_INSTANCE_TAG
+{
+ STRING_HANDLE iothub_host_fqdn; // FQDN of the IoT Hub.
+ XIO_HANDLE tls_io; // TSL I/O transport.
+ AMQP_GET_IO_TRANSPORT underlying_io_transport_provider; // Pointer to the function that creates the TLS I/O (internal use only).
+ AMQP_CONNECTION_HANDLE amqp_connection; // Base amqp connection with service.
+ AMQP_CONNECTION_STATE amqp_connection_state; // Current state of the amqp_connection.
+ AMQP_TRANSPORT_AUTHENTICATION_MODE preferred_authentication_mode; // Used to avoid registered devices using different authentication modes.
+ SINGLYLINKEDLIST_HANDLE registered_devices; // List of devices currently registered in this transport.
+ bool is_trace_on; // Turns logging on and off.
+ OPTIONHANDLER_HANDLE saved_tls_options; // Here are the options from the xio layer if any is saved.
+ bool is_connection_retry_required; // Flag that controls whether the connection should be restablished or not.
+
+ size_t option_sas_token_lifetime_secs; // Device-specific option.
+ size_t option_sas_token_refresh_time_secs; // Device-specific option.
+ size_t option_cbs_request_timeout_secs; // Device-specific option.
+ size_t option_send_event_timeout_secs; // Device-specific option.
} AMQP_TRANSPORT_INSTANCE;
-typedef struct AMQP_TRANSPORT_DEVICE_STATE_TAG
+typedef struct AMQP_TRANSPORT_DEVICE_INSTANCE_TAG
{
- // Identity of the device.
- STRING_HANDLE deviceId;
- // contains the credentials to be used
- AUTHENTICATION_STATE_HANDLE authentication;
-
- // Address to which the transport will connect to and send events.
- STRING_HANDLE targetAddress;
- // Address to which the transport will connect to and receive messages from.
- STRING_HANDLE messageReceiveAddress;
- // Internal parameter that identifies the current logical device within the service.
- STRING_HANDLE devicesPath;
- // Saved reference to the IoTHub LL Client.
- IOTHUB_CLIENT_LL_HANDLE iothub_client_handle;
- // Saved reference to the transport the device is registered on.
- AMQP_TRANSPORT_INSTANCE* transport_state;
- // AMQP link used by the event sender.
- LINK_HANDLE sender_link;
- // uAMQP event sender.
- MESSAGE_SENDER_HANDLE message_sender;
- // State of the message sender.
- MESSAGE_SENDER_STATE message_sender_state;
- // Internal flag that controls if messages should be received or not.
- bool receive_messages;
- // AMQP link used by the message receiver.
- LINK_HANDLE receiver_link;
- // uAMQP message receiver.
- MESSAGE_RECEIVER_HANDLE message_receiver;
- // Message receiver state.
- MESSAGE_RECEIVER_STATE message_receiver_state;
- // List with events still pending to be sent. It is provided by the upper layer.
- PDLIST_ENTRY waitingToSend;
- // Internal list with the items currently being processed/sent through uAMQP.
- DLIST_ENTRY inProgress;
+ STRING_HANDLE device_id; // Identity of the device.
+ DEVICE_HANDLE device_handle; // Logic unit that performs authentication, messaging, etc.
+ IOTHUB_CLIENT_LL_HANDLE iothub_client_handle; // Saved reference to the IoTHub LL Client.
+ AMQP_TRANSPORT_INSTANCE* transport_instance; // Saved reference to the transport the device is registered on.
+ PDLIST_ENTRY waiting_to_send; // List of events waiting to be sent to the iot hub (i.e., haven't been processed by the transport yet).
+ DEVICE_STATE device_state; // Current state of the device_handle instance.
+ size_t number_of_previous_failures; // Number of times the device has failed in sequence; this value is reset to 0 if device succeeds to authenticate, send and/or recv messages.
+ size_t number_of_send_event_complete_failures; // Number of times on_event_send_complete was called in row with an error.
+ time_t time_of_last_state_change; // Time the device_handle last changed state; used to track timeouts of device_start_async and device_stop.
+ unsigned int max_state_change_timeout_secs; // Maximum number of seconds allowed for device_handle to complete start and stop state changes.
#ifdef WIP_C2D_METHODS_AMQP /* This feature is WIP, do not use yet */
// the methods portion
- IOTHUBTRANSPORT_AMQP_METHODS_HANDLE methods_handle;
+ IOTHUBTRANSPORT_AMQP_METHODS_HANDLE methods_handle; // Handle to instance of module that deals with device methods for AMQP.
// is subscription for methods needed?
- bool subscribe_methods_needed;
+ bool subscribe_methods_needed; // Indicates if should subscribe for device methods.
// is the transport subscribed for methods?
- bool subscribed_for_methods;
+ bool subscribed_for_methods; // Indicates if device is subscribed for device methods.
#endif
-} AMQP_TRANSPORT_DEVICE_STATE;
+} AMQP_TRANSPORT_DEVICE_INSTANCE;
+typedef struct MESSAGE_DISPOSITION_CONTEXT_TAG
+{
+ AMQP_TRANSPORT_DEVICE_INSTANCE* device_state;
+ char* link_name;
+ delivery_number message_id;
+} MESSAGE_DISPOSITION_CONTEXT;
-// Auxiliary functions
+// ---------- General Helpers ---------- //
-static STRING_HANDLE concat3Params(const char* prefix, const char* infix, const char* suffix)
+// @brief
+// Evaluates if the ammount of time since start_time is greater or lesser than timeout_in_secs.
+// @param is_timed_out
+// Set to true if a timeout has been reached, false otherwise. Not set if any failure occurs.
+// @returns
+// 0 if no failures occur, non-zero otherwise.
+static int is_timeout_reached(time_t start_time, unsigned int timeout_in_secs, bool *is_timed_out)
{
- STRING_HANDLE result = NULL;
- char* concat;
- size_t totalLength = strlen(prefix) + strlen(infix) + strlen(suffix) + 1; // One extra for \0.
+ int result;
+
+ if (start_time == INDEFINITE_TIME)
+ {
+ LogError("Failed to verify timeout (start_time is INDEFINITE)");
+ result = __FAILURE__;
+ }
+ else
+ {
+ time_t current_time;
- if ((concat = (char*)malloc(totalLength)) != NULL)
- {
- (void)strcpy(concat, prefix);
- (void)strcat(concat, infix);
- (void)strcat(concat, suffix);
- result = STRING_construct(concat);
- free(concat);
- }
- else
- {
- result = NULL;
- }
+ if ((current_time = get_time(NULL)) == INDEFINITE_TIME)
+ {
+ LogError("Failed to verify timeout (get_time failed)");
+ result = __FAILURE__;
+ }
+ else
+ {
+ if (get_difftime(current_time, start_time) >= timeout_in_secs)
+ {
+ *is_timed_out = 1;
+ }
+ else
+ {
+ *is_timed_out = 0;
+ }
- return result;
+ result = RESULT_OK;
+ }
+ }
+
+ return result;
}
-static int getSecondsSinceEpoch(size_t* seconds)
+static STRING_HANDLE get_target_iothub_fqdn(const IOTHUBTRANSPORT_CONFIG* config)
{
- int result;
- time_t current_time;
-
- if ((current_time = get_time(NULL)) == INDEFINITE_TIME)
- {
- LogError("Failed getting the current local time (get_time() failed)");
- result = __FAILURE__;
- }
- else
- {
- *seconds = (size_t)get_difftime(current_time, (time_t)0);
-
- result = RESULT_OK;
- }
-
- return result;
+ STRING_HANDLE fqdn;
+
+ if (config->upperConfig->protocolGatewayHostName == NULL)
+ {
+ if ((fqdn = STRING_construct_sprintf("%s.%s", config->upperConfig->iotHubName, config->upperConfig->iotHubSuffix)) == NULL)
+ {
+ LogError("Failed to copy iotHubName and iotHubSuffix (STRING_construct_sprintf failed)");
+ }
+ }
+ else if ((fqdn = STRING_construct(config->upperConfig->protocolGatewayHostName)) == NULL)
+ {
+ LogError("Failed to copy protocolGatewayHostName (STRING_construct failed)");
+ }
+
+ return fqdn;
}
-static STRING_HANDLE create_link_name(const char* deviceId, const char* tag, int index)
-{
- STRING_HANDLE name = NULL;
- char name_str[1024];
- if (sprintf(name_str, "link-%s-%s-%i", deviceId, tag, index) <= 0)
- {
- LogError("create_link_name failed (sprintf failed)");
- }
- else if ((name = STRING_construct(name_str)) == NULL)
- {
- LogError("create_link_name failed (STRING_construct failed)");
- }
-
- return name;
-}
-
-static STRING_HANDLE create_link_source_name(STRING_HANDLE link_name)
-{
- STRING_HANDLE name = NULL;
- char name_str[1024];
+// ---------- Register/Unregister Helpers ---------- //
- if (sprintf(name_str, "%s-source", STRING_c_str(link_name)) <= 0)
- {
- LogError("create_link_source_name failed (sprintf failed)");
- }
- else if ((name = STRING_construct(name_str)) == NULL)
- {
- LogError("create_link_source_name failed (STRING_construct failed)");
- }
-
- return name;
-}
-
-static STRING_HANDLE create_link_target_name(STRING_HANDLE link_name)
+static void internal_destroy_amqp_device_instance(AMQP_TRANSPORT_DEVICE_INSTANCE *trdev_inst)
{
- STRING_HANDLE name = NULL;
- char name_str[1024];
+#ifdef WIP_C2D_METHODS_AMQP /* This feature is WIP, do not use yet */
+ if (trdev_inst->methods_handle != NULL)
+ {
+ iothubtransportamqp_methods_destroy(trdev_inst->methods_handle);
+ }
+#endif
+ if (trdev_inst->device_handle != NULL)
+ {
+ device_destroy(trdev_inst->device_handle);
+ }
- if (sprintf(name_str, "%s-target", STRING_c_str(link_name)) <= 0)
- {
- LogError("create_link_target_name failed (sprintf failed)");
- }
- else if ((name = STRING_construct(name_str)) == NULL)
- {
- LogError("create_link_target_name failed (STRING_construct failed)");
- }
+ if (trdev_inst->device_id != NULL)
+ {
+ STRING_delete(trdev_inst->device_id);
+ }
- return name;
+ free(trdev_inst);
}
-// Auxiliary function to be used on VECTOR_find_if()
-static bool findDeviceById(const void* element, const void* value)
-{
- const AMQP_TRANSPORT_DEVICE_STATE* device_state = *(const AMQP_TRANSPORT_DEVICE_STATE **)element;
- const char* deviceId = (const char *)value;
-
- return (strcmp(STRING_c_str(device_state->deviceId), deviceId) == 0);
-}
-
-static void trackEventInProgress(IOTHUB_MESSAGE_LIST* message, AMQP_TRANSPORT_DEVICE_STATE* device_state)
-{
- DList_RemoveEntryList(&message->entry);
- DList_InsertTailList(&device_state->inProgress, &message->entry);
-}
-
-static IOTHUB_MESSAGE_LIST* getNextEventToSend(AMQP_TRANSPORT_DEVICE_STATE* device_state)
+// @brief
+// Saves the new state, if it is different than the previous one.
+static void on_device_state_changed_callback(void* context, DEVICE_STATE previous_state, DEVICE_STATE new_state)
{
- IOTHUB_MESSAGE_LIST* message;
-
- if (!DList_IsListEmpty(device_state->waitingToSend))
- {
- PDLIST_ENTRY list_entry = device_state->waitingToSend->Flink;
- message = containingRecord(list_entry, IOTHUB_MESSAGE_LIST, entry);
- }
- else
- {
- message = NULL;
- }
-
- return message;
-}
-
-static int isEventInInProgressList(IOTHUB_MESSAGE_LIST* message)
-{
- return !DList_IsListEmpty(&message->entry);
-}
-
-static void removeEventFromInProgressList(IOTHUB_MESSAGE_LIST* message)
-{
- DList_RemoveEntryList(&message->entry);
- DList_InitializeListHead(&message->entry);
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_061: [If `new_state` is the same as `previous_state`, on_device_state_changed_callback shall return]
+ if (context != NULL && new_state != previous_state)
+ {
+ AMQP_TRANSPORT_DEVICE_INSTANCE* registered_device = (AMQP_TRANSPORT_DEVICE_INSTANCE*)context;
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_062: [If `new_state` shall be saved into the `registered_device` instance]
+ registered_device->device_state = new_state;
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_063: [If `registered_device->time_of_last_state_change` shall be set using get_time()]
+ registered_device->time_of_last_state_change = get_time(NULL);
+ }
}
-static void rollEventBackToWaitList(IOTHUB_MESSAGE_LIST* message, AMQP_TRANSPORT_DEVICE_STATE* device_state)
-{
- removeEventFromInProgressList(message);
- DList_InsertTailList(device_state->waitingToSend, &message->entry);
-}
-
-static void rollEventsBackToWaitList(AMQP_TRANSPORT_DEVICE_STATE* device_state)
+// @brief Auxiliary function to be used to find a device in the registered_devices list.
+// @returns true if the device ids match, false otherwise.
+static bool find_device_by_id_callback(LIST_ITEM_HANDLE list_item, const void* match_context)
{
- PDLIST_ENTRY entry = device_state->inProgress.Blink;
+ bool result;
+
+ if (match_context == NULL)
+ {
+ result = false;
+ }
+ else
+ {
+ AMQP_TRANSPORT_DEVICE_INSTANCE* device_instance = (AMQP_TRANSPORT_DEVICE_INSTANCE*)singlylinkedlist_item_get_value(list_item);
- while (entry != &device_state->inProgress)
- {
- IOTHUB_MESSAGE_LIST* message = containingRecord(entry, IOTHUB_MESSAGE_LIST, entry);
- entry = entry->Blink;
- rollEventBackToWaitList(message, device_state);
- }
+ if (device_instance == NULL ||
+ device_instance->device_id == NULL ||
+ STRING_c_str(device_instance->device_id) != match_context)
+ {
+ result = false;
+ }
+ else
+ {
+ result = true;
+ }
+ }
+
+ return result;
}
-static void on_message_send_complete(void* context, MESSAGE_SEND_RESULT send_result)
+// @brief Verifies if a device is already registered within the transport that owns the list of registered devices.
+// @remarks Returns the correspoding LIST_ITEM_HANDLE in registered_devices, if found.
+// @returns true if the device is already in the list, false otherwise.
+static bool is_device_registered_ex(SINGLYLINKEDLIST_HANDLE registered_devices, const char* device_id, LIST_ITEM_HANDLE *list_item)
{
- IOTHUB_MESSAGE_LIST* message = (IOTHUB_MESSAGE_LIST*)context;
-
- IOTHUB_CLIENT_CONFIRMATION_RESULT iot_hub_send_result;
-
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_142: [The callback 'on_message_send_complete' shall pass to the upper layer callback an IOTHUB_CLIENT_CONFIRMATION_OK if the result received is MESSAGE_SEND_OK]
- if (send_result == MESSAGE_SEND_OK)
- {
- iot_hub_send_result = IOTHUB_CLIENT_CONFIRMATION_OK;
- }
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_143: [The callback 'on_message_send_complete' shall pass to the upper layer callback an IOTHUB_CLIENT_CONFIRMATION_ERROR if the result received is MESSAGE_SEND_ERROR]
- else
- {
- iot_hub_send_result = IOTHUB_CLIENT_CONFIRMATION_ERROR;
- }
+ return ((*list_item = singlylinkedlist_find(registered_devices, find_device_by_id_callback, device_id)) != NULL ? 1 : 0);
+}
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_102: [The callback 'on_message_send_complete' shall invoke the upper layer callback for message received if provided]
- if (message->callback != NULL)
- {
- message->callback(iot_hub_send_result, message->context);
- }
-
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_100: [The callback 'on_message_send_complete' shall remove the target message from the in-progress list after the upper layer callback]
- if (isEventInInProgressList(message))
- {
- removeEventFromInProgressList(message);
- }
-
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_151: [The callback 'on_message_send_complete' shall destroy the message handle (IOTHUB_MESSAGE_HANDLE) using IoTHubMessage_Destroy()]
- IoTHubMessage_Destroy(message->messageHandle);
-
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_152: [The callback 'on_message_send_complete' shall destroy the IOTHUB_MESSAGE_LIST instance]
- free(message);
+// @brief Verifies if a device is already registered within the transport that owns the list of registered devices.
+// @returns true if the device is already in the list, false otherwise.
+static bool is_device_registered(AMQP_TRANSPORT_DEVICE_INSTANCE* amqp_device_instance)
+{
+ LIST_ITEM_HANDLE list_item;
+ const char* device_id = STRING_c_str(amqp_device_instance->device_id);
+ return is_device_registered_ex(amqp_device_instance->transport_instance->registered_devices, device_id, &list_item);
}
-static AMQP_VALUE on_message_received(const void* context, MESSAGE_HANDLE message)
+
+// ---------- Callbacks ---------- //
+
+static MESSAGE_CALLBACK_INFO* MESSAGE_CALLBACK_INFO_Create(IOTHUB_MESSAGE_HANDLE message, DEVICE_MESSAGE_DISPOSITION_INFO* disposition_info, AMQP_TRANSPORT_DEVICE_INSTANCE* device_state)
{
- AMQP_VALUE result = NULL;
- int api_call_result;
- IOTHUB_MESSAGE_HANDLE iothub_message = NULL;
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_195: [The callback 'on_message_received' shall shall get a IOTHUB_MESSAGE_HANDLE instance out of the uamqp's MESSAGE_HANDLE instance by using IoTHubMessage_CreateFromUamqpMessage()]
- if ((api_call_result = IoTHubMessage_CreateFromUamqpMessage(message, &iothub_message)) != RESULT_OK)
- {
- LogError("Transport failed processing the message received (error = %d).", api_call_result);
+ MESSAGE_CALLBACK_INFO* result;
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_196: [If IoTHubMessage_CreateFromUamqpMessage fails, the callback 'on_message_received' shall reject the incoming message by calling messaging_delivery_rejected() and return.]
- result = messaging_delivery_rejected("Rejected due to failure reading AMQP message", "Failed reading AMQP message");
- }
- else
- {
- IOTHUBMESSAGE_DISPOSITION_RESULT disposition_result;
-
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_104: [The callback 'on_message_received' shall invoke IoTHubClient_LL_MessageCallback() passing the client and the incoming message handles as parameters]
- disposition_result = IoTHubClient_LL_MessageCallback((IOTHUB_CLIENT_LL_HANDLE)context, iothub_message);
+ if ((result = (MESSAGE_CALLBACK_INFO*)malloc(sizeof(MESSAGE_CALLBACK_INFO))) == NULL)
+ {
+ LogError("Failed creating MESSAGE_CALLBACK_INFO (malloc failed)");
+ }
+ else
+ {
+ MESSAGE_DISPOSITION_CONTEXT* tc;
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_197: [The callback 'on_message_received' shall destroy the IOTHUB_MESSAGE_HANDLE instance after invoking IoTHubClient_LL_MessageCallback().]
- IoTHubMessage_Destroy(iothub_message);
+ if ((tc = (MESSAGE_DISPOSITION_CONTEXT*)malloc(sizeof(MESSAGE_DISPOSITION_CONTEXT))) == NULL)
+ {
+ LogError("Failed creating MESSAGE_DISPOSITION_CONTEXT (malloc failed)");
+ free(result);
+ result = NULL;
+ }
+ else
+ {
+ if (mallocAndStrcpy_s(&(tc->link_name), disposition_info->source) == 0)
+ {
+ tc->device_state = device_state;
+ tc->message_id = disposition_info->message_id;
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_105: [The callback 'on_message_received' shall return the result of messaging_delivery_accepted() if the IoTHubClient_LL_MessageCallback() returns IOTHUBMESSAGE_ACCEPTED]
- if (disposition_result == IOTHUBMESSAGE_ACCEPTED)
- {
- result = messaging_delivery_accepted();
- }
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_106: [The callback 'on_message_received' shall return the result of messaging_delivery_released() if the IoTHubClient_LL_MessageCallback() returns IOTHUBMESSAGE_ABANDONED]
- else if (disposition_result == IOTHUBMESSAGE_ABANDONED)
- {
- result = messaging_delivery_released();
- }
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_107: [The callback 'on_message_received' shall return the result of messaging_delivery_rejected("Rejected by application", "Rejected by application") if the IoTHubClient_LL_MessageCallback() returns IOTHUBMESSAGE_REJECTED]
- else if (disposition_result == IOTHUBMESSAGE_REJECTED)
- {
- result = messaging_delivery_rejected("Rejected by application", "Rejected by application");
- }
- }
+ result->messageHandle = message;
+ result->transportContext = tc;
+ }
+ else
+ {
+ LogError("Failed creating MESSAGE_CALLBACK_INFO (mallocAndStrcyp_s failed)");
+ free(tc);
+ free(result);
+ result = NULL;
+ }
+ }
+ }
- return result;
+ return result;
+}
+
+static void MESSAGE_CALLBACK_INFO_Destroy(MESSAGE_CALLBACK_INFO* message_callback_info)
+{
+ free(message_callback_info->transportContext->link_name);
+ free(message_callback_info->transportContext);
+ free(message_callback_info);
}
-static void destroyConnection(AMQP_TRANSPORT_INSTANCE* transport_state)
+static DEVICE_MESSAGE_DISPOSITION_RESULT get_device_disposition_result_from(IOTHUBMESSAGE_DISPOSITION_RESULT iothubclient_disposition_result)
{
- if (transport_state->cbs_connection.cbs_handle != NULL)
- {
- cbs_destroy(transport_state->cbs_connection.cbs_handle);
- transport_state->cbs_connection.cbs_handle = NULL;
- }
-
- if (transport_state->session != NULL)
- {
- session_destroy(transport_state->session);
- transport_state->session = NULL;
- }
-
- if (transport_state->connection != NULL)
- {
- connection_destroy(transport_state->connection);
- transport_state->connection = NULL;
- }
+ DEVICE_MESSAGE_DISPOSITION_RESULT device_disposition_result;
- if (transport_state->cbs_connection.sasl_io != NULL)
- {
- xio_destroy(transport_state->cbs_connection.sasl_io);
- transport_state->cbs_connection.sasl_io = NULL;
- }
-
- if (transport_state->cbs_connection.sasl_mechanism != NULL)
- {
- saslmechanism_destroy(transport_state->cbs_connection.sasl_mechanism);
- transport_state->cbs_connection.sasl_mechanism = NULL;
- }
+ if (iothubclient_disposition_result == IOTHUBMESSAGE_ACCEPTED)
+ {
+ device_disposition_result = DEVICE_MESSAGE_DISPOSITION_RESULT_ACCEPTED;
+ }
+ else if (iothubclient_disposition_result == IOTHUBMESSAGE_ABANDONED)
+ {
+ device_disposition_result = DEVICE_MESSAGE_DISPOSITION_RESULT_RELEASED;
+ }
+ else if (iothubclient_disposition_result == IOTHUBMESSAGE_REJECTED)
+ {
+ device_disposition_result = DEVICE_MESSAGE_DISPOSITION_RESULT_REJECTED;
+ }
+ else
+ {
+ LogError("Failed getting corresponding DEVICE_MESSAGE_DISPOSITION_RESULT for IOTHUBMESSAGE_DISPOSITION_RESULT (%d is not supported)", iothubclient_disposition_result);
+ device_disposition_result = DEVICE_MESSAGE_DISPOSITION_RESULT_RELEASED;
+ }
- if (transport_state->tls_io != NULL)
- {
- /*before destroying, we shall save its options for later use*/
- transport_state->xioOptions = xio_retrieveoptions(transport_state->tls_io);
- if (transport_state->xioOptions == NULL)
- {
- LogError("unable to retrieve xio_retrieveoptions");
- }
-
- xio_destroy(transport_state->tls_io);
- transport_state->tls_io = NULL;
- }
+ return device_disposition_result;
}
-static void on_amqp_management_state_changed(void* context, AMQP_MANAGEMENT_STATE new_amqp_management_state, AMQP_MANAGEMENT_STATE previous_amqp_management_state)
+static DEVICE_MESSAGE_DISPOSITION_RESULT on_message_received(IOTHUB_MESSAGE_HANDLE message, DEVICE_MESSAGE_DISPOSITION_INFO* disposition_info, void* context)
{
- (void)previous_amqp_management_state;
- AMQP_TRANSPORT_INSTANCE* transport_state = (AMQP_TRANSPORT_INSTANCE*)context;
+ AMQP_TRANSPORT_DEVICE_INSTANCE* amqp_device_instance = (AMQP_TRANSPORT_DEVICE_INSTANCE*)context;
+ DEVICE_MESSAGE_DISPOSITION_RESULT device_disposition_result;
+ MESSAGE_CALLBACK_INFO* message_data;
- if (transport_state != NULL)
- {
- transport_state->connection_state = new_amqp_management_state;
- }
+ if ((message_data = MESSAGE_CALLBACK_INFO_Create(message, disposition_info, amqp_device_instance)) == NULL)
+ {
+ LogError("Failed processing message received (failed to assemble callback info)");
+ device_disposition_result = DEVICE_MESSAGE_DISPOSITION_RESULT_RELEASED;
+ }
+ else
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_089: [IoTHubClient_LL_MessageCallback() shall be invoked passing the client and the incoming message handles as parameters]
+ if (IoTHubClient_LL_MessageCallback(amqp_device_instance->iothub_client_handle, message_data) != true)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_090: [If IoTHubClient_LL_MessageCallback() fails, on_message_received_callback shall return DEVICE_MESSAGE_DISPOSITION_RESULT_RELEASED]
+ LogError("Failed processing message received (IoTHubClient_LL_MessageCallback failed)");
+ IoTHubMessage_Destroy(message);
+ device_disposition_result = DEVICE_MESSAGE_DISPOSITION_RESULT_RELEASED;
+ }
+ else
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_091: [If IoTHubClient_LL_MessageCallback() succeeds, on_message_received_callback shall return DEVICE_MESSAGE_DISPOSITION_RESULT_NONE]
+ device_disposition_result = DEVICE_MESSAGE_DISPOSITION_RESULT_NONE;
+ }
+ }
+
+ return device_disposition_result;
}
-static void on_connection_io_error(void* context)
-{
- AMQP_TRANSPORT_INSTANCE* transport_state = (AMQP_TRANSPORT_INSTANCE*)context;
-
- if (transport_state != NULL)
- {
- transport_state->connection_state = AMQP_MANAGEMENT_STATE_ERROR;
- }
-}
#ifdef WIP_C2D_METHODS_AMQP /* This feature is WIP, do not use yet */
static void on_methods_error(void* context)
@@ -452,14 +367,14 @@
static void on_methods_unsubscribed(void* context)
{
/* Codess_SRS_IOTHUBTRANSPORT_AMQP_METHODS_12_001: [ `on_methods_unsubscribed` calls iothubtransportamqp_methods_unsubscribe. ]*/
- AMQP_TRANSPORT_DEVICE_STATE* device_state = (AMQP_TRANSPORT_DEVICE_STATE*)context;
+ AMQP_TRANSPORT_DEVICE_INSTANCE* device_state = (AMQP_TRANSPORT_DEVICE_INSTANCE*)context;
IoTHubTransport_AMQP_Common_Unsubscribe_DeviceMethod(device_state);
}
static int on_method_request_received(void* context, const char* method_name, const unsigned char* request, size_t request_size, IOTHUBTRANSPORT_AMQP_METHOD_HANDLE method_handle)
{
int result;
- AMQP_TRANSPORT_DEVICE_STATE* device_state = (AMQP_TRANSPORT_DEVICE_STATE*)context;
+ AMQP_TRANSPORT_DEVICE_INSTANCE* device_state = (AMQP_TRANSPORT_DEVICE_INSTANCE*)context;
/* Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_01_017: [ `on_methods_request_received` shall call the `IoTHubClient_LL_DeviceMethodComplete` passing the method name, request buffer and size and the newly created BUFFER handle. ]*/
/* Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_01_022: [ The status code shall be the return value of the call to `IoTHubClient_LL_DeviceMethodComplete`. ]*/
@@ -475,7 +390,7 @@
return result;
}
-static int subscribe_methods(AMQP_TRANSPORT_DEVICE_STATE* deviceState)
+static int subscribe_methods(AMQP_TRANSPORT_DEVICE_INSTANCE* deviceState)
{
int result;
@@ -485,9 +400,16 @@
}
else
{
+ SESSION_HANDLE session_handle;
+
+ if ((amqp_connection_get_session_handle(deviceState->transport_instance->amqp_connection, &session_handle)) != RESULT_OK)
+ {
+ LogError("Device '%s' failed subscribing for methods (failed getting session handle)", STRING_c_str(deviceState->device_id));
+ result = __FAILURE__;
+ }
/* Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_01_024: [ If the device authentication status is AUTHENTICATION_STATUS_OK and `IoTHubTransport_AMQP_Common_Subscribe_DeviceMethod` was called to register for methods, `IoTHubTransport_AMQP_Common_DoWork` shall call `iothubtransportamqp_methods_subscribe`. ]*/
/* Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_01_027: [ The current session handle shall be passed to `iothubtransportamqp_methods_subscribe`. ]*/
- if (iothubtransportamqp_methods_subscribe(deviceState->methods_handle, deviceState->transport_state->session, on_methods_error, deviceState, on_method_request_received, deviceState, on_methods_unsubscribed, deviceState) != 0)
+ else if (iothubtransportamqp_methods_subscribe(deviceState->methods_handle, session_handle, on_methods_error, deviceState, on_method_request_received, deviceState, on_methods_unsubscribed, deviceState) != 0)
{
LogError("Cannot subscribe for methods");
result = __FAILURE__;
@@ -503,785 +425,777 @@
}
#endif
-static void set_session_options(SESSION_HANDLE session)
+
+// ---------- Underlying TLS I/O Helpers ---------- //
+
+// @brief
+// Retrieves the options of the current underlying TLS I/O instance and saves in the transport instance.
+// @remarks
+// This is used when the new underlying I/O transport (TLS I/O, or WebSockets, etc) needs to be recreated,
+// and the options previously set must persist.
+//
+// If no TLS I/O instance was created yet, results in failure.
+// @returns
+// 0 if succeeds, non-zero otherwise.
+static int save_underlying_io_transport_options(AMQP_TRANSPORT_INSTANCE* transport_instance)
{
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_065: [IoTHubTransport_AMQP_Common_DoWork shall apply a default value of UINT_MAX for the parameter 'AMQP incoming window']
- if (session_set_incoming_window(session, (uint32_t)DEFAULT_INCOMING_WINDOW_SIZE) != 0)
- {
- LogError("Failed to set the AMQP incoming window size.");
- }
+ int result;
+
+ if (transport_instance->tls_io == NULL)
+ {
+ LogError("failed saving underlying I/O transport options (tls_io instance is NULL)");
+ result = __FAILURE__;
+ }
+ else
+ {
+ OPTIONHANDLER_HANDLE fresh_options;
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_115: [IoTHubTransport_AMQP_Common_DoWork shall apply a default value of 100 for the parameter 'AMQP outgoing window']
- if (session_set_outgoing_window(session, DEFAULT_OUTGOING_WINDOW_SIZE) != 0)
- {
- LogError("Failed to set the AMQP outgoing window size.");
- }
+ if ((fresh_options = xio_retrieveoptions(transport_instance->tls_io)) == NULL)
+ {
+ LogError("failed saving underlying I/O transport options (tls_io instance is NULL)");
+ result = __FAILURE__;
+ }
+ else
+ {
+ OPTIONHANDLER_HANDLE previous_options = transport_instance->saved_tls_options;
+ transport_instance->saved_tls_options = fresh_options;
+
+ if (previous_options != NULL)
+ {
+ OptionHandler_Destroy(previous_options);
+ }
+
+ result = RESULT_OK;
+ }
+ }
+
+ return result;
+}
+
+static void destroy_underlying_io_transport_options(AMQP_TRANSPORT_INSTANCE* transport_instance)
+{
+ if (transport_instance->saved_tls_options != NULL)
+ {
+ OptionHandler_Destroy(transport_instance->saved_tls_options);
+ transport_instance->saved_tls_options = NULL;
+ }
}
-static int establishConnection(AMQP_TRANSPORT_INSTANCE* transport_state)
+// @brief
+// Applies TLS I/O options if previously saved to a new TLS I/O instance.
+// @returns
+// 0 if succeeds, non-zero otherwise.
+static int restore_underlying_io_transport_options(AMQP_TRANSPORT_INSTANCE* transport_instance, XIO_HANDLE xio_handle)
+{
+ int result;
+
+ if (transport_instance->saved_tls_options == NULL)
+ {
+ result = RESULT_OK;
+ }
+ else
+ {
+ if (OptionHandler_FeedOptions(transport_instance->saved_tls_options, xio_handle) != OPTIONHANDLER_OK)
+ {
+ LogError("Failed feeding existing options to new TLS instance.");
+ result = __FAILURE__;
+ }
+ else
+ {
+ result = RESULT_OK;
+ }
+ }
+
+ return result;
+}
+
+// @brief Destroys the XIO_HANDLE obtained with underlying_io_transport_provider(), saving its options beforehand.
+static void destroy_underlying_io_transport(AMQP_TRANSPORT_INSTANCE* transport_instance)
+{
+ if (transport_instance->tls_io != NULL)
+ {
+ xio_destroy(transport_instance->tls_io);
+ transport_instance->tls_io = NULL;
+ }
+}
+
+// @brief Invokes underlying_io_transport_provider() and retrieves a new XIO_HANDLE to use for I/O (TLS, or websockets, or w/e is supported).
+// @param xio_handle: if successfull, set with the new XIO_HANDLE acquired; not changed otherwise.
+// @returns 0 if successfull, non-zero otherwise.
+static int get_new_underlying_io_transport(AMQP_TRANSPORT_INSTANCE* transport_instance, XIO_HANDLE *xio_handle)
+{
+ int result;
+
+ if ((*xio_handle = transport_instance->underlying_io_transport_provider(STRING_c_str(transport_instance->iothub_host_fqdn))) == NULL)
+ {
+ LogError("Failed to obtain a TLS I/O transport layer (underlying_io_transport_provider() failed)");
+ result = __FAILURE__;
+ }
+ else
+ {
+ if (restore_underlying_io_transport_options(transport_instance, *xio_handle) != RESULT_OK)
+ {
+ /*pessimistically hope TLS will fail, be recreated and options re-given*/
+ LogError("Failed to apply options previous saved to new underlying I/O transport instance.");
+ }
+
+ result = RESULT_OK;
+ }
+
+ return result;
+}
+
+
+// ---------- AMQP connection establishment/tear-down, connectry retry ---------- //
+
+static void on_amqp_connection_state_changed(const void* context, AMQP_CONNECTION_STATE previous_state, AMQP_CONNECTION_STATE new_state)
+{
+ if (context != NULL && new_state != previous_state)
+ {
+ AMQP_TRANSPORT_INSTANCE* transport_instance = (AMQP_TRANSPORT_INSTANCE*)context;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_059: [`new_state` shall be saved in to the transport instance]
+ transport_instance->amqp_connection_state = new_state;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_060: [If `new_state` is AMQP_CONNECTION_STATE_ERROR, the connection shall be flagged as faulty (so the connection retry logic can be triggered)]
+ if (new_state == AMQP_CONNECTION_STATE_ERROR)
+ {
+ LogError("Transport received an ERROR from the amqp_connection (state changed %d->%d); it will be flagged for connection retry.", previous_state, new_state);
+
+ transport_instance->is_connection_retry_required = true;
+ }
+ }
+}
+
+static int establish_amqp_connection(AMQP_TRANSPORT_INSTANCE* transport_instance)
{
int result;
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_110: [IoTHubTransport_AMQP_Common_DoWork shall create the TLS IO using transport_state->io_transport_provider callback function]
- if (transport_state->tls_io == NULL &&
- (transport_state->tls_io = transport_state->underlying_io_transport_provider(STRING_c_str(transport_state->iotHubHostFqdn))) == NULL)
- {
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_136: [If transport_state->io_transport_provider_callback fails, IoTHubTransport_AMQP_Common_DoWork shall fail and return immediately]
- result = __FAILURE__;
- LogError("Failed to obtain a TLS I/O transport layer.");
- }
- else
- {
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_239: [IoTHubTransport_AMQP_Common_DoWork shall apply any TLS I/O saved options to the new TLS instance using OptionHandler_FeedOptions]
- if (transport_state->xioOptions != NULL)
- {
- if (OptionHandler_FeedOptions(transport_state->xioOptions, transport_state->tls_io) != 0)
- {
- LogError("unable to replay options to TLS"); /*pessimistically hope TLS will fail, be recreated and options re-given*/
- }
- else
- {
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_240: [If OptionHandler_FeedOptions succeeds, IoTHubTransport_AMQP_Common_DoWork shall destroy any TLS options saved on the transport state]
- OptionHandler_Destroy(transport_state->xioOptions);
- transport_state->xioOptions = NULL;
- }
- }
-
- switch (transport_state->preferred_credential_type)
- {
- case (DEVICE_KEY):
- case (DEVICE_SAS_TOKEN):
- {
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_056: [IoTHubTransport_AMQP_Common_DoWork shall create the SASL mechanism using AMQP's saslmechanism_create() API]
- if ((transport_state->cbs_connection.sasl_mechanism = saslmechanism_create(saslmssbcbs_get_interface(), NULL)) == NULL)
- {
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_057: [If saslmechanism_create() fails, IoTHubTransport_AMQP_Common_DoWork shall fail and return immediately]
- result = __FAILURE__;
- LogError("Failed to create a SASL mechanism.");
- }
- else
- {
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_060: [IoTHubTransport_AMQP_Common_DoWork shall create the SASL I / O layer using the xio_create() C Shared Utility API]
- SASLCLIENTIO_CONFIG sasl_client_config;
- sasl_client_config.sasl_mechanism = transport_state->cbs_connection.sasl_mechanism;
- sasl_client_config.underlying_io = transport_state->tls_io;
- if ((transport_state->cbs_connection.sasl_io = xio_create(saslclientio_get_interface_description(), &sasl_client_config)) == NULL)
- {
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_061: [If xio_create() fails creating the SASL I/O layer, IoTHubTransport_AMQP_Common_DoWork shall fail and return immediately]
- result = __FAILURE__;
- LogError("Failed to create a SASL I/O layer.");
- }
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_062: [IoTHubTransport_AMQP_Common_DoWork shall create the connection with the IoT service using connection_create2() AMQP API, passing the SASL I/O layer, IoT Hub FQDN and container ID as parameters (pass NULL for callbacks)]
- else if ((transport_state->connection = connection_create2(transport_state->cbs_connection.sasl_io, STRING_c_str(transport_state->iotHubHostFqdn), DEFAULT_CONTAINER_ID, NULL, NULL, NULL, NULL, on_connection_io_error, (void*)transport_state)) == NULL)
- {
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_063: [If connection_create2() fails, IoTHubTransport_AMQP_Common_DoWork shall fail and return immediately.]
- result = __FAILURE__;
- LogError("Failed to create the AMQP connection.");
- }
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_137: [IoTHubTransport_AMQP_Common_DoWork shall create the AMQP session session_create() AMQP API, passing the connection instance as parameter]
- else if ((transport_state->session = session_create(transport_state->connection, NULL, NULL)) == NULL)
- {
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_138 : [If session_create() fails, IoTHubTransport_AMQP_Common_DoWork shall fail and return immediately]
- result = __FAILURE__;
- LogError("Failed to create the AMQP session.");
- }
- else
- {
- set_session_options(transport_state->session);
+ if (transport_instance->preferred_authentication_mode == AMQP_TRANSPORT_AUTHENTICATION_MODE_NOT_SET)
+ {
+ LogError("Failed establishing connection (transport doesn't have a preferred authentication mode set; unexpected!).");
+ result = __FAILURE__;
+ }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_023: [If `instance->tls_io` is NULL, it shall be set invoking instance->underlying_io_transport_provider()]
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_025: [When `instance->tls_io` is created, it shall be set with `instance->saved_tls_options` using OptionHandler_FeedOptions()]
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_111: [If OptionHandler_FeedOptions() fails, it shall be ignored]
+ else if (transport_instance->tls_io == NULL &&
+ get_new_underlying_io_transport(transport_instance, &transport_instance->tls_io) != RESULT_OK)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_024: [If instance->underlying_io_transport_provider() fails, IoTHubTransport_AMQP_Common_DoWork shall fail and return]
+ LogError("Failed establishing connection (failed to obtain a TLS I/O transport layer).");
+ result = __FAILURE__;
+ }
+ else
+ {
+ AMQP_CONNECTION_CONFIG amqp_connection_config;
+ amqp_connection_config.iothub_host_fqdn = STRING_c_str(transport_instance->iothub_host_fqdn);
+ amqp_connection_config.underlying_io_transport = transport_instance->tls_io;
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_029: [`instance->is_trace_on` shall be set into `AMQP_CONNECTION_CONFIG->is_trace_on`]
+ amqp_connection_config.is_trace_on = transport_instance->is_trace_on;
+ amqp_connection_config.on_state_changed_callback = on_amqp_connection_state_changed;
+ amqp_connection_config.on_state_changed_context = transport_instance;
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_066: [IoTHubTransport_AMQP_Common_DoWork shall establish the CBS connection using the cbs_create() AMQP API]
- if ((transport_state->cbs_connection.cbs_handle = cbs_create(transport_state->session, on_amqp_management_state_changed, NULL)) == NULL)
- {
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_067: [If cbs_create() fails, IoTHubTransport_AMQP_Common_DoWork shall fail and return immediately]
- result = __FAILURE__;
- LogError("Failed to create the CBS connection.");
- }
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_139: [IoTHubTransport_AMQP_Common_DoWork shall open the CBS connection using the cbs_open() AMQP API]
- else if (cbs_open(transport_state->cbs_connection.cbs_handle) != 0)
- {
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_140: [If cbs_open() fails, IoTHubTransport_AMQP_Common_DoWork shall fail and return immediately]
- result = __FAILURE__;
- LogError("Failed to open the connection with CBS.");
- }
- else
- {
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_199: [The value of the option `logtrace` saved by the transport instance shall be applied to each new connection instance using connection_set_trace().]
- connection_set_trace(transport_state->connection, transport_state->is_trace_on);
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_200: [The value of the option `logtrace` saved by the transport instance shall be applied to each new SASL_IO instance using xio_setoption().]
- (void)xio_setoption(transport_state->cbs_connection.sasl_io, OPTION_LOG_TRACE, &transport_state->is_trace_on);
- result = RESULT_OK;
- }
- }
- }
- break;
- }
- case(X509):
- {
- /*Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_02_006: [ IoTHubTransport_AMQP_Common_DoWork shall not establish a CBS connection. ]*/
- /*Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_02_005: [ IoTHubTransport_AMQP_Common_DoWork shall create the connection with the IoT service using connection_create2() AMQP API, passing the TLS I/O layer, IoT Hub FQDN and container ID as parameters (pass NULL for callbacks) ]*/
- if ((transport_state->connection = connection_create2(transport_state->tls_io, STRING_c_str(transport_state->iotHubHostFqdn), DEFAULT_CONTAINER_ID, NULL, NULL, NULL, NULL, on_connection_io_error, (void*)transport_state)) == NULL)
- {
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_063: [If connection_create2() fails, IoTHubTransport_AMQP_Common_DoWork shall fail and return immediately.]
- result = __FAILURE__;
- LogError("Failed to create the AMQP connection.");
- }
- else
- {
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_137: [IoTHubTransport_AMQP_Common_DoWork shall create the AMQP session session_create() AMQP API, passing the connection instance as parameter]
- if ((transport_state->session = session_create(transport_state->connection, NULL, NULL)) == NULL)
- {
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_138 : [If session_create() fails, IoTHubTransport_AMQP_Common_DoWork shall fail and return immediately]
- result = __FAILURE__;
- LogError("Failed to create the AMQP session.");
- }
- else
- {
- set_session_options(transport_state->session);
-
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_199: [The value of the option `logtrace` saved by the transport instance shall be applied to each new connection instance using connection_set_trace().]
- connection_set_trace(transport_state->connection, transport_state->is_trace_on);
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_201: [The value of the option `logtrace` saved by the transport instance shall be applied to each new TLS_IO instance using xio_setoption().]
- (void)xio_setoption(transport_state->tls_io, OPTION_LOG_TRACE, &transport_state->is_trace_on);
- result = RESULT_OK;
- }
- }
- break;
- }
- default:
- {
- LogError("internal error: unexpected enum value for transport_state->credential.credentialType = %d", transport_state->preferred_credential_type);
- result = __FAILURE__;
- break;
- }
- }/*switch*/
- }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_027: [If `transport->preferred_authentication_method` is CBS, AMQP_CONNECTION_CONFIG shall be set with `create_sasl_io` = true and `create_cbs_connection` = true]
+ if (transport_instance->preferred_authentication_mode == AMQP_TRANSPORT_AUTHENTICATION_MODE_CBS)
+ {
+ amqp_connection_config.create_sasl_io = true;
+ amqp_connection_config.create_cbs_connection = true;
+ }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_028: [If `transport->preferred_credential_method` is X509, AMQP_CONNECTION_CONFIG shall be set with `create_sasl_io` = false and `create_cbs_connection` = false]
+ else if (transport_instance->preferred_authentication_mode == AMQP_TRANSPORT_AUTHENTICATION_MODE_X509)
+ {
+ amqp_connection_config.create_sasl_io = false;
+ amqp_connection_config.create_cbs_connection = false;
+ }
+ // If new AMQP_TRANSPORT_AUTHENTICATION_MODE values are added, they need to be covered here.
- if (result != RESULT_OK)
- {
- destroyConnection(transport_state);
- }
+ transport_instance->amqp_connection_state = AMQP_CONNECTION_STATE_CLOSED;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_026: [If `transport->connection` is NULL, it shall be created using amqp_connection_create()]
+ if ((transport_instance->amqp_connection = amqp_connection_create(&amqp_connection_config)) == NULL)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_030: [If amqp_connection_create() fails, IoTHubTransport_AMQP_Common_DoWork shall fail and return]
+ LogError("Failed establishing connection (failed to create the amqp_connection instance).");
+ result = __FAILURE__;
+ }
+ else
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_110: [If amqp_connection_create() succeeds, IoTHubTransport_AMQP_Common_DoWork shall proceed to invoke amqp_connection_do_work]
+ result = RESULT_OK;
+ }
+ }
return result;
}
-static void attachDeviceClientTypeToLink(LINK_HANDLE link)
+static void prepare_device_for_connection_retry(AMQP_TRANSPORT_DEVICE_INSTANCE* registered_device)
{
- fields attach_properties;
- AMQP_VALUE deviceClientTypeKeyName;
- AMQP_VALUE deviceClientTypeValue;
- int result;
-
- //
- // Attempt to add the device client type string to the attach properties.
- // If this doesn't happen, well, this isn't that important. We can operate
- // without this property. It's worth noting that even though we are going
- // on, the reasons any of these operations fail don't bode well for the
- // actual upcoming attach.
- //
-
- if ((attach_properties = amqpvalue_create_map()) == NULL)
- {
- LogError("Failed to create the map for device client type.");
- }
- else
- {
- if ((deviceClientTypeKeyName = amqpvalue_create_symbol("com.microsoft:client-version")) == NULL)
- {
- LogError("Failed to create the key name for the device client type.");
- }
- else
- {
- if ((deviceClientTypeValue = amqpvalue_create_string(CLIENT_DEVICE_TYPE_PREFIX CLIENT_DEVICE_BACKSLASH IOTHUB_SDK_VERSION)) == NULL)
- {
- LogError("Failed to create the key value for the device client type.");
- }
- else
- {
- if ((result = amqpvalue_set_map_value(attach_properties, deviceClientTypeKeyName, deviceClientTypeValue)) != 0)
- {
- LogError("Failed to set the property map for the device client type. Error code is: %d", result);
- }
- else if ((result = link_set_attach_properties(link, attach_properties)) != 0)
- {
- LogError("Unable to attach the device client type to the link properties. Error code is: %d", result);
- }
+#ifdef WIP_C2D_METHODS_AMQP /* This feature is WIP, do not use yet */
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_032: [ Each `instance->registered_devices` shall unsubscribe from receiving C2D method requests by calling `iothubtransportamqp_methods_unsubscribe`]
+ iothubtransportamqp_methods_unsubscribe(registered_device->methods_handle);
+ registered_device->subscribed_for_methods = 0;
+#endif
- amqpvalue_destroy(deviceClientTypeValue);
- }
-
- amqpvalue_destroy(deviceClientTypeKeyName);
- }
-
- amqpvalue_destroy(attach_properties);
- }
-}
-
-static void destroyEventSender(AMQP_TRANSPORT_DEVICE_STATE* device_state)
-{
- if (device_state->message_sender != NULL)
- {
- messagesender_destroy(device_state->message_sender);
- device_state->message_sender = NULL;
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_031: [device_stop() shall be invoked on all `instance->registered_devices` that are not already stopped]
+ if (registered_device->device_state != DEVICE_STATE_STOPPED)
+ {
+ if (device_stop(registered_device->device_handle) != RESULT_OK)
+ {
+ LogError("Failed preparing device '%s' for connection retry (device_stop failed)", STRING_c_str(registered_device->device_id));
+ }
+ }
- link_destroy(device_state->sender_link);
- device_state->sender_link = NULL;
- }
-}
-
-static void on_event_sender_state_changed(void* context, MESSAGE_SENDER_STATE new_state, MESSAGE_SENDER_STATE previous_state)
-{
- if (context != NULL)
- {
- AMQP_TRANSPORT_DEVICE_STATE* device_state = (AMQP_TRANSPORT_DEVICE_STATE*)context;
-
- if (device_state->transport_state->is_trace_on)
- {
- LogInfo("Event sender state changed [%s, %d->%d]", STRING_c_str(device_state->deviceId), previous_state, new_state);
- }
-
- device_state->message_sender_state = new_state;
-
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_192: [If a message sender instance changes its state to MESSAGE_SENDER_STATE_ERROR (first transition only) the connection retry logic shall be triggered]
- if (new_state != previous_state && new_state == MESSAGE_SENDER_STATE_ERROR)
- {
- device_state->transport_state->connection_state = AMQP_MANAGEMENT_STATE_ERROR;
- }
- }
+ registered_device->number_of_previous_failures = 0;
+ registered_device->number_of_send_event_complete_failures = 0;
}
-static int createEventSender(AMQP_TRANSPORT_DEVICE_STATE* device_state)
+static void prepare_for_connection_retry(AMQP_TRANSPORT_INSTANCE* transport_instance)
{
- int result;
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_034: [`instance->tls_io` options shall be saved on `instance->saved_tls_options` using xio_retrieveoptions()]
+ if (save_underlying_io_transport_options(transport_instance) != RESULT_OK)
+ {
+ LogError("Failed saving TLS I/O options while preparing for connection retry; failure will be ignored");
+ }
- STRING_HANDLE link_name = NULL;
- STRING_HANDLE source_name = NULL;
- AMQP_VALUE source = NULL;
- AMQP_VALUE target = NULL;
+ LIST_ITEM_HANDLE list_item = singlylinkedlist_get_head_item(transport_instance->registered_devices);
+
+ while (list_item != NULL)
+ {
+ AMQP_TRANSPORT_DEVICE_INSTANCE* registered_device = (AMQP_TRANSPORT_DEVICE_INSTANCE*)singlylinkedlist_item_get_value(list_item);
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_251: [Every new message_sender AMQP link shall be created using unique link and source names per device, per connection]
- if ((link_name = create_link_name(STRING_c_str(device_state->deviceId), MESSAGE_SENDER_LINK_NAME_TAG, device_state->transport_state->link_count++)) == NULL)
- {
- LogError("Failed creating a name for the AMQP message sender link.");
- result = __FAILURE__;
- }
- else if ((source_name = create_link_source_name(link_name)) == NULL)
- {
- LogError("Failed creating a name for the AMQP message sender source.");
- result = __FAILURE__;
- }
- else if ((source = messaging_create_source(STRING_c_str(source_name))) == NULL)
- {
- LogError("Failed creating AMQP messaging source attribute.");
- result = __FAILURE__;
- }
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_252: [The message_sender AMQP link shall be created using the `target` address created according to SRS_IOTHUBTRANSPORTAMQP_09_014]
- else if ((target = messaging_create_target(STRING_c_str(device_state->targetAddress))) == NULL)
- {
- LogError("Failed creating AMQP messaging target attribute.");
- result = __FAILURE__;
- }
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_068: [IoTHubTransport_AMQP_Common_DoWork shall create the AMQP link using link_create(), with role as 'role_sender']
- else if ((device_state->sender_link = link_create(device_state->transport_state->session, STRING_c_str(link_name), role_sender, source, target)) == NULL)
- {
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_069: [If IoTHubTransport_AMQP_Common_DoWork fails to create the AMQP link for sending messages, the function shall fail and return immediately, flagging the connection to be re-stablished]
- LogError("Failed creating AMQP link for message sender.");
- result = __FAILURE__;
- }
- else
- {
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_119: [IoTHubTransport_AMQP_Common_DoWork shall apply a default value of 65536 for the parameter 'Link MAX message size']
- if (link_set_max_message_size(device_state->sender_link, MESSAGE_SENDER_MAX_LINK_SIZE) != RESULT_OK)
- {
- LogError("Failed setting AMQP link max message size.");
- }
+ if (registered_device == NULL)
+ {
+ LogError("Failed preparing device for connection retry (singlylinkedlist_item_get_value failed)");
+ }
+ else
+ {
+ prepare_device_for_connection_retry(registered_device);
+ }
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_249: [The message sender link should have a property set with the type and version of the IoT Hub client application, set as `CLIENT_DEVICE_TYPE_PREFIX/IOTHUB_SDK_VERSION`]
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_250: [If the message sender link fails to have the client type and version set on its properties, the failure shall be ignored]
- attachDeviceClientTypeToLink(device_state->sender_link);
+ list_item = singlylinkedlist_get_next_item(list_item);
+ }
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_070: [IoTHubTransport_AMQP_Common_DoWork shall create the AMQP message sender using messagesender_create() AMQP API]
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_191: [IoTHubTransport_AMQP_Common_DoWork shall create each AMQP message sender tracking its state changes with a callback function]
- if ((device_state->message_sender = messagesender_create(device_state->sender_link, on_event_sender_state_changed, (void*)device_state)) == NULL)
- {
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_071: [IoTHubTransport_AMQP_Common_DoWork shall fail and return immediately if the AMQP message sender instance fails to be created, flagging the connection to be re-established]
- LogError("Could not allocate AMQP message sender");
- result = __FAILURE__;
- }
- else
- {
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_072: [IoTHubTransport_AMQP_Common_DoWork shall open the AMQP message sender using messagesender_open() AMQP API]
- if (messagesender_open(device_state->message_sender) != RESULT_OK)
- {
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_073: [IoTHubTransport_AMQP_Common_DoWork shall fail and return immediately if the AMQP message sender instance fails to be opened, flagging the connection to be re-established]
- LogError("Failed opening the AMQP message sender.");
- result = __FAILURE__;
- }
- else
- {
- result = RESULT_OK;
- }
- }
- }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_033: [`instance->connection` shall be destroyed using amqp_connection_destroy()]
+ amqp_connection_destroy(transport_instance->amqp_connection);
+ transport_instance->amqp_connection = NULL;
+ transport_instance->amqp_connection_state = AMQP_CONNECTION_STATE_CLOSED;
- if (link_name != NULL)
- STRING_delete(link_name);
- if (source_name != NULL)
- STRING_delete(source_name);
- if (source != NULL)
- amqpvalue_destroy(source);
- if (target != NULL)
- amqpvalue_destroy(target);
-
- return result;
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_035: [`instance->tls_io` shall be destroyed using xio_destroy()]
+ destroy_underlying_io_transport(transport_instance);
}
-static int destroyMessageReceiver(AMQP_TRANSPORT_DEVICE_STATE* device_state)
+
+// @brief Verifies if the crendentials used by the device match the requirements and authentication mode currently supported by the transport.
+// @returns true if credentials are good, false otherwise.
+static bool is_device_credential_acceptable(const IOTHUB_DEVICE_CONFIG* device_config, AMQP_TRANSPORT_AUTHENTICATION_MODE preferred_authentication_mode)
{
- int result;
+ bool result;
- if (device_state->message_receiver == NULL)
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_03_003: [IoTHubTransport_AMQP_Common_Register shall return NULL if both deviceKey and deviceSasToken are not NULL.]
+ if ((device_config->deviceSasToken != NULL) && (device_config->deviceKey != NULL))
+ {
+ LogError("Credential of device '%s' is not acceptable (must provide EITHER deviceSasToken OR deviceKey)", device_config->deviceId);
+ result = false;
+ }
+ else if (preferred_authentication_mode == AMQP_TRANSPORT_AUTHENTICATION_MODE_NOT_SET)
{
- result = RESULT_OK;
+ result = true;
+ }
+ else if (preferred_authentication_mode == AMQP_TRANSPORT_AUTHENTICATION_MODE_X509 && (device_config->deviceKey != NULL || device_config->deviceSasToken != NULL))
+ {
+ LogError("Credential of device '%s' is not acceptable (transport is using X509 certificate authentication, but device config contains deviceKey or sasToken)", device_config->deviceId);
+ result = false;
+ }
+ else if (preferred_authentication_mode != AMQP_TRANSPORT_AUTHENTICATION_MODE_X509 && (device_config->deviceKey == NULL && device_config->deviceSasToken == NULL))
+ {
+ LogError("Credential of device '%s' is not acceptable (transport is using CBS authentication, but device config does not contain deviceKey nor sasToken)", device_config->deviceId);
+ result = false;
}
else
{
- if (messagereceiver_close(device_state->message_receiver) != RESULT_OK)
- {
- LogError("Failed closing the AMQP message receiver.");
- result = __FAILURE__;
- }
- else
- {
- messagereceiver_destroy(device_state->message_receiver);
-
- device_state->message_receiver = NULL;
-
- link_destroy(device_state->receiver_link);
-
- device_state->receiver_link = NULL;
-
- result = RESULT_OK;
- }
- }
-
- return result;
-}
-
-static void on_message_receiver_state_changed(const void* context, MESSAGE_RECEIVER_STATE new_state, MESSAGE_RECEIVER_STATE previous_state)
-{
- if (context != NULL)
- {
- AMQP_TRANSPORT_DEVICE_STATE* device_state = (AMQP_TRANSPORT_DEVICE_STATE*)context;
-
- if (device_state->transport_state->is_trace_on)
- {
- LogInfo("Message receiver state changed [%s; %d->%d]", STRING_c_str(device_state->deviceId), previous_state, new_state);
- }
-
- device_state->message_receiver_state = new_state;
-
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_190: [If a message_receiver instance changes its state to MESSAGE_RECEIVER_STATE_ERROR (first transition only) the connection retry logic shall be triggered]
- if (new_state != previous_state && new_state == MESSAGE_RECEIVER_STATE_ERROR)
- {
- device_state->transport_state->connection_state = AMQP_MANAGEMENT_STATE_ERROR;
- }
- }
-}
-
-static int createMessageReceiver(AMQP_TRANSPORT_DEVICE_STATE* device_state)
-{
- int result;
-
- STRING_HANDLE link_name = NULL;
- STRING_HANDLE target_name = NULL;
- AMQP_VALUE source = NULL;
- AMQP_VALUE target = NULL;
-
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_246: [Every new message_receiver AMQP link shall be created using unique link and target names per device, per connection]
- if ((link_name = create_link_name(STRING_c_str(device_state->deviceId), MESSAGE_RECEIVER_LINK_NAME_TAG, device_state->transport_state->link_count++)) == NULL)
- {
- LogError("Failed creating a name for the AMQP message receiver link.");
- result = __FAILURE__;
- }
- else if ((target_name = create_link_target_name(link_name)) == NULL)
- {
- LogError("Failed creating a name for the AMQP message receiver target.");
- result = __FAILURE__;
- }
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_253: [The message_receiver AMQP link shall be created using the `source` address created according to SRS_IOTHUBTRANSPORTAMQP_09_053]
- else if ((source = messaging_create_source(STRING_c_str(device_state->messageReceiveAddress))) == NULL)
- {
- LogError("Failed creating AMQP message receiver source attribute.");
- result = __FAILURE__;
- }
- else if ((target = messaging_create_target(STRING_c_str(target_name))) == NULL)
- {
- LogError("Failed creating AMQP message receiver target attribute.");
- result = __FAILURE__;
- }
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_074: [IoTHubTransport_AMQP_Common_DoWork shall create the AMQP link using link_create(), with role as 'role_receiver']
- else if ((device_state->receiver_link = link_create(device_state->transport_state->session, STRING_c_str(link_name), role_receiver, source, target)) == NULL)
- {
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_075: [If IoTHubTransport_AMQP_Common_DoWork fails to create the AMQP link for receiving messages, the function shall fail and return immediately, flagging the connection to be re-stablished]
- LogError("Failed creating AMQP link for message receiver.");
- result = __FAILURE__;
- }
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_076: [IoTHubTransport_AMQP_Common_DoWork shall set the receiver link settle mode as receiver_settle_mode_first]
- else if (link_set_rcv_settle_mode(device_state->receiver_link, receiver_settle_mode_first) != RESULT_OK)
- {
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_141: [If IoTHubTransport_AMQP_Common_DoWork fails to set the settle mode on the AMQP link for receiving messages, the function shall fail and return immediately, flagging the connection to be re-stablished]
- LogError("Failed setting AMQP link settle mode for message receiver.");
- result = __FAILURE__;
- }
- else
- {
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_119: [IoTHubTransport_AMQP_Common_DoWork shall apply a default value of 65536 for the parameter 'Link MAX message size']
- if (link_set_max_message_size(device_state->receiver_link, MESSAGE_RECEIVER_MAX_LINK_SIZE) != RESULT_OK)
- {
- LogError("Failed setting AMQP link max message size for message receiver.");
- }
-
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_247: [The message receiver link should have a property set with the type and version of the IoT Hub client application, set as `CLIENT_DEVICE_TYPE_PREFIX/IOTHUB_SDK_VERSION`]
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_248: [If the message receiver link fails to have the client type and version set on its properties, the failure shall be ignored]
- attachDeviceClientTypeToLink(device_state->receiver_link);
-
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_077: [IoTHubTransport_AMQP_Common_DoWork shall create the AMQP message receiver using messagereceiver_create() AMQP API]
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_189: [IoTHubTransport_AMQP_Common_DoWork shall create each AMQP message_receiver tracking its state changes with a callback function]
- if ((device_state->message_receiver = messagereceiver_create(device_state->receiver_link, on_message_receiver_state_changed, (void*)device_state)) == NULL)
- {
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_078: [IoTHubTransport_AMQP_Common_DoWork shall fail and return immediately if the AMQP message receiver instance fails to be created, flagging the connection to be re-established]
- LogError("Could not allocate AMQP message receiver.");
- result = __FAILURE__;
- }
- else
- {
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_079: [IoTHubTransport_AMQP_Common_DoWork shall open the AMQP message receiver using messagereceiver_open() AMQP API, passing a callback function for handling C2D incoming messages]
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_123: [IoTHubTransport_AMQP_Common_DoWork shall create each AMQP message_receiver passing the 'on_message_received' as the callback function]
- if (messagereceiver_open(device_state->message_receiver, on_message_received, (const void*)device_state->iothub_client_handle) != RESULT_OK)
- {
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_080: [IoTHubTransport_AMQP_Common_DoWork shall fail and return immediately if the AMQP message receiver instance fails to be opened, flagging the connection to be re-established]
- LogError("Failed opening the AMQP message receiver.");
- result = __FAILURE__;
- }
- else
- {
- result = RESULT_OK;
- }
- }
- }
-
- if (link_name != NULL)
- STRING_delete(link_name);
- if (target_name != NULL)
- STRING_delete(target_name);
- if (source != NULL)
- amqpvalue_destroy(source);
- if (target != NULL)
- amqpvalue_destroy(target);
-
- return result;
-}
-
-static int sendPendingEvents(AMQP_TRANSPORT_DEVICE_STATE* device_state)
-{
- int result = RESULT_OK;
- IOTHUB_MESSAGE_LIST* message;
-
- while ((message = getNextEventToSend(device_state)) != NULL)
- {
- result = __FAILURE__;
-
- MESSAGE_HANDLE amqp_message = NULL;
- bool is_message_error = false;
-
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_086: [IoTHubTransport_AMQP_Common_DoWork shall move queued events to an "in-progress" list right before processing them for sending]
- trackEventInProgress(message, device_state);
-
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_193: [IoTHubTransport_AMQP_Common_DoWork shall get a MESSAGE_HANDLE instance out of the event's IOTHUB_MESSAGE_HANDLE instance by using message_create_from_iothub_message().]
- if ((result = message_create_from_iothub_message(message->messageHandle, &amqp_message)) != RESULT_OK)
- {
- LogError("Failed creating AMQP message (error=%d).", result);
- result = __FAILURE__;
- is_message_error = true;
- }
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_097: [IoTHubTransport_AMQP_Common_DoWork shall pass the MESSAGE_HANDLE intance to uAMQP for sending (along with on_message_send_complete callback) using messagesender_send()]
- else if (messagesender_send(device_state->message_sender, amqp_message, on_message_send_complete, message) != RESULT_OK)
- {
- LogError("Failed sending the AMQP message.");
- result = __FAILURE__;
- }
- else
- {
- result = RESULT_OK;
- }
-
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_194: [IoTHubTransport_AMQP_Common_DoWork shall destroy the MESSAGE_HANDLE instance after messagesender_send() is invoked.]
- if (amqp_message != NULL)
- {
- // It can be destroyed because AMQP keeps a clone of the message.
- message_destroy(amqp_message);
- }
-
- if (result != RESULT_OK)
- {
- if (is_message_error)
- {
- on_message_send_complete(message, MESSAGE_SEND_ERROR);
- }
- else
- {
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_111: [If message_create_from_iothub_message() fails, IoTHubTransport_AMQP_Common_DoWork notify the failure, roll back the event to waitToSend list and return]
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_113: [If messagesender_send() fails, IoTHubTransport_AMQP_Common_DoWork notify the failure, roll back the event to waitToSend list and return]
- rollEventBackToWaitList(message, device_state);
- break;
- }
- }
- }
-
- return result;
-}
-
-static void prepareDeviceForConnectionRetry(AMQP_TRANSPORT_DEVICE_STATE* device_state)
-{
- if (authentication_reset(device_state->authentication) != RESULT_OK)
- {
- LogError("Failed resetting the authenticatication state of device %s", STRING_c_str(device_state->deviceId));
- }
-
-#ifdef WIP_C2D_METHODS_AMQP /* This feature is WIP, do not use yet */
- iothubtransportamqp_methods_unsubscribe(device_state->methods_handle);
- device_state->subscribed_for_methods = false;
-#endif
-
- destroyMessageReceiver(device_state);
- destroyEventSender(device_state);
- rollEventsBackToWaitList(device_state);
-}
-
-static void prepareForConnectionRetry(AMQP_TRANSPORT_INSTANCE* transport_state)
-{
- size_t number_of_registered_devices = VECTOR_size(transport_state->registered_devices);
-
- for (size_t i = 0; i < number_of_registered_devices; i++)
- {
- AMQP_TRANSPORT_DEVICE_STATE* device_state = *(AMQP_TRANSPORT_DEVICE_STATE**)VECTOR_element(transport_state->registered_devices, i);
-
- prepareDeviceForConnectionRetry(device_state);
- }
-
- destroyConnection(transport_state);
- transport_state->connection_state = AMQP_MANAGEMENT_STATE_IDLE;
-}
-
-static int is_credential_compatible(const IOTHUB_DEVICE_CONFIG* device_config, AMQP_TRANSPORT_CREDENTIAL_TYPE preferred_authentication_type)
-{
- int result;
-
- if (preferred_authentication_type == CREDENTIAL_NOT_BUILD)
- {
- result = RESULT_OK;
- }
- else if (preferred_authentication_type == X509 && (device_config->deviceKey != NULL || device_config->deviceSasToken != NULL))
- {
- LogError("Incompatible credentials: transport is using X509 certificate authentication, but device config contains deviceKey and/or sasToken");
- result = __FAILURE__;
- }
- else if (preferred_authentication_type != X509 && (device_config->deviceKey == NULL && device_config->deviceSasToken == NULL))
- {
- LogError("Incompatible credentials: transport is using CBS authentication, but device config does not contain deviceKey nor sasToken");
- result = __FAILURE__;
- }
- else
- {
- result = RESULT_OK;
+ result = true;
}
return result;
}
-// API functions
+
+//---------- DoWork Helpers ----------//
+
+static IOTHUB_MESSAGE_LIST* get_next_event_to_send(AMQP_TRANSPORT_DEVICE_INSTANCE* registered_device)
+{
+ IOTHUB_MESSAGE_LIST* message;
+
+ if (!DList_IsListEmpty(registered_device->waiting_to_send))
+ {
+ PDLIST_ENTRY list_entry = registered_device->waiting_to_send->Flink;
+ message = containingRecord(list_entry, IOTHUB_MESSAGE_LIST, entry);
+ (void)DList_RemoveEntryList(list_entry);
+ }
+ else
+ {
+ message = NULL;
+ }
+
+ return message;
+}
+
+// @brief "Parses" the D2C_EVENT_SEND_RESULT (from iothubtransport_amqp_device module) into a IOTHUB_CLIENT_CONFIRMATION_RESULT.
+static IOTHUB_CLIENT_CONFIRMATION_RESULT get_iothub_client_confirmation_result_from(D2C_EVENT_SEND_RESULT result)
+{
+ IOTHUB_CLIENT_CONFIRMATION_RESULT iothub_send_result;
+
+ switch (result)
+ {
+ case D2C_EVENT_SEND_COMPLETE_RESULT_OK:
+ iothub_send_result = IOTHUB_CLIENT_CONFIRMATION_OK;
+ break;
+ case D2C_EVENT_SEND_COMPLETE_RESULT_ERROR_CANNOT_PARSE:
+ case D2C_EVENT_SEND_COMPLETE_RESULT_ERROR_FAIL_SENDING:
+ iothub_send_result = IOTHUB_CLIENT_CONFIRMATION_ERROR;
+ break;
+ case D2C_EVENT_SEND_COMPLETE_RESULT_ERROR_TIMEOUT:
+ iothub_send_result = IOTHUB_CLIENT_CONFIRMATION_MESSAGE_TIMEOUT;
+ break;
+ case D2C_EVENT_SEND_COMPLETE_RESULT_DEVICE_DESTROYED:
+ iothub_send_result = IOTHUB_CLIENT_CONFIRMATION_BECAUSE_DESTROY;
+ break;
+ case D2C_EVENT_SEND_COMPLETE_RESULT_ERROR_UNKNOWN:
+ default:
+ iothub_send_result = IOTHUB_CLIENT_CONFIRMATION_ERROR;
+ break;
+ }
+
+ return iothub_send_result;
+}
+
+// @brief
+// Callback function for device_send_event_async.
+static void on_event_send_complete(IOTHUB_MESSAGE_LIST* message, D2C_EVENT_SEND_RESULT result, void* context)
+{
+ AMQP_TRANSPORT_DEVICE_INSTANCE* registered_device = (AMQP_TRANSPORT_DEVICE_INSTANCE*)context;
+
+ if (result != D2C_EVENT_SEND_COMPLETE_RESULT_OK && result != D2C_EVENT_SEND_COMPLETE_RESULT_DEVICE_DESTROYED)
+ {
+ registered_device->number_of_send_event_complete_failures++;
+ }
+ else
+ {
+ registered_device->number_of_send_event_complete_failures = 0;
+ }
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_056: [If `message->callback` is not NULL, it shall invoked with the `iothub_send_result`]
+ if (message->callback != NULL)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_050: [If result is D2C_EVENT_SEND_COMPLETE_RESULT_OK, `iothub_send_result` shall be set using IOTHUB_CLIENT_CONFIRMATION_OK]
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_051: [If result is D2C_EVENT_SEND_COMPLETE_RESULT_ERROR_CANNOT_PARSE, `iothub_send_result` shall be set using IOTHUB_CLIENT_CONFIRMATION_ERROR]
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_052: [If result is D2C_EVENT_SEND_COMPLETE_RESULT_ERROR_FAIL_SENDING, `iothub_send_result` shall be set using IOTHUB_CLIENT_CONFIRMATION_ERROR]
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_053: [If result is D2C_EVENT_SEND_COMPLETE_RESULT_ERROR_TIMEOUT, `iothub_send_result` shall be set using IOTHUB_CLIENT_CONFIRMATION_MESSAGE_TIMEOUT]
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_054: [If result is D2C_EVENT_SEND_COMPLETE_RESULT_DEVICE_DESTROYED, `iothub_send_result` shall be set using IOTHUB_CLIENT_CONFIRMATION_BECAUSE_DESTROY]
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_055: [If result is D2C_EVENT_SEND_COMPLETE_RESULT_ERROR_UNKNOWN, `iothub_send_result` shall be set using IOTHUB_CLIENT_CONFIRMATION_ERROR]
+ IOTHUB_CLIENT_CONFIRMATION_RESULT iothub_send_result = get_iothub_client_confirmation_result_from(result);
+
+ message->callback(iothub_send_result, message->context);
+ }
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_057: [`message->messageHandle` shall be destroyed using IoTHubMessage_Destroy]
+ IoTHubMessage_Destroy(message->messageHandle);
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_058: [`message` shall be destroyed using free]
+ free(message);
+}
+
+// @brief
+// Gets events from wait to send list and sends to service in the order they were added.
+// @returns
+// 0 if all events could be sent to the next layer successfully, non-zero otherwise.
+static int send_pending_events(AMQP_TRANSPORT_DEVICE_INSTANCE* device_state)
+{
+ int result;
+ IOTHUB_MESSAGE_LIST* message;
+
+ result = RESULT_OK;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_047: [If the registered device is started, each event on `registered_device->wait_to_send_list` shall be removed from the list and sent using device_send_event_async()]
+ while ((message = get_next_event_to_send(device_state)) != NULL)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_048: [device_send_event_async() shall be invoked passing `on_event_send_complete`]
+ if (device_send_event_async(device_state->device_handle, message, on_event_send_complete, device_state) != RESULT_OK)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_049: [If device_send_event_async() fails, `on_event_send_complete` shall be invoked passing EVENT_SEND_COMPLETE_RESULT_ERROR_FAIL_SENDING and return]
+ LogError("Device '%s' failed to send message (device_send_event_async failed)", STRING_c_str(device_state->device_id));
+ result = __FAILURE__;
+
+ on_event_send_complete(message, D2C_EVENT_SEND_COMPLETE_RESULT_ERROR_FAIL_SENDING, device_state);
+ break;
+ }
+ }
+
+ return result;
+}
+
+// @brief
+// Auxiliary function for the public DoWork API, performing DoWork activities (authenticate, messaging) for a specific device.
+// @requires
+// The transport to have a valid instance of AMQP_CONNECTION (from which to obtain SESSION_HANDLE and CBS_HANDLE)
+// @returns
+// 0 if no errors occur, non-zero otherwise.
+static int IoTHubTransport_AMQP_Common_Device_DoWork(AMQP_TRANSPORT_DEVICE_INSTANCE* registered_device)
+{
+ int result;
+
+ if (registered_device->device_state != DEVICE_STATE_STARTED)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_036: [If the device state is DEVICE_STATE_STOPPED, it shall be started]
+ if (registered_device->device_state == DEVICE_STATE_STOPPED)
+ {
+ SESSION_HANDLE session_handle;
+ CBS_HANDLE cbs_handle = NULL;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_039: [amqp_connection_get_session_handle() shall be invoked on `instance->connection`]
+ if (amqp_connection_get_session_handle(registered_device->transport_instance->amqp_connection, &session_handle) != RESULT_OK)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_040: [If amqp_connection_get_session_handle() fails, IoTHubTransport_AMQP_Common_DoWork shall fail and return]
+ LogError("Failed performing DoWork for device '%s' (failed to get the amqp_connection session_handle)", STRING_c_str(registered_device->device_id));
+ result = __FAILURE__;
+ }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_037: [If transport is using CBS authentication, amqp_connection_get_cbs_handle() shall be invoked on `instance->connection`]
+ else if (registered_device->transport_instance->preferred_authentication_mode == AMQP_TRANSPORT_AUTHENTICATION_MODE_CBS &&
+ amqp_connection_get_cbs_handle(registered_device->transport_instance->amqp_connection, &cbs_handle) != RESULT_OK)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_038: [If amqp_connection_get_cbs_handle() fails, IoTHubTransport_AMQP_Common_DoWork shall fail and return]
+ LogError("Failed performing DoWork for device '%s' (failed to get the amqp_connection cbs_handle)", STRING_c_str(registered_device->device_id));
+ result = __FAILURE__;
+ }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_041: [The device handle shall be started using device_start_async()]
+ else if (device_start_async(registered_device->device_handle, session_handle, cbs_handle) != RESULT_OK)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_042: [If device_start_async() fails, IoTHubTransport_AMQP_Common_DoWork shall fail and skip to the next registered device]
+ LogError("Failed performing DoWork for device '%s' (failed to start device)", STRING_c_str(registered_device->device_id));
+ result = __FAILURE__;
+ }
+ else
+ {
+ result = RESULT_OK;
+ }
+ }
+ else if (registered_device->device_state == DEVICE_STATE_STARTING ||
+ registered_device->device_state == DEVICE_STATE_STOPPING)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_043: [If the device handle is in state DEVICE_STATE_STARTING or DEVICE_STATE_STOPPING, it shall be checked for state change timeout]
+ bool is_timed_out;
+ if (is_timeout_reached(registered_device->time_of_last_state_change, registered_device->max_state_change_timeout_secs, &is_timed_out) != RESULT_OK)
+ {
+ LogError("Failed performing DoWork for device '%s' (failed tracking timeout of device %d state)", STRING_c_str(registered_device->device_id), registered_device->device_state);
+ registered_device->device_state = DEVICE_STATE_ERROR_AUTH; // if time could not be calculated, the worst must be assumed.
+ result = __FAILURE__;
+ }
+ else if (is_timed_out)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_044: [If the device times out in state DEVICE_STATE_STARTING or DEVICE_STATE_STOPPING, the registered device shall be marked with failure]
+ LogError("Failed performing DoWork for device '%s' (device failed to start or stop within expected timeout)", STRING_c_str(registered_device->device_id));
+ registered_device->device_state = DEVICE_STATE_ERROR_AUTH; // this will cause device to be stopped bellow on the next call to this function.
+ result = __FAILURE__;
+ }
+ else
+ {
+ result = RESULT_OK;
+ }
+ }
+ else // i.e., DEVICE_STATE_ERROR_AUTH || DEVICE_STATE_ERROR_AUTH_TIMEOUT || DEVICE_STATE_ERROR_MSG
+ {
+ LogError("Failed performing DoWork for device '%s' (device reported state %d; number of previous failures: %d)",
+ STRING_c_str(registered_device->device_id), registered_device->device_state, registered_device->number_of_previous_failures);
+
+ registered_device->number_of_previous_failures++;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_046: [If the device has failed for MAX_NUMBER_OF_DEVICE_FAILURES in a row, it shall trigger a connection retry on the transport]
+ if (registered_device->number_of_previous_failures >= MAX_NUMBER_OF_DEVICE_FAILURES)
+ {
+ result = __FAILURE__;
+ }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_045: [If the registered device has a failure, it shall be stopped using device_stop()]
+ else if (device_stop(registered_device->device_handle) != RESULT_OK)
+ {
+ LogError("Failed to stop reset device '%s' (device_stop failed)", STRING_c_str(registered_device->device_id));
+ result = __FAILURE__;
+ }
+ else
+ {
+ result = RESULT_OK;
+ }
+ }
+ }
+#ifdef WIP_C2D_METHODS_AMQP /* This feature is WIP, do not use yet */
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_01_031: [ Once the device is authenticated, `iothubtransportamqp_methods_subscribe` shall be invoked (subsequent DoWork calls shall not call it if already subscribed). ]
+ else if (registered_device->subscribe_methods_needed &&
+ !registered_device->subscribed_for_methods &&
+ subscribe_methods(registered_device) != RESULT_OK)
+ {
+ LogError("Failed performing DoWork for device '%s' (failed registering for device methods)", STRING_c_str(registered_device->device_id));
+ registered_device->number_of_previous_failures++;
+ result = __FAILURE__;
+ }
+#endif
+ else
+ {
+ if (send_pending_events(registered_device) != RESULT_OK)
+ {
+ LogError("Failed performing DoWork for device '%s' (failed sending pending events)", STRING_c_str(registered_device->device_id));
+ registered_device->number_of_previous_failures++;
+ result = __FAILURE__;
+ }
+ else
+ {
+ registered_device->number_of_previous_failures = 0;
+ result = RESULT_OK;
+ }
+ }
+
+ // No harm in invoking this as API will simply exit if the state is not "started".
+ device_do_work(registered_device->device_handle);
+
+ return result;
+}
+
+
+//---------- SetOption-ish Helpers ----------//
+
+// @brief
+// Gets all the device-specific options and replicates them into this new registered device.
+// @returns
+// 0 if the function succeeds, non-zero otherwise.
+static int replicate_device_options_to(AMQP_TRANSPORT_DEVICE_INSTANCE* dev_instance, DEVICE_AUTH_MODE auth_mode)
+{
+ int result;
+
+ if (device_set_option(
+ dev_instance->device_handle,
+ DEVICE_OPTION_EVENT_SEND_TIMEOUT_SECS,
+ &dev_instance->transport_instance->option_send_event_timeout_secs) != RESULT_OK)
+ {
+ LogError("Failed to apply option DEVICE_OPTION_EVENT_SEND_TIMEOUT_SECS to device '%s' (device_set_option failed)", STRING_c_str(dev_instance->device_id));
+ result = __FAILURE__;
+ }
+ else if (auth_mode == DEVICE_AUTH_MODE_CBS)
+ {
+ if (device_set_option(
+ dev_instance->device_handle,
+ DEVICE_OPTION_CBS_REQUEST_TIMEOUT_SECS,
+ &dev_instance->transport_instance->option_cbs_request_timeout_secs) != RESULT_OK)
+ {
+ LogError("Failed to apply option DEVICE_OPTION_CBS_REQUEST_TIMEOUT_SECS to device '%s' (device_set_option failed)", STRING_c_str(dev_instance->device_id));
+ result = __FAILURE__;
+ }
+ else if (device_set_option(
+ dev_instance->device_handle,
+ DEVICE_OPTION_SAS_TOKEN_LIFETIME_SECS,
+ &dev_instance->transport_instance->option_sas_token_lifetime_secs) != RESULT_OK)
+ {
+ LogError("Failed to apply option DEVICE_OPTION_SAS_TOKEN_LIFETIME_SECS to device '%s' (device_set_option failed)", STRING_c_str(dev_instance->device_id));
+ result = __FAILURE__;
+ }
+ else if (device_set_option(
+ dev_instance->device_handle,
+ DEVICE_OPTION_SAS_TOKEN_REFRESH_TIME_SECS,
+ &dev_instance->transport_instance->option_sas_token_refresh_time_secs) != RESULT_OK)
+ {
+ LogError("Failed to apply option DEVICE_OPTION_SAS_TOKEN_REFRESH_TIME_SECS to device '%s' (device_set_option failed)", STRING_c_str(dev_instance->device_id));
+ result = __FAILURE__;
+ }
+ else
+ {
+ result = RESULT_OK;
+ }
+ }
+ else
+ {
+ result = RESULT_OK;
+ }
+
+ return result;
+}
+
+// @brief
+// Translates from the option names supported by iothubtransport_amqp_common to the ones supported by iothubtransport_amqp_device.
+static const char* get_device_option_name_from(const char* iothubclient_option_name)
+{
+ const char* device_option_name;
+
+ if (strcmp(OPTION_SAS_TOKEN_LIFETIME, iothubclient_option_name) == 0)
+ {
+ device_option_name = DEVICE_OPTION_SAS_TOKEN_LIFETIME_SECS;
+ }
+ else if (strcmp(OPTION_SAS_TOKEN_REFRESH_TIME, iothubclient_option_name) == 0)
+ {
+ device_option_name = DEVICE_OPTION_SAS_TOKEN_REFRESH_TIME_SECS;
+ }
+ else if (strcmp(OPTION_CBS_REQUEST_TIMEOUT, iothubclient_option_name) == 0)
+ {
+ device_option_name = DEVICE_OPTION_CBS_REQUEST_TIMEOUT_SECS;
+ }
+ else if (strcmp(OPTION_EVENT_SEND_TIMEOUT_SECS, iothubclient_option_name) == 0)
+ {
+ device_option_name = DEVICE_OPTION_EVENT_SEND_TIMEOUT_SECS;
+ }
+ else
+ {
+ device_option_name = NULL;
+ }
+
+ return device_option_name;
+}
+
+// @brief
+// Auxiliary function invoked by IoTHubTransport_AMQP_Common_SetOption to set an option on every registered device.
+// @returns
+// 0 if it succeeds, non-zero otherwise.
+static int IoTHubTransport_AMQP_Common_Device_SetOption(TRANSPORT_LL_HANDLE handle, const char* option, void* value)
+{
+ int result;
+ const char* device_option;
+
+ if ((device_option = get_device_option_name_from(option)) == NULL)
+ {
+ LogError("failed setting option '%s' to registered device (could not match name to options supported by device)", option);
+ result = __FAILURE__;
+ }
+ else
+ {
+ AMQP_TRANSPORT_INSTANCE* instance = (AMQP_TRANSPORT_INSTANCE*)handle;
+ result = RESULT_OK;
+
+ LIST_ITEM_HANDLE list_item = singlylinkedlist_get_head_item(instance->registered_devices);
+
+ while (list_item != NULL)
+ {
+ AMQP_TRANSPORT_DEVICE_INSTANCE* registered_device;
+
+ if ((registered_device = (AMQP_TRANSPORT_DEVICE_INSTANCE*)singlylinkedlist_item_get_value(list_item)) == NULL)
+ {
+ LogError("failed setting option '%s' to registered device (singlylinkedlist_item_get_value failed)", option);
+ result = __FAILURE__;
+ break;
+ }
+ else if (device_set_option(registered_device->device_handle, device_option, value) != RESULT_OK)
+ {
+ LogError("failed setting option '%s' to registered device '%s' (device_set_option failed)",
+ option, STRING_c_str(registered_device->device_id));
+ result = __FAILURE__;
+ break;
+ }
+
+ list_item = singlylinkedlist_get_next_item(list_item);
+ }
+ }
+
+ return result;
+}
+
+static void internal_destroy_instance(AMQP_TRANSPORT_INSTANCE* instance)
+{
+ if (instance != NULL)
+ {
+ if (instance->registered_devices != NULL)
+ {
+ LIST_ITEM_HANDLE list_item = singlylinkedlist_get_head_item(instance->registered_devices);
+
+ while (list_item != NULL)
+ {
+ AMQP_TRANSPORT_DEVICE_INSTANCE* registered_device = (AMQP_TRANSPORT_DEVICE_INSTANCE*)singlylinkedlist_item_get_value(list_item);
+ list_item = singlylinkedlist_get_next_item(list_item);
+ IoTHubTransport_AMQP_Common_Unregister(registered_device);
+ }
+
+ singlylinkedlist_destroy(instance->registered_devices);
+ }
+
+ if (instance->amqp_connection != NULL)
+ {
+ amqp_connection_destroy(instance->amqp_connection);
+ }
+
+ destroy_underlying_io_transport(instance);
+ destroy_underlying_io_transport_options(instance);
+
+ STRING_delete(instance->iothub_host_fqdn);
+
+ free(instance);
+ }
+}
+
+
+// ---------- API functions ---------- //
TRANSPORT_LL_HANDLE IoTHubTransport_AMQP_Common_Create(const IOTHUBTRANSPORT_CONFIG* config, AMQP_GET_IO_TRANSPORT get_io_transport)
{
- AMQP_TRANSPORT_INSTANCE* transport_state = NULL;
-
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_005: [If parameter config or config->upperConfig are NULL then IoTHubTransport_AMQP_Common_Create shall fail and return NULL.]
- if (config == NULL || config->upperConfig == NULL)
- {
- LogError("IoTHub AMQP client transport null configuration parameter.");
- }
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_006: [IoTHubTransport_AMQP_Common_Create shall fail and return NULL if any fields of the config->upperConfig structure are NULL.]
- else if (config->upperConfig->protocol == NULL)
- {
- LogError("Invalid configuration (NULL protocol detected)");
- }
- else if (config->upperConfig->iotHubName == NULL)
- {
- LogError("Invalid configuration (NULL iotHubName detected)");
- }
- else if (config->upperConfig->iotHubSuffix == NULL)
- {
- LogError("Invalid configuration (NULL iotHubSuffix detected)");
- }
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_134: [IoTHubTransport_AMQP_Common_Create shall fail and return NULL if the combined length of config->iotHubName and config->iotHubSuffix exceeds 254 bytes (RFC1035)]
- else if ((strlen(config->upperConfig->iotHubName) + strlen(config->upperConfig->iotHubSuffix)) > (RFC1035_MAX_FQDN_LENGTH - 1))
- {
- LogError("The lengths of iotHubName and iotHubSuffix together exceed the maximum FQDN length allowed (RFC 1035)");
- }
- // Codes_SRS_IOTHUBTRANSPORTAMQP_09_254: [If parameter `get_io_transport` is NULL then IoTHubTransport_AMQP_Common_Create shall fail and return NULL.]
- else if (get_io_transport == NULL)
- {
- LogError("Invalid configuration (get_io_transport is NULL)");
- }
- else
- {
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_009: [IoTHubTransport_AMQP_Common_Create shall fail and return NULL if memory allocation of the transport's internal state structure fails.]
- if ((transport_state = (AMQP_TRANSPORT_INSTANCE*)malloc(sizeof(AMQP_TRANSPORT_INSTANCE))) == NULL)
- {
- LogError("Could not allocate AMQP transport state");
- }
- else
- {
- bool cleanup_required = false;
+ TRANSPORT_LL_HANDLE result;
- transport_state->iotHubHostFqdn = NULL;
- transport_state->connection = NULL;
- transport_state->connection_state = AMQP_MANAGEMENT_STATE_IDLE;
- transport_state->session = NULL;
- transport_state->tls_io = NULL;
- transport_state->underlying_io_transport_provider = get_io_transport;
- transport_state->is_trace_on = false;
-
- transport_state->cbs_connection.cbs_handle = NULL;
- transport_state->cbs_connection.sasl_io = NULL;
- transport_state->cbs_connection.sasl_mechanism = NULL;
-
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_020: [IoTHubTransport_AMQP_Common_Create shall set parameter device_state->sas_token_lifetime with the default value of 3600000 (milliseconds).]
- transport_state->cbs_connection.sas_token_lifetime = DEFAULT_SAS_TOKEN_LIFETIME_MS;
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_128: [IoTHubTransport_AMQP_Common_Create shall set parameter device_state->sas_token_refresh_time with the default value of sas_token_lifetime/2 (milliseconds).]
- transport_state->cbs_connection.sas_token_refresh_time = transport_state->cbs_connection.sas_token_lifetime / 2;
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_129 : [IoTHubTransport_AMQP_Common_Create shall set parameter device_state->cbs_request_timeout with the default value of 30000 (milliseconds).]
- transport_state->cbs_connection.cbs_request_timeout = DEFAULT_CBS_REQUEST_TIMEOUT_MS;
-
- transport_state->preferred_credential_type = CREDENTIAL_NOT_BUILD;
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_001: [If `config` or `config->upperConfig` or `get_io_transport` are NULL then IoTHubTransport_AMQP_Common_Create shall fail and return NULL.]
+ if (config == NULL || config->upperConfig == NULL || get_io_transport == NULL)
+ {
+ LogError("IoTHub AMQP client transport null configuration parameter (config=%p, get_io_transport=%p).", config, get_io_transport);
+ result = NULL;
+ }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_002: [IoTHubTransport_AMQP_Common_Create shall fail and return NULL if `config->upperConfig->protocol` is NULL]
+ else if (config->upperConfig->protocol == NULL)
+ {
+ LogError("Failed to create the AMQP transport common instance (NULL parameter received: protocol=%p, iotHubName=%p, iotHubSuffix=%p)",
+ config->upperConfig->protocol, config->upperConfig->iotHubName, config->upperConfig->iotHubSuffix);
+ result = NULL;
+ }
+ else
+ {
+ AMQP_TRANSPORT_INSTANCE* instance;
- transport_state->xioOptions = NULL;
- transport_state->link_count = 0;
-
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_010: [If config->upperConfig->protocolGatewayHostName is NULL, IoTHubTransport_AMQP_Common_Create shall create an immutable string, referred to as iotHubHostFqdn, from the following pieces: config->iotHubName + "." + config->iotHubSuffix.]
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_20_001: [If config->upperConfig->protocolGatewayHostName is not NULL, IoTHubTransport_AMQP_Common_Create shall use it as iotHubHostFqdn]
- if ((transport_state->iotHubHostFqdn = (config->upperConfig->protocolGatewayHostName != NULL ? STRING_construct(config->upperConfig->protocolGatewayHostName) : concat3Params(config->upperConfig->iotHubName, ".", config->upperConfig->iotHubSuffix))) == NULL)
- {
- LogError("Failed to set transport_state->iotHubHostFqdn.");
- cleanup_required = true;
- }
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_218: [IoTHubTransport_AMQP_Common_Create shall initialize the transport state registered device list with a VECTOR instance.]
- else if ((transport_state->registered_devices = VECTOR_create(sizeof(IOTHUB_DEVICE_HANDLE))) == NULL)
- {
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_219: [If VECTOR_create fails, IoTHubTransport_AMQP_Common_Create shall fail and return.]
- LogError("Failed to initialize the internal list of registered devices");
- cleanup_required = true;
- }
-
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_236: [If IoTHubTransport_AMQP_Common_Create fails it shall free any memory it allocated (iotHubHostFqdn, transport state).]
- if (cleanup_required)
- {
- if (transport_state->iotHubHostFqdn != NULL)
- STRING_delete(transport_state->iotHubHostFqdn);
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_003: [Memory shall be allocated for the transport's internal state structure (`instance`)]
+ if ((instance = (AMQP_TRANSPORT_INSTANCE*)malloc(sizeof(AMQP_TRANSPORT_INSTANCE))) == NULL)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_004: [If malloc() fails, IoTHubTransport_AMQP_Common_Create shall fail and return NULL]
+ LogError("Could not allocate AMQP transport state (malloc failed)");
+ result = NULL;
+ }
+ else
+ {
+ memset(instance, 0, sizeof(AMQP_TRANSPORT_INSTANCE));
- free(transport_state);
- transport_state = NULL;
- }
- }
- }
-
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_023: [If IoTHubTransport_AMQP_Common_Create succeeds it shall return a non-NULL pointer to the structure that represents the transport.]
- return transport_state;
-}
-
-static RESULT device_DoWork(AMQP_TRANSPORT_DEVICE_STATE* device_state)
-{
- RESULT result = RESULT_SUCCESS;
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_243: [IoTHubTransport_AMQP_Common_DoWork shall retrieve the authenticatication status of the device using deviceauthentication_get_status()]
- AUTHENTICATION_STATUS auth_status = authentication_get_status(device_state->authentication);
-
- switch (auth_status)
- {
- case AUTHENTICATION_STATUS_IDLE:
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_243: [If the device authentication status is AUTHENTICATION_STATUS_IDLE, IoTHubTransport_AMQP_Common_DoWork shall authenticate it using authentication_authenticate()]
- if (authentication_authenticate(device_state->authentication) != RESULT_OK)
- {
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_146: [If authentication_authenticate() fails, IoTHubTransport_AMQP_Common_DoWork shall fail and process the next device]
- LogError("Failed authenticating AMQP connection [%s]", STRING_c_str(device_state->deviceId));
- result = RESULT_RETRYABLE_ERROR;
- }
- break;
- case AUTHENTICATION_STATUS_REFRESH_REQUIRED:
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_081: [If the device authentication status is AUTHENTICATION_STATUS_REFRESH_REQUIRED, IoTHubTransport_AMQP_Common_DoWork shall refresh it using authentication_refresh()]
- if (authentication_refresh(device_state->authentication) != RESULT_OK)
- {
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_082: [**If authentication_refresh() fails, IoTHubTransport_AMQP_Common_DoWork shall fail and process the next device]
- LogError("AMQP transport failed to refresh authentication [%s]", STRING_c_str(device_state->deviceId));
- result = RESULT_RETRYABLE_ERROR;
- }
- break;
- case AUTHENTICATION_STATUS_OK:
-#ifdef WIP_C2D_METHODS_AMQP /* This feature is WIP, do not use yet */
- /* Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_01_031: [ `iothubtransportamqp_methods_subscribe` shall only be called once (subsequent DoWork calls shall not call it if already subscribed). ]*/
- if ((device_state->subscribe_methods_needed) &&
- (subscribe_methods(device_state) != 0))
- {
- LogError("Failed subscribing for methods");
- }
-#endif
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_005: [If `config->upperConfig->protocolGatewayHostName` is NULL, `instance->iothub_target_fqdn` shall be set as `config->upperConfig->iotHubName` + "." + `config->upperConfig->iotHubSuffix`]
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_006: [If `config->upperConfig->protocolGatewayHostName` is not NULL, `instance->iothub_target_fqdn` shall be set with a copy of it]
+ if ((instance->iothub_host_fqdn = get_target_iothub_fqdn(config)) == NULL)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_007: [If `instance->iothub_target_fqdn` fails to be set, IoTHubTransport_AMQP_Common_Create shall fail and return NULL]
+ LogError("Failed to obtain the iothub target fqdn.");
+ result = NULL;
+ }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_008: [`instance->registered_devices` shall be set using singlylinkedlist_create()]
+ else if ((instance->registered_devices = singlylinkedlist_create()) == NULL)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_009: [If singlylinkedlist_create() fails, IoTHubTransport_AMQP_Common_Create shall fail and return NULL]
+ LogError("Failed to initialize the internal list of registered devices (singlylinkedlist_create failed)");
+ result = NULL;
+ }
+ else
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_010: [`get_io_transport` shall be saved on `instance->underlying_io_transport_provider`]
+ instance->underlying_io_transport_provider = get_io_transport;
+ instance->preferred_authentication_mode = AMQP_TRANSPORT_AUTHENTICATION_MODE_NOT_SET;
+ instance->option_sas_token_lifetime_secs = DEFAULT_SAS_TOKEN_LIFETIME_SECS;
+ instance->option_sas_token_refresh_time_secs = DEFAULT_SAS_TOKEN_REFRESH_TIME_SECS;
+ instance->option_cbs_request_timeout_secs = DEFAULT_CBS_REQUEST_TIMEOUT_SECS;
+ instance->option_send_event_timeout_secs = DEFAULT_EVENT_SEND_TIMEOUT_SECS;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_012: [If IoTHubTransport_AMQP_Common_Create succeeds it shall return a pointer to `instance`.]
+ result = (TRANSPORT_LL_HANDLE)instance;
+ }
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_145: [If the device authentication status is AUTHENTICATION_STATUS_OK, IoTHubTransport_AMQP_Common_DoWork shall proceed to sending events, registering for messages]
-
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_121: [IoTHubTransport_AMQP_Common_DoWork shall create an AMQP message_receiver if transport_state->message_receive is NULL and transport_state->receive_messages is true]
- if (device_state->receive_messages == true &&
- device_state->message_receiver == NULL &&
- createMessageReceiver(device_state) != RESULT_OK)
- {
- LogError("Failed creating AMQP transport message receiver [%s]", STRING_c_str(device_state->deviceId));
- result = RESULT_CRITICAL_ERROR;
- }
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_122: [IoTHubTransport_AMQP_Common_DoWork shall destroy the transport_state->message_receiver (and set it to NULL) if it exists and transport_state->receive_messages is false]
- else if (device_state->receive_messages == false &&
- device_state->message_receiver != NULL &&
- destroyMessageReceiver(device_state) != RESULT_OK)
- {
- LogError("Failed destroying AMQP transport message receiver [%s]", STRING_c_str(device_state->deviceId));
- }
-
- if (device_state->message_sender == NULL &&
- createEventSender(device_state) != RESULT_OK)
- {
- LogError("Failed creating AMQP transport event sender [%s]", STRING_c_str(device_state->deviceId));
- result = RESULT_CRITICAL_ERROR;
- }
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_245: [IoTHubTransport_AMQP_Common_DoWork shall skip sending events if the state of the message_sender is not MESSAGE_SENDER_STATE_OPEN]
- else if (device_state->message_sender_state == MESSAGE_SENDER_STATE_OPEN &&
- sendPendingEvents(device_state) != RESULT_OK)
- {
- LogError("AMQP transport failed sending events [%s]", STRING_c_str(device_state->deviceId));
- result = RESULT_CRITICAL_ERROR;
- }
- break;
- case AUTHENTICATION_STATUS_FAILURE:
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_083: [If the device authentication status is AUTHENTICATION_STATUS_FAILURE, IoTHubTransport_AMQP_Common_DoWork shall fail and process the next device]
- LogError("Authentication failed [%s]", STRING_c_str(device_state->deviceId));
- result = RESULT_CRITICAL_ERROR;
- break;
- case AUTHENTICATION_STATUS_TIMEOUT:
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_084: [If the device authentication status is AUTHENTICATION_STATUS_TIMEOUT, IoTHubTransport_AMQP_Common_DoWork shall fail and process the next device]
- LogError("Authentication timed-out [%s]", STRING_c_str(device_state->deviceId));
- result = RESULT_CRITICAL_ERROR;
- break;
- default:
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_244: [If the device authentication status is AUTHENTICATION_STATUS_IN_PROGRESS, IoTHubTransport_AMQP_Common_DoWork shall skip and process the next device]
- break;
- }
+ if (result == NULL)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_011: [If IoTHubTransport_AMQP_Common_Create fails it shall free any memory it allocated]
+ internal_destroy_instance(instance);
+ }
+ }
+ }
return result;
}
@@ -1297,60 +1211,75 @@
void IoTHubTransport_AMQP_Common_DoWork(TRANSPORT_LL_HANDLE handle, IOTHUB_CLIENT_LL_HANDLE iotHubClientHandle)
{
- (void)iotHubClientHandle;
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_051: [IoTHubTransport_AMQP_Common_DoWork shall fail and return immediately if the transport handle parameter is NULL]
+ (void)iotHubClientHandle; // unused as of now.
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_016: [If `handle` is NULL, IoTHubTransport_AMQP_Common_DoWork shall return without doing any work]
if (handle == NULL)
{
LogError("IoTHubClient DoWork failed: transport handle parameter is NULL.");
}
else
{
- bool trigger_connection_retry = false;
- AMQP_TRANSPORT_INSTANCE* transport_state = (AMQP_TRANSPORT_INSTANCE*)handle;
- size_t number_of_registered_devices = VECTOR_size(transport_state->registered_devices);
+ AMQP_TRANSPORT_INSTANCE* transport_instance = (AMQP_TRANSPORT_INSTANCE*)handle;
+ LIST_ITEM_HANDLE list_item;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_017: [If `instance->is_connection_retry_required` is true, IoTHubTransport_AMQP_Common_DoWork shall trigger the connection-retry logic and return]
+ if (transport_instance->is_connection_retry_required)
+ {
+ LogError("An error occured on AMQP connection. The connection will be restablished.");
+
+ prepare_for_connection_retry(transport_instance);
+
+ transport_instance->is_connection_retry_required = false;
+ }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_018: [If there are no devices registered on the transport, IoTHubTransport_AMQP_Common_DoWork shall skip do_work for devices]
+ else if ((list_item = singlylinkedlist_get_head_item(transport_instance->registered_devices)) != NULL)
+ {
+ // We need to check if there are devices, otherwise the amqp_connection won't be able to be created since
+ // there is not a preferred authentication mode set yet on the transport.
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_237: [IoTHubTransport_AMQP_Common_DoWork shall return immediately if there are no devices registered on the transport]
- if (number_of_registered_devices > 0)
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_019: [If `instance->amqp_connection` is NULL, it shall be established]
+ if (transport_instance->amqp_connection == NULL && establish_amqp_connection(transport_instance) != RESULT_OK)
+ {
+ LogError("AMQP transport failed to establish connection with service.");
+ }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_020: [If the amqp_connection is OPENED, the transport shall iterate through each registered device and perform a device-specific do_work on each]
+ else if (transport_instance->amqp_connection_state == AMQP_CONNECTION_STATE_OPENED)
+ {
+ while (list_item != NULL)
+ {
+ AMQP_TRANSPORT_DEVICE_INSTANCE* registered_device;
+
+ if ((registered_device = (AMQP_TRANSPORT_DEVICE_INSTANCE*)singlylinkedlist_item_get_value(list_item)) == NULL)
+ {
+ LogError("Transport had an unexpected failure during DoWork (failed to fetch a registered_devices list item value)");
+ }
+ else if (registered_device->number_of_send_event_complete_failures >= MAX_NUMBER_OF_DEVICE_FAILURES)
+ {
+ LogError("Device '%s' reported a critical failure (events completed sending with failures); connection retry will be triggered.", STRING_c_str(registered_device->device_id));
+
+ transport_instance->is_connection_retry_required = true;
+ }
+ else if (IoTHubTransport_AMQP_Common_Device_DoWork(registered_device) != RESULT_OK)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_021: [If DoWork fails for the registered device for more than MAX_NUMBER_OF_DEVICE_FAILURES, connection retry shall be triggered]
+ if (registered_device->number_of_previous_failures >= MAX_NUMBER_OF_DEVICE_FAILURES)
+ {
+ LogError("Device '%s' reported a critical failure; connection retry will be triggered.", STRING_c_str(registered_device->device_id));
+
+ transport_instance->is_connection_retry_required = true;
+ }
+ }
+
+ list_item = singlylinkedlist_get_next_item(list_item);
+ }
+ }
+ }
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_022: [If `instance->amqp_connection` is not NULL, amqp_connection_do_work shall be invoked]
+ if (transport_instance->amqp_connection != NULL)
{
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_238: [If the transport state has a faulty connection state, IoTHubTransport_AMQP_Common_DoWork shall trigger the connection-retry logic]
- if (transport_state->connection != NULL &&
- transport_state->connection_state == AMQP_MANAGEMENT_STATE_ERROR)
- {
- LogError("An error occured on AMQP connection. The connection will be restablished.");
- trigger_connection_retry = true;
- }
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_055: [If the transport handle has a NULL connection, IoTHubTransport_AMQP_Common_DoWork shall instantiate and initialize the AMQP components and establish the connection]
- else if (transport_state->connection == NULL &&
- establishConnection(transport_state) != RESULT_OK)
- {
- LogError("AMQP transport failed to establish connection with service.");
- trigger_connection_retry = true;
- }
- else
- {
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_241: [IoTHubTransport_AMQP_Common_DoWork shall iterate through all its registered devices to process authentication, events to be sent, messages to be received]
- for (size_t i = 0; i < number_of_registered_devices; i++)
- {
- AMQP_TRANSPORT_DEVICE_STATE* device_state = *(AMQP_TRANSPORT_DEVICE_STATE**)VECTOR_element(transport_state->registered_devices, i);
-
- RESULT actionable_result = device_DoWork(device_state);
-
- if (actionable_result == RESULT_CRITICAL_ERROR)
- {
- trigger_connection_retry = true;
- }
- }
- }
-
- if (trigger_connection_retry)
- {
- prepareForConnectionRetry(transport_state);
- }
- else
- {
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_103: [IoTHubTransport_AMQP_Common_DoWork shall invoke connection_dowork() on AMQP for triggering sending and receiving messages]
- connection_dowork(transport_state->connection);
- }
+ amqp_connection_do_work(transport_instance->amqp_connection);
}
}
}
@@ -1359,7 +1288,7 @@
{
int result;
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_037: [IoTHubTransport_AMQP_Common_Subscribe shall fail if the transport handle parameter received is NULL.]
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_084: [If `handle` is NULL, IoTHubTransport_AMQP_Common_Subscribe shall return a non-zero result]
if (handle == NULL)
{
LogError("Invalid handle to IoTHubClient AMQP transport device handle.");
@@ -1367,10 +1296,26 @@
}
else
{
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_038: [IoTHubTransport_AMQP_Common_Subscribe shall set transport_handle->receive_messages to true and return success code.]
- AMQP_TRANSPORT_DEVICE_STATE* device_state = (AMQP_TRANSPORT_DEVICE_STATE*)handle;
- device_state->receive_messages = true;
- result = 0;
+ AMQP_TRANSPORT_DEVICE_INSTANCE* amqp_device_instance = (AMQP_TRANSPORT_DEVICE_INSTANCE*)handle;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_085: [If `amqp_device_instance` is not registered, IoTHubTransport_AMQP_Common_Subscribe shall return a non-zero result]
+ if (!is_device_registered(amqp_device_instance))
+ {
+ LogError("Device '%s' failed subscribing to cloud-to-device messages (device is not registered)", STRING_c_str(amqp_device_instance->device_id));
+ result = __FAILURE__;
+ }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_086: [device_subscribe_message() shall be invoked passing `on_message_received_callback`]
+ else if (device_subscribe_message(amqp_device_instance->device_handle, on_message_received, amqp_device_instance) != RESULT_OK)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_087: [If device_subscribe_message() fails, IoTHubTransport_AMQP_Common_Subscribe shall return a non-zero result]
+ LogError("Device '%s' failed subscribing to cloud-to-device messages (device_subscribe_message failed)", STRING_c_str(amqp_device_instance->device_id));
+ result = __FAILURE__;
+ }
+ else
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_088: [If no failures occur, IoTHubTransport_AMQP_Common_Subscribe shall return 0]
+ result = RESULT_OK;
+ }
}
return result;
@@ -1378,16 +1323,25 @@
void IoTHubTransport_AMQP_Common_Unsubscribe(IOTHUB_DEVICE_HANDLE handle)
{
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_039: [IoTHubTransport_AMQP_Common_Unsubscribe shall fail if the transport handle parameter received is NULL.]
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_093: [If `handle` is NULL, IoTHubTransport_AMQP_Common_Subscribe shall return]
if (handle == NULL)
{
LogError("Invalid handle to IoTHubClient AMQP transport device handle.");
}
else
{
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_040: [IoTHubTransport_AMQP_Common_Unsubscribe shall set transport_handle->receive_messages to false.]
- AMQP_TRANSPORT_DEVICE_STATE* device_state = (AMQP_TRANSPORT_DEVICE_STATE*)handle;
- device_state->receive_messages = false;
+ AMQP_TRANSPORT_DEVICE_INSTANCE* amqp_device_instance = (AMQP_TRANSPORT_DEVICE_INSTANCE*)handle;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_094: [If `amqp_device_instance` is not registered, IoTHubTransport_AMQP_Common_Subscribe shall return]
+ if (!is_device_registered(amqp_device_instance))
+ {
+ LogError("Device '%s' failed unsubscribing to cloud-to-device messages (device is not registered)", STRING_c_str(amqp_device_instance->device_id));
+ }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_095: [device_unsubscribe_message() shall be invoked passing `amqp_device_instance->device_handle`]
+ else if (device_unsubscribe_message(amqp_device_instance->device_handle) != RESULT_OK)
+ {
+ LogError("Device '%s' failed unsubscribing to cloud-to-device messages (device_unsubscribe_message failed)", STRING_c_str(amqp_device_instance->device_id));
+ }
}
}
@@ -1420,7 +1374,7 @@
else
{
#ifdef WIP_C2D_METHODS_AMQP /* This feature is WIP, do not use yet */
- AMQP_TRANSPORT_DEVICE_STATE* device_state = (AMQP_TRANSPORT_DEVICE_STATE*)handle;
+ AMQP_TRANSPORT_DEVICE_INSTANCE* device_state = (AMQP_TRANSPORT_DEVICE_INSTANCE*)handle;
/* Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_01_026: [ `IoTHubTransport_AMQP_Common_Subscribe_DeviceMethod` shall remember that a subscribe is to be performed in the next call to DoWork and on success it shall return 0. ]*/
/* Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_01_005: [ If the transport is already subscribed to receive C2D method requests, `IoTHubTransport_AMQP_Common_Subscribe_DeviceMethod` shall perform no additional action and return 0. ]*/
device_state->subscribe_methods_needed = true;
@@ -1445,14 +1399,15 @@
else
{
#ifdef WIP_C2D_METHODS_AMQP /* This feature is WIP, do not use yet */
- AMQP_TRANSPORT_DEVICE_STATE* device_state = (AMQP_TRANSPORT_DEVICE_STATE*)handle;
+ AMQP_TRANSPORT_DEVICE_INSTANCE* device_state = (AMQP_TRANSPORT_DEVICE_INSTANCE*)handle;
/* Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_01_008: [ If the transport is not subscribed to receive C2D method requests then `IoTHubTransport_AMQP_Common_Unsubscribe_DeviceMethod` shall do nothing. ]*/
if (device_state->subscribe_methods_needed)
{
/* Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_01_007: [ `IoTHubTransport_AMQP_Common_Unsubscribe_DeviceMethod` shall unsubscribe from receiving C2D method requests by calling `iothubtransportamqp_methods_unsubscribe`. ]*/
- device_state->subscribed_for_methods = false;
- iothubtransportamqp_methods_unsubscribe(device_state->methods_handle);
+ device_state->subscribed_for_methods = false;
+ device_state->subscribe_methods_needed = false;
+ iothubtransportamqp_methods_unsubscribe(device_state->methods_handle);
}
#else
LogError("Not implemented");
@@ -1467,7 +1422,7 @@
(void)status_response;
(void)methodId;
int result;
- AMQP_TRANSPORT_DEVICE_STATE* device_state = (AMQP_TRANSPORT_DEVICE_STATE*)handle;
+ AMQP_TRANSPORT_DEVICE_INSTANCE* device_state = (AMQP_TRANSPORT_DEVICE_INSTANCE*)handle;
if (device_state != NULL)
{
#ifdef WIP_C2D_METHODS_AMQP /* This feature is WIP, do not use yet */
@@ -1499,34 +1454,40 @@
{
IOTHUB_CLIENT_RESULT result;
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_041: [IoTHubTransport_AMQP_Common_GetSendStatus shall return IOTHUB_CLIENT_INVALID_ARG if called with NULL parameter.]
- if (handle == NULL)
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_096: [If `handle` or `iotHubClientStatus` are NULL, IoTHubTransport_AMQP_Common_GetSendStatus shall return IOTHUB_CLIENT_INVALID_ARG]
+ if (handle == NULL || iotHubClientStatus == NULL)
{
result = IOTHUB_CLIENT_INVALID_ARG;
- LogError("Invalid handle to IoTHubClient AMQP transport instance.");
- }
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_041: [IoTHubTransport_AMQP_Common_GetSendStatus shall return IOTHUB_CLIENT_INVALID_ARG if called with NULL parameter.]
- else if (iotHubClientStatus == NULL)
- {
- result = IOTHUB_CLIENT_INVALID_ARG;
- LogError("Invalid pointer to output parameter IOTHUB_CLIENT_STATUS.");
+ LogError("Failed retrieving the device send status (either handle (%p) or iotHubClientStatus (%p) are NULL)", handle, iotHubClientStatus);
}
else
{
- AMQP_TRANSPORT_DEVICE_STATE* device_state = (AMQP_TRANSPORT_DEVICE_STATE*)handle;
+ AMQP_TRANSPORT_DEVICE_INSTANCE* amqp_device_state = (AMQP_TRANSPORT_DEVICE_INSTANCE*)handle;
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_043: [IoTHubTransport_AMQP_Common_GetSendStatus shall return IOTHUB_CLIENT_OK and status IOTHUB_CLIENT_SEND_STATUS_BUSY if there are currently event items to be sent or being sent.]
- if (!DList_IsListEmpty(device_state->waitingToSend) || !DList_IsListEmpty(&(device_state->inProgress)))
- {
- *iotHubClientStatus = IOTHUB_CLIENT_SEND_STATUS_BUSY;
- }
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_042: [IoTHubTransport_AMQP_Common_GetSendStatus shall return IOTHUB_CLIENT_OK and status IOTHUB_CLIENT_SEND_STATUS_IDLE if there are currently no event items to be sent or being sent.]
- else
- {
- *iotHubClientStatus = IOTHUB_CLIENT_SEND_STATUS_IDLE;
- }
+ DEVICE_SEND_STATUS device_send_status;
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_097: [IoTHubTransport_AMQP_Common_GetSendStatus shall invoke device_get_send_status()]
+ if (device_get_send_status(amqp_device_state->device_handle, &device_send_status) != RESULT_OK)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_098: [If device_get_send_status() fails, IoTHubTransport_AMQP_Common_GetSendStatus shall return IOTHUB_CLIENT_ERROR]
+ LogError("Failed retrieving the device send status (device_get_send_status failed)");
+ result = IOTHUB_CLIENT_ERROR;
+ }
+ else
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_099: [If device_get_send_status() returns DEVICE_SEND_STATUS_BUSY, IoTHubTransport_AMQP_Common_GetSendStatus shall return IOTHUB_CLIENT_OK and status IOTHUB_CLIENT_SEND_STATUS_BUSY]
+ if (device_send_status == DEVICE_SEND_STATUS_BUSY)
+ {
+ *iotHubClientStatus = IOTHUB_CLIENT_SEND_STATUS_BUSY;
+ }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_100: [If device_get_send_status() returns DEVICE_SEND_STATUS_IDLE, IoTHubTransport_AMQP_Common_GetSendStatus shall return IOTHUB_CLIENT_OK and status IOTHUB_CLIENT_SEND_STATUS_IDLE]
+ else // DEVICE_SEND_STATUS_IDLE
+ {
+ *iotHubClientStatus = IOTHUB_CLIENT_SEND_STATUS_IDLE;
+ }
- result = IOTHUB_CLIENT_OK;
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_109: [If no failures occur, IoTHubTransport_AMQP_Common_GetSendStatus shall return IOTHUB_CLIENT_OK]
+ result = IOTHUB_CLIENT_OK;
+ }
}
return result;
@@ -1536,137 +1497,122 @@
{
IOTHUB_CLIENT_RESULT result;
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_044: [If handle parameter is NULL then IoTHubTransport_AMQP_Common_SetOption shall return IOTHUB_CLIENT_INVALID_ARG.]
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_045: [If parameter optionName is NULL then IoTHubTransport_AMQP_Common_SetOption shall return IOTHUB_CLIENT_INVALID_ARG.]
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_046: [If parameter value is NULL then IoTHubTransport_AMQP_Common_SetOption shall return IOTHUB_CLIENT_INVALID_ARG.]
- if (
- (handle == NULL) ||
- (option == NULL) ||
- (value == NULL)
- )
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_101: [If `handle`, `option` or `value` are NULL then IoTHubTransport_AMQP_Common_SetOption shall return IOTHUB_CLIENT_INVALID_ARG.]
+ if ((handle == NULL) || (option == NULL) || (value == NULL))
{
- result = IOTHUB_CLIENT_INVALID_ARG;
- LogError("Invalid parameter (NULL) passed to AMQP transport SetOption()");
+ LogError("Invalid parameter (NULL) passed to AMQP transport SetOption (handle=%p, options=%p, value=%p)", handle, option, value);
+ result = IOTHUB_CLIENT_INVALID_ARG;
}
else
{
- AMQP_TRANSPORT_INSTANCE* transport_state = (AMQP_TRANSPORT_INSTANCE*)handle;
-
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_048: [IotHubTransportAMQP_SetOption shall save and apply the value if the option name is "sas_token_lifetime", returning IOTHUB_CLIENT_OK]
- if (strcmp(OPTION_SAS_TOKEN_LIFETIME, option) == 0)
- {
- transport_state->cbs_connection.sas_token_lifetime = *((size_t*)value);
- result = IOTHUB_CLIENT_OK;
- }
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_049: [IotHubTransportAMQP_SetOption shall save and apply the value if the option name is "sas_token_refresh_time", returning IOTHUB_CLIENT_OK]
- else if (strcmp(OPTION_SAS_TOKEN_REFRESH_TIME, option) == 0)
- {
- transport_state->cbs_connection.sas_token_refresh_time = *((size_t*)value);
- result = IOTHUB_CLIENT_OK;
- }
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_148: [IotHubTransportAMQP_SetOption shall save and apply the value if the option name is "cbs_request_timeout", returning IOTHUB_CLIENT_OK]
- else if (strcmp(OPTION_CBS_REQUEST_TIMEOUT, option) == 0)
- {
- transport_state->cbs_connection.cbs_request_timeout = *((size_t*)value);
- result = IOTHUB_CLIENT_OK;
- }
- else if (strcmp(OPTION_LOG_TRACE, option) == 0)
- {
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_198: [If `optionName` is `logtrace`, IoTHubTransport_AMQP_Common_SetOption shall save the value on the transport instance.]
- transport_state->is_trace_on = *((bool*)value);
+ AMQP_TRANSPORT_INSTANCE* transport_instance = (AMQP_TRANSPORT_INSTANCE*)handle;
+ bool is_device_specific_option;
- if (transport_state->connection != NULL)
- {
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_202: [If `optionName` is `logtrace`, IoTHubTransport_AMQP_Common_SetOption shall apply it using connection_set_trace() to current connection instance if it exists and return IOTHUB_CLIENT_OK.]
- connection_set_trace(transport_state->connection, transport_state->is_trace_on);
- }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_102: [If `option` is a device-specific option, it shall be saved and applied to each registered device using device_set_option()]
+ if (strcmp(OPTION_SAS_TOKEN_LIFETIME, option) == 0)
+ {
+ is_device_specific_option = true;
+ transport_instance->option_sas_token_lifetime_secs = *(size_t*)value;
+ }
+ else if (strcmp(OPTION_SAS_TOKEN_REFRESH_TIME, option) == 0)
+ {
+ is_device_specific_option = true;
+ transport_instance->option_sas_token_refresh_time_secs = *(size_t*)value;
+ }
+ else if (strcmp(OPTION_CBS_REQUEST_TIMEOUT, option) == 0)
+ {
+ is_device_specific_option = true;
+ transport_instance->option_cbs_request_timeout_secs = *(size_t*)value;
+ }
+ else if (strcmp(OPTION_EVENT_SEND_TIMEOUT_SECS, option) == 0)
+ {
+ is_device_specific_option = true;
+ transport_instance->option_send_event_timeout_secs = *(size_t*)value;
+ }
+ else
+ {
+ is_device_specific_option = false;
+ }
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_203: [If `optionName` is `logtrace`, IoTHubTransport_AMQP_Common_SetOption shall apply it using xio_setoption() to current SASL IO instance if it exists.]
- if (transport_state->cbs_connection.sasl_io != NULL &&
- xio_setoption(transport_state->cbs_connection.sasl_io, OPTION_LOG_TRACE, &transport_state->is_trace_on) != RESULT_OK)
- {
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_204: [If xio_setoption() fails, IoTHubTransport_AMQP_Common_SetOption shall fail and return IOTHUB_CLIENT_ERROR.]
- LogError("IoTHubTransport_AMQP_Common_SetOption failed (xio_setoption failed to set logging on SASL IO)");
- result = IOTHUB_CLIENT_ERROR;
- }
- else
- {
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_205: [If xio_setoption() succeeds, IoTHubTransport_AMQP_Common_SetOption shall return IOTHUB_CLIENT_OK.]
- result = IOTHUB_CLIENT_OK;
- }
- }
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_047: [If the option name does not match one of the options handled by this module, IoTHubTransport_AMQP_Common_SetOption shall pass the value and name to the XIO using xio_setoption().]
- else
- {
- result = IOTHUB_CLIENT_OK;
+ if (is_device_specific_option)
+ {
+ if (IoTHubTransport_AMQP_Common_Device_SetOption(handle, option, (void*)value) != RESULT_OK)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_103: [If device_set_option() fails, IoTHubTransport_AMQP_Common_SetOption shall return IOTHUB_CLIENT_ERROR]
+ LogError("transport failed setting option '%s' (failed setting option on one or more registered devices)", option);
+ result = IOTHUB_CLIENT_ERROR;
+ }
+ else
+ {
+ result = IOTHUB_CLIENT_OK;
+ }
+ }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_104: [If `option` is `logtrace`, `value` shall be saved and applied to `instance->connection` using amqp_connection_set_logging()]
+ else if (strcmp(OPTION_LOG_TRACE, option) == 0)
+ {
+ transport_instance->is_trace_on = *((bool*)value);
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_02_007: [ If optionName is x509certificate and the authentication method is not x509 then IoTHubTransport_AMQP_Common_SetOption shall return IOTHUB_CLIENT_INVALID_ARG. ]
- if (strcmp(OPTION_X509_CERT, option) == 0)
- {
- if (transport_state->preferred_credential_type == CREDENTIAL_NOT_BUILD)
- {
- transport_state->preferred_credential_type = X509;
- }
- else if (transport_state->preferred_credential_type != X509)
- {
- LogError("x509certificate specified, but authentication method is not x509");
- result = IOTHUB_CLIENT_INVALID_ARG;
- }
- }
- /*Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_02_008: [ If optionName is x509privatekey and the authentication method is not x509 then IoTHubTransport_AMQP_Common_SetOption shall return IOTHUB_CLIENT_INVALID_ARG. ]*/
- else if (strcmp(OPTION_X509_PRIVATE_KEY, option) == 0)
- {
- if (transport_state->preferred_credential_type == CREDENTIAL_NOT_BUILD)
- {
- transport_state->preferred_credential_type = X509;
- }
- else if (transport_state->preferred_credential_type != X509)
- {
- LogError("x509privatekey specified, but authentication method is not x509");
- result = IOTHUB_CLIENT_INVALID_ARG;
- }
- }
+ if (transport_instance->amqp_connection != NULL &&
+ amqp_connection_set_logging(transport_instance->amqp_connection, transport_instance->is_trace_on) != RESULT_OK)
+ {
+ LogError("transport failed setting option '%s' (amqp_connection_set_logging failed)", option);
+ result = IOTHUB_CLIENT_ERROR;
+ }
+ else
+ {
+ result = IOTHUB_CLIENT_OK;
+ }
+ }
+ else
+ {
+ result = IOTHUB_CLIENT_OK;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_02_007: [ If `option` is `x509certificate` and the transport preferred authentication method is not x509 then IoTHubTransport_AMQP_Common_SetOption shall return IOTHUB_CLIENT_INVALID_ARG. ]
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_02_008: [ If `option` is `x509privatekey` and the transport preferred authentication method is not x509 then IoTHubTransport_AMQP_Common_SetOption shall return IOTHUB_CLIENT_INVALID_ARG. ]
+ if (strcmp(OPTION_X509_CERT, option) == 0 || strcmp(OPTION_X509_PRIVATE_KEY, option) == 0)
+ {
+ if (transport_instance->preferred_authentication_mode == AMQP_TRANSPORT_AUTHENTICATION_MODE_NOT_SET)
+ {
+ transport_instance->preferred_authentication_mode = AMQP_TRANSPORT_AUTHENTICATION_MODE_X509;
+ }
+ else if (transport_instance->preferred_authentication_mode != AMQP_TRANSPORT_AUTHENTICATION_MODE_X509)
+ {
+ LogError("transport failed setting option '%s' (preferred authentication method is not x509)", option);
+ result = IOTHUB_CLIENT_INVALID_ARG;
+ }
+ }
- if (result != IOTHUB_CLIENT_INVALID_ARG)
- {
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_206: [If the TLS IO does not exist, IoTHubTransport_AMQP_Common_SetOption shall create it and save it on the transport instance.]
- if (transport_state->tls_io == NULL &&
- (transport_state->tls_io = transport_state->underlying_io_transport_provider(STRING_c_str(transport_state->iotHubHostFqdn))) == NULL)
- {
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_207: [If IoTHubTransport_AMQP_Common_SetOption fails creating the TLS IO instance, it shall fail and return IOTHUB_CLIENT_ERROR.]
- result = IOTHUB_CLIENT_ERROR;
- LogError("IoTHubTransport_AMQP_Common_SetOption failed (failed to obtain a TLS I/O transport layer).");
- }
- else
- {
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_208: [When a new TLS IO instance is created, IoTHubTransport_AMQP_Common_SetOption shall apply the TLS I/O Options with OptionHandler_FeedOptions() if it is has any saved.]
- if (transport_state->xioOptions != NULL)
- {
- if (OptionHandler_FeedOptions(transport_state->xioOptions, transport_state->tls_io) != 0)
- {
- LogError("IoTHubTransport_AMQP_Common_SetOption failed (unable to replay options to TLS)");
- }
- else
- {
- OptionHandler_Destroy(transport_state->xioOptions);
- transport_state->xioOptions = NULL;
- }
- }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_105: [If `option` does not match one of the options handled by this module, it shall be passed to `instance->tls_io` using xio_setoption()]
+ if (result != IOTHUB_CLIENT_INVALID_ARG)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_106: [If `instance->tls_io` is NULL, it shall be set invoking instance->underlying_io_transport_provider()]
+ if (transport_instance->tls_io == NULL &&
+ get_new_underlying_io_transport(transport_instance, &transport_instance->tls_io) != RESULT_OK)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_107: [If instance->underlying_io_transport_provider() fails, IoTHubTransport_AMQP_Common_SetOption shall fail and return IOTHUB_CLIENT_ERROR]
+ LogError("transport failed setting option '%s' (failed to obtain a TLS I/O transport).", option);
+ result = IOTHUB_CLIENT_ERROR;
+ }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_108: [When `instance->tls_io` is created, IoTHubTransport_AMQP_Common_SetOption shall apply `instance->saved_tls_options` with OptionHandler_FeedOptions()]
+ else if (xio_setoption(transport_instance->tls_io, option, value) != RESULT_OK)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_03_001: [If xio_setoption fails, IoTHubTransport_AMQP_Common_SetOption shall return IOTHUB_CLIENT_ERROR.]
+ LogError("transport failed setting option '%s' (xio_setoption failed)", option);
+ result = IOTHUB_CLIENT_ERROR;
+ }
+ else
+ {
+ if (save_underlying_io_transport_options(transport_instance) != RESULT_OK)
+ {
+ LogError("IoTHubTransport_AMQP_Common_SetOption failed to save underlying I/O options; failure will be ignored");
+ }
- if (xio_setoption(transport_state->tls_io, option, value) != RESULT_OK)
- {
- /* Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_03_001: [If xio_setoption fails, IoTHubTransport_AMQP_Common_SetOption shall return IOTHUB_CLIENT_ERROR.] */
- result = IOTHUB_CLIENT_ERROR;
- LogError("Invalid option (%s) passed to IoTHubTransport_AMQP_Common_SetOption", option);
- }
- else
- {
- result = IOTHUB_CLIENT_OK;
- }
- }
- }
- }
- }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_03_001: [If no failures occur, IoTHubTransport_AMQP_Common_SetOption shall return IOTHUB_CLIENT_OK.]
+ result = IOTHUB_CLIENT_OK;
+ }
+ }
+ }
+ }
return result;
}
@@ -1677,178 +1623,145 @@
UNUSED(iotHubClientHandle);
#endif
- IOTHUB_DEVICE_HANDLE result = NULL;
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_17_001: [IoTHubTransport_AMQP_Common_Register shall return NULL if device, or waitingToSend are NULL.]
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_17_005: [IoTHubTransport_AMQP_Common_Register shall return NULL if the TRANSPORT_LL_HANDLE is NULL.]
- if ((handle == NULL) || (device == NULL) || (waitingToSend == NULL))
+ IOTHUB_DEVICE_HANDLE result;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_17_005: [If `handle`, `device`, `iotHubClientHandle` or `waitingToSend` is NULL, IoTHubTransport_AMQP_Common_Register shall return NULL]
+ if ((handle == NULL) || (device == NULL) || (waitingToSend == NULL) || (iotHubClientHandle == NULL))
{
- LogError("invalid parameter TRANSPORT_LL_HANDLE handle=%p, const IOTHUB_DEVICE_CONFIG* device=%p, IOTHUB_CLIENT_LL_HANDLE iotHubClientHandle=%p, PDLIST_ENTRY waitingToSend=%p",
+ LogError("invalid parameter TRANSPORT_LL_HANDLE handle=%p, const IOTHUB_DEVICE_CONFIG* device=%p, IOTHUB_CLIENT_LL_HANDLE iotHubClientHandle=%p, PDLIST_ENTRY waiting_to_send=%p",
handle, device, iotHubClientHandle, waitingToSend);
+ result = NULL;
}
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_220: [IoTHubTransport_AMQP_Common_Register shall fail and return NULL if the IOTHUB_CLIENT_LL_HANDLE is NULL.]
- else if (iotHubClientHandle == NULL)
- {
- LogError("IoTHubTransport_AMQP_Common_Register failed (invalid parameter; iotHubClientHandle list is NULL)");
- }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_03_002: [IoTHubTransport_AMQP_Common_Register shall return NULL if `device->deviceId` is NULL.]
+ else if (device->deviceId == NULL)
+ {
+ LogError("Transport failed to register device (device_id provided is NULL)");
+ result = NULL;
+ }
else
{
- AMQP_TRANSPORT_INSTANCE* transport_state = (AMQP_TRANSPORT_INSTANCE*)handle;
+ LIST_ITEM_HANDLE list_item;
+ AMQP_TRANSPORT_INSTANCE* transport_instance = (AMQP_TRANSPORT_INSTANCE*)handle;
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_03_002: [IoTHubTransport_AMQP_Common_Register shall return NULL if deviceId is NULL.]
- if (device->deviceId == NULL)
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_064: [If the device is already registered, IoTHubTransport_AMQP_Common_Register shall fail and return NULL.]
+ if (is_device_registered_ex(transport_instance->registered_devices, device->deviceId, &list_item))
+ {
+ LogError("IoTHubTransport_AMQP_Common_Register failed (device '%s' already registered on this transport instance)", device->deviceId);
+ result = NULL;
+ }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_065: [IoTHubTransport_AMQP_Common_Register shall fail and return NULL if the device is not using an authentication mode compatible with the currently used by the transport.]
+ else if (!is_device_credential_acceptable(device, transport_instance->preferred_authentication_mode))
{
- LogError("IoTHubTransport_AMQP_Common_Register failed (deviceId provided is NULL)");
- }
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_03_003: [IoTHubTransport_AMQP_Common_Register shall return NULL if both deviceKey and deviceSasToken are not NULL.]
- else if ((device->deviceSasToken != NULL) && (device->deviceKey != NULL))
- {
- LogError("IoTHubTransport_AMQP_Common_Register failed (invalid IOTHUB_DEVICE_CONFIG; must provide EITHER 'deviceSasToken' OR 'deviceKey')");
- }
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_221: [IoTHubTransport_AMQP_Common_Register shall fail and return NULL if the device is not using an authentication mode compatible with the currently used by the transport.]
- else if (is_credential_compatible(device, transport_state->preferred_credential_type) != RESULT_OK)
- {
- LogError("IoTHubTransport_AMQP_Common_Register failed (transport does not support mixed authentication methods)");
- }
+ LogError("Transport failed to register device '%s' (device credential was not accepted)", device->deviceId);
+ result = NULL;
+ }
else
{
- AMQP_TRANSPORT_DEVICE_STATE* device_state;
+ AMQP_TRANSPORT_DEVICE_INSTANCE* amqp_device_instance;
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_222: [If a device matching the deviceId provided is already registered, IoTHubTransport_AMQP_Common_Register shall fail and return NULL.]
- if (VECTOR_find_if(transport_state->registered_devices, findDeviceById, device->deviceId) != NULL)
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_066: [IoTHubTransport_AMQP_Common_Register shall allocate an instance of AMQP_TRANSPORT_DEVICE_INSTANCE to store the state of the new registered device.]
+ if ((amqp_device_instance = (AMQP_TRANSPORT_DEVICE_INSTANCE*)malloc(sizeof(AMQP_TRANSPORT_DEVICE_INSTANCE))) == NULL)
{
- LogError("IoTHubTransport_AMQP_Common_Register failed (device '%s' already registered on this transport instance)", device->deviceId);
- }
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_223: [IoTHubTransport_AMQP_Common_Register shall allocate an instance of AMQP_TRANSPORT_DEVICE_STATE to store the state of the new registered device.]
- else if ((device_state = (AMQP_TRANSPORT_DEVICE_STATE*)malloc(sizeof(AMQP_TRANSPORT_DEVICE_STATE))) == NULL)
- {
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_224: [If malloc fails to allocate memory for AMQP_TRANSPORT_DEVICE_STATE, IoTHubTransport_AMQP_Common_Register shall fail and return NULL.]
- LogError("IoTHubTransport_AMQP_Common_Register failed (malloc failed)");
- }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_067: [If malloc fails, IoTHubTransport_AMQP_Common_Register shall fail and return NULL.]
+ LogError("Transport failed to register device '%s' (failed to create the device state instance; malloc failed)", device->deviceId);
+ result = NULL;
+ }
else
{
- bool cleanup_required;
- const char* deviceId = device->deviceId;
-
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_225: [IoTHubTransport_AMQP_Common_Register shall save the handle references to the IoTHubClient, transport, waitingToSend list on the device state.]
- device_state->iothub_client_handle = iotHubClientHandle;
- device_state->transport_state = transport_state;
-
- device_state->waitingToSend = waitingToSend;
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_226: [IoTHubTransport_AMQP_Common_Register shall initialize the device state inProgress list using DList_InitializeListHead().]
- DList_InitializeListHead(&device_state->inProgress);
-
- device_state->deviceId = NULL;
- device_state->authentication = NULL;
- device_state->devicesPath = NULL;
- device_state->messageReceiveAddress = NULL;
- device_state->targetAddress = NULL;
-
- device_state->receive_messages = false;
- device_state->message_receiver = NULL;
- device_state->message_sender = NULL;
- device_state->message_sender_state = MESSAGE_SENDER_STATE_IDLE;
- device_state->receiver_link = NULL;
- device_state->sender_link = NULL;
+ memset(amqp_device_instance, 0, sizeof(AMQP_TRANSPORT_DEVICE_INSTANCE));
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_068: [IoTHubTransport_AMQP_Common_Register shall save the handle references to the IoTHubClient, transport, waitingToSend list on `amqp_device_instance`.]
+ amqp_device_instance->iothub_client_handle = iotHubClientHandle;
+ amqp_device_instance->transport_instance = transport_instance;
+ amqp_device_instance->waiting_to_send = waitingToSend;
+ amqp_device_instance->device_state = DEVICE_STATE_STOPPED;
+ amqp_device_instance->max_state_change_timeout_secs = DEFAULT_DEVICE_STATE_CHANGE_TIMEOUT_SECS;
+
#ifdef WIP_C2D_METHODS_AMQP /* This feature is WIP, do not use yet */
- device_state->subscribe_methods_needed = false;
- device_state->subscribed_for_methods = false;
+ amqp_device_instance->subscribe_methods_needed = false;
+ amqp_device_instance->subscribed_for_methods = false;
#endif
-
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_227: [IoTHubTransport_AMQP_Common_Register shall store a copy of config->deviceId into device_state->deviceId.]
- if ((device_state->deviceId = STRING_construct(deviceId)) == NULL)
- {
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_228: [If STRING_construct fails to copy config->deviceId, IoTHubTransport_AMQP_Common_Register shall fail and return NULL.]
- LogError("IoTHubTransport_AMQP_Common_Register failed to copy the deviceId.");
- cleanup_required = true;
- }
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_012: [IoTHubTransport_AMQP_Common_Register shall create an immutable string, referred to as devicesPath, from the following parts: host_fqdn + "/devices/" + deviceId.]
- else if ((device_state->devicesPath = concat3Params(STRING_c_str(transport_state->iotHubHostFqdn), "/devices/", STRING_c_str(device_state->deviceId))) == NULL)
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_069: [A copy of `config->deviceId` shall be saved into `device_state->device_id`]
+ if ((amqp_device_instance->device_id = STRING_construct(device->deviceId)) == NULL)
{
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_013: [If creating devicesPath fails for any reason then IoTHubTransport_AMQP_Common_Register shall fail and return NULL.]
- LogError("IoTHubTransport_AMQP_Common_Register failed to construct the devicesPath.");
- cleanup_required = true;
- }
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_014: [IoTHubTransport_AMQP_Common_Register shall create an immutable string, referred to as targetAddress, from the following parts: "amqps://" + devicesPath + "/messages/events".]
- else if ((device_state->targetAddress = concat3Params("amqps://", STRING_c_str(device_state->devicesPath), "/messages/events")) == NULL)
- {
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_015: [If creating the targetAddress fails for any reason then IoTHubTransport_AMQP_Common_Register shall fail and return NULL.]
- LogError("IoTHubTransport_AMQP_Common_Register failed to construct the targetAddress.");
- cleanup_required = true;
- }
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_053: [IoTHubTransport_AMQP_Common_Register shall define the source address for receiving messages as "amqps://" + devicesPath + "/messages/devicebound", stored in the transport handle as messageReceiveAddress]
- else if ((device_state->messageReceiveAddress = concat3Params("amqps://", STRING_c_str(device_state->devicesPath), "/messages/devicebound")) == NULL)
- {
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_054: [If creating the messageReceiveAddress fails for any reason then IoTHubTransport_AMQP_Common_Register shall fail and return NULL.]
- LogError("IoTHubTransport_AMQP_Common_Register failed to construct the messageReceiveAddress.");
- cleanup_required = true;
- }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_070: [If STRING_construct() fails, IoTHubTransport_AMQP_Common_Register shall fail and return NULL]
+ LogError("Transport failed to register device '%s' (failed to copy the deviceId)", device->deviceId);
+ result = NULL;
+ }
else
{
+ DEVICE_CONFIG device_config;
+ memset(&device_config, 0, sizeof(DEVICE_CONFIG));
+ device_config.device_id = (char*)device->deviceId;
+ device_config.iothub_host_fqdn = (char*)STRING_c_str(transport_instance->iothub_host_fqdn);
+ device_config.device_primary_key = (char*)device->deviceKey;
+ device_config.device_sas_token = (char*)device->deviceSasToken;
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_072: [The configuration for device_create shall be set according to the authentication preferred by IOTHUB_DEVICE_CONFIG]
+ device_config.authentication_mode = (device->deviceKey != NULL || device->deviceSasToken != NULL ? DEVICE_AUTH_MODE_CBS : DEVICE_AUTH_MODE_X509);
+ device_config.on_state_changed_callback = on_device_state_changed_callback;
+ device_config.on_state_changed_context = amqp_device_instance;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_071: [`amqp_device_instance->device_handle` shall be set using device_create()]
+ if ((amqp_device_instance->device_handle = device_create(&device_config)) == NULL)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_073: [If device_create() fails, IoTHubTransport_AMQP_Common_Register shall fail and return NULL]
+ LogError("Transport failed to register device '%s' (failed to create the DEVICE_HANDLE instance)", device->deviceId);
+ result = NULL;
+ }
+ else
+ {
+ bool is_first_device_being_registered = (singlylinkedlist_get_head_item(transport_instance->registered_devices) == NULL);
+
#ifdef WIP_C2D_METHODS_AMQP /* This feature is WIP, do not use yet */
- /* Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_01_010: [ `IoTHubTransport_AMQP_Common_Create` shall create a new iothubtransportamqp_methods instance by calling `iothubtransportamqp_methods_create` while passing to it the the fully qualified domain name and the device Id. ]*/
- device_state->methods_handle = iothubtransportamqp_methods_create(STRING_c_str(transport_state->iotHubHostFqdn), deviceId);
- if (device_state->methods_handle == NULL)
- {
- /* Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_01_011: [ If `iothubtransportamqp_methods_create` fails, `IoTHubTransport_AMQP_Common_Create` shall fail and return NULL. ]*/
- LogError("Cannot create the methods module");
- cleanup_required = true;
- }
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_231: [IoTHubTransport_AMQP_Common_Register shall add the device to transport_state->registered_devices using VECTOR_push_back().]
- else
+ /* Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_01_010: [ `IoTHubTransport_AMQP_Common_Create` shall create a new iothubtransportamqp_methods instance by calling `iothubtransportamqp_methods_create` while passing to it the the fully qualified domain name and the device Id. ]*/
+ amqp_device_instance->methods_handle = iothubtransportamqp_methods_create(STRING_c_str(transport_instance->iothub_host_fqdn), device->deviceId);
+ if (amqp_device_instance->methods_handle == NULL)
+ {
+ /* Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_01_011: [ If `iothubtransportamqp_methods_create` fails, `IoTHubTransport_AMQP_Common_Create` shall fail and return NULL. ]*/
+ LogError("Transport failed to register device '%s' (Cannot create the methods module)", device->deviceId);
+ result = NULL;
+ }
+ else
#endif
- if (VECTOR_push_back(transport_state->registered_devices, &device_state, 1) != 0)
- {
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_232: [If VECTOR_push_back() fails to add the new registered device, IoTHubTransport_AMQP_Common_Register shall clean the memory it allocated, fail and return NULL.]
- LogError("IoTHubTransport_AMQP_Common_Register failed to add the new device to its list of registered devices (VECTOR_push_back failed).");
- cleanup_required = true;
- }
- else
- {
- AUTHENTICATION_CONFIG auth_config;
- auth_config.device_id = deviceId;
- auth_config.device_key = device->deviceKey;
- auth_config.device_sas_token = device->deviceSasToken;
- auth_config.cbs_connection = &device_state->transport_state->cbs_connection;
- auth_config.iot_hub_host_fqdn = STRING_c_str(device_state->transport_state->iotHubHostFqdn);
+ if (replicate_device_options_to(amqp_device_instance, device_config.authentication_mode) != RESULT_OK)
+ {
+ LogError("Transport failed to register device '%s' (failed to replicate options)", device->deviceId);
+ result = NULL;
+ }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_074: [IoTHubTransport_AMQP_Common_Register shall add the `amqp_device_instance` to `instance->registered_devices`]
+ else if (singlylinkedlist_add(transport_instance->registered_devices, amqp_device_instance) == NULL)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_075: [If it fails to add `amqp_device_instance`, IoTHubTransport_AMQP_Common_Register shall fail and return NULL]
+ LogError("Transport failed to register device '%s' (singlylinkedlist_add failed)", device->deviceId);
+ result = NULL;
+ }
+ else
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_076: [If the device is the first being registered on the transport, IoTHubTransport_AMQP_Common_Register shall save its authentication mode as the transport preferred authentication mode]
+ if (transport_instance->preferred_authentication_mode == AMQP_TRANSPORT_AUTHENTICATION_MODE_NOT_SET &&
+ is_first_device_being_registered)
+ {
+ if (device_config.authentication_mode == DEVICE_AUTH_MODE_CBS)
+ {
+ transport_instance->preferred_authentication_mode = AMQP_TRANSPORT_AUTHENTICATION_MODE_CBS;
+ }
+ else
+ {
+ transport_instance->preferred_authentication_mode = AMQP_TRANSPORT_AUTHENTICATION_MODE_X509;
+ }
+ }
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_229: [IoTHubTransport_AMQP_Common_Register shall create an authentication state for the device using authentication_create() and store it on the device state.]
- if ((device_state->authentication = authentication_create(&auth_config)) == NULL)
- {
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_230: [If authentication_create() fails, IoTHubTransport_AMQP_Common_Register shall fail and return NULL.]
- LogError("IoTHubTransport_AMQP_Common_Register failed to create an authentication state for the device.");
- cleanup_required = true;
- }
- else
- {
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_234: [If the device is the first being registered on the transport, IoTHubTransport_AMQP_Common_Register shall save its authentication mode as the transport preferred authentication mode.]
- if (VECTOR_size(transport_state->registered_devices) == 1)
- {
- transport_state->preferred_credential_type = authentication_get_credential(device_state->authentication)->type;
- }
-
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_233: [IoTHubTransport_AMQP_Common_Register shall return its internal device representation as a IOTHUB_DEVICE_HANDLE.]
- result = (IOTHUB_DEVICE_HANDLE)device_state;
- cleanup_required = false;
- }
- }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_078: [IoTHubTransport_AMQP_Common_Register shall return a handle to `amqp_device_instance` as a IOTHUB_DEVICE_HANDLE]
+ result = (IOTHUB_DEVICE_HANDLE)amqp_device_instance;
+ }
+ }
}
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_233: [If IoTHubTransport_AMQP_Common_Register fails, it shall free all memory it alloacated (destroy deviceId, authentication state, targetAddress, messageReceiveAddress, devicesPath, device state).]
- if (cleanup_required)
+ if (result == NULL)
{
- if (device_state->deviceId != NULL)
- STRING_delete(device_state->deviceId);
- if (device_state->authentication != NULL)
- authentication_destroy(device_state->authentication);
- if (device_state->targetAddress != NULL)
- STRING_delete(device_state->targetAddress);
- if (device_state->messageReceiveAddress != NULL)
- STRING_delete(device_state->messageReceiveAddress);
- if (device_state->devicesPath != NULL)
- STRING_delete(device_state->devicesPath);
-
- free(device_state);
- }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_077: [If IoTHubTransport_AMQP_Common_Register fails, it shall free all memory it allocated]
+ internal_destroy_amqp_device_instance(amqp_device_instance);
+ }
}
}
}
@@ -1858,101 +1771,62 @@
void IoTHubTransport_AMQP_Common_Unregister(IOTHUB_DEVICE_HANDLE deviceHandle)
{
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_214: [IoTHubTransport_AMQP_Common_Unregister should fail and return if the IOTHUB_DEVICE_HANDLE parameter provided is NULL.]
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_079: [if `deviceHandle` provided is NULL, IoTHubTransport_AMQP_Common_Unregister shall return.]
if (deviceHandle == NULL)
{
- LogError("IoTHubTransport_AMQP_Common_Unregister failed (deviceHandle is NULL).");
+ LogError("Failed to unregister device (deviceHandle is NULL).");
}
else
{
- AMQP_TRANSPORT_DEVICE_STATE* device_state = (AMQP_TRANSPORT_DEVICE_STATE*)deviceHandle;
-
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_215: [IoTHubTransport_AMQP_Common_Unregister should fail and return if the IOTHUB_DEVICE_HANDLE parameter provided has a NULL reference to its transport instance.]
- if (device_state->transport_state == NULL)
- {
- LogError("IoTHubTransport_AMQP_Common_Unregister failed (deviceHandle does not have a transport state associated to).");
- }
- else
- {
- IOTHUB_DEVICE_HANDLE* registered_device_state;
-
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_216: [IoTHubTransport_AMQP_Common_Unregister should fail and return if the device is not registered with this transport.]
- if ((registered_device_state = VECTOR_find_if(device_state->transport_state->registered_devices, findDeviceById, STRING_c_str(device_state->deviceId))) == NULL)
- {
- LogError("IoTHubTransport_AMQP_Common_Unregister failed (device '%s' is not registered on this transport instance)", STRING_c_str(device_state->deviceId));
- }
- else
- {
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_024: [IoTHubTransport_AMQP_Common_Unregister shall destroy the AMQP message_sender.]
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_029: [IoTHubTransport_AMQP_Common_Unregister shall destroy the AMQP message_sender link.]
- destroyEventSender(device_state);
+ AMQP_TRANSPORT_DEVICE_INSTANCE* registered_device = (AMQP_TRANSPORT_DEVICE_INSTANCE*)deviceHandle;
+ const char* device_id;
+ LIST_ITEM_HANDLE list_item;
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_025: [IoTHubTransport_AMQP_Common_Unregister shall destroy the AMQP message_receiver.]
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_211: [IoTHubTransport_AMQP_Common_Unregister shall destroy the AMQP message_receiver link.]
- destroyMessageReceiver(device_state);
-
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_036: [IoTHubTransport_AMQP_Common_Unregister shall return the remaining items in inProgress to waitingToSend list.]
- rollEventsBackToWaitList(device_state);
+ if ((device_id = STRING_c_str(registered_device->device_id)) == NULL)
+ {
+ LogError("Failed to unregister device (failed to get device id char ptr)");
+ }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_080: [if `deviceHandle` has a NULL reference to its transport instance, IoTHubTransport_AMQP_Common_Unregister shall return.]
+ else if (registered_device->transport_instance == NULL)
+ {
+ LogError("Failed to unregister device '%s' (deviceHandle does not have a transport state associated to).", device_id);
+ }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_081: [If the device is not registered with this transport, IoTHubTransport_AMQP_Common_Unregister shall return]
+ else if (!is_device_registered_ex(registered_device->transport_instance->registered_devices, device_id, &list_item))
+ {
+ LogError("Failed to unregister device '%s' (device is not registered within this transport).", device_id);
+ }
+ else
+ {
+ // Removing it first so the race hazzard is reduced between this function and DoWork. Best would be to use locks.
+ if (singlylinkedlist_remove(registered_device->transport_instance->registered_devices, list_item) != RESULT_OK)
+ {
+ LogError("Failed to unregister device '%s' (singlylinkedlist_remove failed).", device_id);
+ }
+ else
+ {
+ // TODO: Q: should we go through waiting_to_send list and raise on_event_send_complete with BECAUSE_DESTROY ?
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_035: [IoTHubTransport_AMQP_Common_Unregister shall delete its internally-set parameters (targetAddress, messageReceiveAddress, devicesPath, deviceId).]
- STRING_delete(device_state->targetAddress);
- STRING_delete(device_state->messageReceiveAddress);
- STRING_delete(device_state->devicesPath);
- STRING_delete(device_state->deviceId);
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_217: [IoTHubTransport_AMQP_Common_Unregister shall destroy the authentication state of the device using authentication_destroy.]
- authentication_destroy(device_state->authentication);
-
-#ifdef WIP_C2D_METHODS_AMQP /* This feature is WIP, do not use yet */
- /* Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_01_012: [IoTHubTransport_AMQP_Common_Unregister shall destroy the C2D methods handler by calling iothubtransportamqp_methods_destroy.]*/
- iothubtransportamqp_methods_destroy(device_state->methods_handle);
-#endif
-
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_218: [IoTHubTransport_AMQP_Common_Unregister shall remove the device from its list of registered devices using VECTOR_erase().]
- VECTOR_erase(device_state->transport_state->registered_devices, registered_device_state, 1);
-
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_219: [IoTHubTransport_AMQP_Common_Unregister shall destroy the IOTHUB_DEVICE_HANDLE instance provided.]
- free(device_state);
- }
- }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_01_012: [IoTHubTransport_AMQP_Common_Unregister shall destroy the C2D methods handler by calling iothubtransportamqp_methods_destroy]
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_083: [IoTHubTransport_AMQP_Common_Unregister shall free all the memory allocated for the `device_instance`]
+ internal_destroy_amqp_device_instance(registered_device);
+ }
+ }
}
}
void IoTHubTransport_AMQP_Common_Destroy(TRANSPORT_LL_HANDLE handle)
{
- if (handle != NULL)
- {
- AMQP_TRANSPORT_INSTANCE* transport_state = (AMQP_TRANSPORT_INSTANCE*)handle;
-
- size_t numberOfRegisteredDevices = VECTOR_size(transport_state->registered_devices);
-
- for (size_t i = 0; i < numberOfRegisteredDevices; i++)
- {
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_209: [IoTHubTransport_AMQP_Common_Destroy shall invoke IoTHubTransport_AMQP_Common_Unregister on each of its registered devices.]
- IoTHubTransport_AMQP_Common_Unregister(*(AMQP_TRANSPORT_DEVICE_STATE**)VECTOR_element(transport_state->registered_devices, i));
- }
-
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_210: [IoTHubTransport_AMQP_Common_Destroy shall its list of registered devices using VECTOR_destroy().]
- VECTOR_destroy(transport_state->registered_devices);
-
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_027: [IoTHubTransport_AMQP_Common_Destroy shall destroy the AMQP cbs instance]
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_030: [IoTHubTransport_AMQP_Common_Destroy shall destroy the AMQP session.]
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_031: [IoTHubTransport_AMQP_Common_Destroy shall destroy the AMQP connection.]
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_032: [IoTHubTransport_AMQP_Common_Destroy shall destroy the AMQP SASL I / O transport.]
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_033: [IoTHubTransport_AMQP_Common_Destroy shall destroy the AMQP SASL mechanism.]
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_034: [IoTHubTransport_AMQP_Common_Destroy shall destroy the AMQP TLS I/O transport.]
- destroyConnection(transport_state);
-
- // CodeS_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_212: [IoTHubTransport_AMQP_Common_Destroy shall destroy the IoTHub FQDN value saved on the transport instance]
- STRING_delete(transport_state->iotHubHostFqdn);
-
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_213: [IoTHubTransport_AMQP_Common_Destroy shall destroy any TLS I/O options saved on the transport instance using OptionHandler_Destroy()]
- if (transport_state->xioOptions != NULL)
- {
- OptionHandler_Destroy(transport_state->xioOptions);
- }
-
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_150: [IoTHubTransport_AMQP_Common_Destroy shall destroy the transport instance]
- free(transport_state);
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_013: [If `handle` is NULL, IoTHubTransport_AMQP_Common_Destroy shall return immediatelly]
+ if (handle == NULL)
+ {
+ LogError("Failed to destroy AMQP transport instance (handle is NULL)");
+ }
+ else
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_014: [IoTHubTransport_AMQP_Common_Destroy shall invoke IoTHubTransport_AMQP_Common_Unregister on each of its registered devices.]
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_015: [All members of `instance` (including tls_io) shall be destroyed and its memory released]
+ internal_destroy_instance((AMQP_TRANSPORT_INSTANCE*)handle);
}
}
@@ -1972,16 +1846,103 @@
STRING_HANDLE IoTHubTransport_AMQP_Common_GetHostname(TRANSPORT_LL_HANDLE handle)
{
STRING_HANDLE result;
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_02_001: [If parameter handle is NULL then IoTHubTransport_AMQP_Common_GetHostname shall return NULL.]
- if (handle == NULL)
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_02_001: [If `handle` is NULL, `IoTHubTransport_AMQP_Common_GetHostname` shall return NULL.]
+ if (handle == NULL)
{
- result = NULL;
+ LogError("Cannot provide the target host name (transport handle is NULL).");
+
+ result = NULL;
+ }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_02_002: [IoTHubTransport_AMQP_Common_GetHostname shall return a copy of `instance->iothub_target_fqdn`.]
+ else if ((result = STRING_clone(((AMQP_TRANSPORT_INSTANCE*)(handle))->iothub_host_fqdn)) == NULL)
+ {
+ LogError("Cannot provide the target host name (STRING_clone failed).");
+ }
+
+ return result;
+}
+
+static DEVICE_MESSAGE_DISPOSITION_INFO* create_device_message_disposition_info_from(MESSAGE_CALLBACK_INFO* message_data)
+{
+ DEVICE_MESSAGE_DISPOSITION_INFO* result;
+
+ if ((result = (DEVICE_MESSAGE_DISPOSITION_INFO*)malloc(sizeof(DEVICE_MESSAGE_DISPOSITION_INFO))) == NULL)
+ {
+ LogError("Failed creating DEVICE_MESSAGE_DISPOSITION_INFO (malloc failed)");
+ }
+ else if (mallocAndStrcpy_s(&result->source, message_data->transportContext->link_name) != RESULT_OK)
+ {
+ LogError("Failed creating DEVICE_MESSAGE_DISPOSITION_INFO (mallocAndStrcpy_s failed)");
+ free(result);
+ result = NULL;
+ }
+ else
+ {
+ result->message_id = message_data->transportContext->message_id;
+ }
+
+ return result;
+}
+
+static void destroy_device_message_disposition_info(DEVICE_MESSAGE_DISPOSITION_INFO* device_message_disposition_info)
+{
+ free(device_message_disposition_info->source);
+ free(device_message_disposition_info);
+}
+
+IOTHUB_CLIENT_RESULT IoTHubTransport_AMQP_Common_SendMessageDisposition(MESSAGE_CALLBACK_INFO* message_data, IOTHUBMESSAGE_DISPOSITION_RESULT disposition)
+{
+ IOTHUB_CLIENT_RESULT result;
+ if (message_data == NULL)
+ {
+ /* Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_10_001: [If messageData is NULL, IoTHubTransport_AMQP_Common_SendMessageDisposition shall fail and return IOTHUB_CLIENT_INVALID_ARG.] */
+ LogError("Failed sending message disposition (message_data is NULL)");
+ result = IOTHUB_CLIENT_INVALID_ARG;
}
else
{
- // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_02_002: [Otherwise IoTHubTransport_AMQP_Common_GetHostname shall return the target IoT Hub FQDN as a STRING_HANDLE.]
- result = ((AMQP_TRANSPORT_INSTANCE*)(handle))->iotHubHostFqdn;
+ /* Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_10_002: [If any of the messageData fields are NULL, IoTHubTransport_AMQP_Common_SendMessageDisposition shall fail and return IOTHUB_CLIENT_INVALID_ARG.] */
+ if (message_data->messageHandle == NULL || message_data->transportContext == NULL)
+ {
+ LogError("Failed sending message disposition (message_data->messageHandle (%p) or message_data->transportContext (%p) are NULL)", message_data->messageHandle, message_data->transportContext);
+ result = IOTHUB_CLIENT_INVALID_ARG;
+ }
+ else
+ {
+ DEVICE_MESSAGE_DISPOSITION_INFO* device_message_disposition_info;
+
+ /* Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_10_004: [IoTHubTransport_AMQP_Common_SendMessageDisposition shall convert the given IOTHUBMESSAGE_DISPOSITION_RESULT to the equivalent AMQP_VALUE and will return the result of calling messagereceiver_send_message_disposition. ] */
+ DEVICE_MESSAGE_DISPOSITION_RESULT device_disposition_result = get_device_disposition_result_from(disposition);
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_112: [A DEVICE_MESSAGE_DISPOSITION_INFO instance shall be created with a copy of the `link_name` and `message_id` contained in `message_data`]
+ if ((device_message_disposition_info = create_device_message_disposition_info_from(message_data)) == NULL)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_113: [If the DEVICE_MESSAGE_DISPOSITION_INFO fails to be created, `IoTHubTransport_AMQP_Common_SendMessageDisposition()` shall fail and return IOTHUB_CLIENT_ERROR]
+ LogError("Device '%s' failed sending message disposition (failed creating DEVICE_MESSAGE_DISPOSITION_RESULT)", STRING_c_str(message_data->transportContext->device_state->device_id));
+ result = IOTHUB_CLIENT_ERROR;
+ }
+ else
+ {
+ /* Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_10_003: [IoTHubTransport_AMQP_Common_SendMessageDisposition shall fail and return IOTHUB_CLIENT_ERROR if the POST message fails, otherwise return IOTHUB_CLIENT_OK.] */
+ if (device_send_message_disposition(message_data->transportContext->device_state->device_handle, device_message_disposition_info, device_disposition_result) != RESULT_OK)
+ {
+ LogError("Device '%s' failed sending message disposition (device_send_message_disposition failed)", STRING_c_str(message_data->transportContext->device_state->device_id));
+ result = IOTHUB_CLIENT_ERROR;
+ }
+ else
+ {
+ IoTHubMessage_Destroy(message_data->messageHandle);
+ MESSAGE_CALLBACK_INFO_Destroy(message_data);
+ result = IOTHUB_CLIENT_OK;
+ }
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_COMMON_09_114: [`IoTHubTransport_AMQP_Common_SendMessageDisposition()` shall destroy the DEVICE_MESSAGE_DISPOSITION_INFO instance]
+ destroy_device_message_disposition_info(device_message_disposition_info);
+ }
+ }
}
+
return result;
}
--- a/iothubtransport_amqp_common.h Fri Feb 24 14:00:00 2017 -0800
+++ b/iothubtransport_amqp_common.h Fri Mar 10 11:46:55 2017 -0800
@@ -5,14 +5,16 @@
#define IOTHUBTRANSPORTAMQP_COMMON_H
#include "azure_c_shared_utility/strings.h"
+#include "azure_c_shared_utility/umock_c_prod.h"
#include "iothub_transport_ll.h"
-#include "azure_c_shared_utility/umock_c_prod.h"
#ifdef __cplusplus
extern "C"
{
#endif
+static const char* OPTION_EVENT_SEND_TIMEOUT_SECS = "event_send_timeout_secs";
+
typedef XIO_HANDLE(*AMQP_GET_IO_TRANSPORT)(const char* target_fqdn);
MOCKABLE_FUNCTION(, TRANSPORT_LL_HANDLE, IoTHubTransport_AMQP_Common_Create, const IOTHUBTRANSPORT_CONFIG*, config, AMQP_GET_IO_TRANSPORT, get_io_transport);
@@ -32,6 +34,7 @@
MOCKABLE_FUNCTION(, IOTHUB_DEVICE_HANDLE, IoTHubTransport_AMQP_Common_Register, TRANSPORT_LL_HANDLE, handle, const IOTHUB_DEVICE_CONFIG*, device, IOTHUB_CLIENT_LL_HANDLE, iotHubClientHandle, PDLIST_ENTRY, waitingToSend);
MOCKABLE_FUNCTION(, void, IoTHubTransport_AMQP_Common_Unregister, IOTHUB_DEVICE_HANDLE, deviceHandle);
MOCKABLE_FUNCTION(, STRING_HANDLE, IoTHubTransport_AMQP_Common_GetHostname, TRANSPORT_LL_HANDLE, handle);
+MOCKABLE_FUNCTION(, IOTHUB_CLIENT_RESULT, IoTHubTransport_AMQP_Common_SendMessageDisposition, MESSAGE_CALLBACK_INFO*, message_data, IOTHUBMESSAGE_DISPOSITION_RESULT, disposition);
#ifdef __cplusplus
}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/iothubtransport_amqp_connection.c Fri Mar 10 11:46:55 2017 -0800
@@ -0,0 +1,525 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+#include <stdlib.h>
+#include <limits.h>
+#include "iothubtransport_amqp_connection.h"
+#include "azure_c_shared_utility/optimize_size.h"
+#include "azure_c_shared_utility/gballoc.h"
+#include "azure_c_shared_utility/strings.h"
+#include "azure_c_shared_utility/uniqueid.h"
+#include "azure_uamqp_c/sasl_mechanism.h"
+#include "azure_uamqp_c/saslclientio.h"
+#include "azure_uamqp_c/sasl_mssbcbs.h"
+#include "azure_uamqp_c/connection.h"
+
+#define RESULT_OK 0
+#define DEFAULT_CONNECTION_IDLE_TIMEOUT 240000
+#define DEFAULT_INCOMING_WINDOW_SIZE UINT_MAX
+#define DEFAULT_OUTGOING_WINDOW_SIZE 100
+#define SASL_IO_OPTION_LOG_TRACE "logtrace"
+#define DEFAULT_UNIQUE_ID_LENGTH 40
+
+typedef struct AMQP_CONNECTION_INSTANCE_TAG
+{
+ STRING_HANDLE iothub_fqdn;
+ XIO_HANDLE underlying_io_transport;
+ CBS_HANDLE cbs_handle;
+ CONNECTION_HANDLE connection_handle;
+ SESSION_HANDLE session_handle;
+ XIO_HANDLE sasl_io;
+ SASL_MECHANISM_HANDLE sasl_mechanism;
+ bool is_trace_on;
+ AMQP_CONNECTION_STATE current_state;
+ ON_AMQP_CONNECTION_STATE_CHANGED on_state_changed_callback;
+ const void* on_state_changed_context;
+} AMQP_CONNECTION_INSTANCE;
+
+static int create_sasl_components(AMQP_CONNECTION_INSTANCE* instance)
+{
+ int result;
+ SASL_MECHANISM_HANDLE sasl_mechanism;
+ XIO_HANDLE sasl_io;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_CONNECTION_09_012: [`instance->sasl_mechanism` shall be created using saslmechanism_create()]
+ if ((sasl_mechanism = saslmechanism_create(saslmssbcbs_get_interface(), NULL)) == NULL)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_CONNECTION_09_013: [If saslmechanism_create() fails, amqp_connection_create() shall fail and return NULL]
+ LogError("Failed creating the SASL mechanism (saslmechanism_create failed)");
+ result = __FAILURE__;
+ }
+ else
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_CONNECTION_09_014: [A SASLCLIENTIO_CONFIG shall be set with `instance->underlying_io_transport` and `instance->sasl_mechanism`]
+ SASLCLIENTIO_CONFIG sasl_client_config;
+ sasl_client_config.sasl_mechanism = sasl_mechanism;
+ sasl_client_config.underlying_io = instance->underlying_io_transport;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_CONNECTION_09_015: [`instance->sasl_io` shall be created using xio_create() passing saslclientio_get_interface_description() and the SASLCLIENTIO_CONFIG instance]
+ if ((sasl_io = xio_create(saslclientio_get_interface_description(), &sasl_client_config)) == NULL)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_CONNECTION_09_016: [If xio_create() fails, amqp_connection_create() shall fail and return NULL]
+ LogError("Failed creating the SASL I/O (xio_create failed)");
+ saslmechanism_destroy(sasl_mechanism);
+ result = __FAILURE__;
+ }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_CONNECTION_09_017: [The sasl_io "logtrace" option shall be set using xio_setoption(), passing `instance->is_trace_on`]
+ else if (xio_setoption(sasl_io, SASL_IO_OPTION_LOG_TRACE, (const void*)&instance->is_trace_on) != RESULT_OK)
+ {
+ LogError("Failed setting the SASL I/O logging trace option (xio_setoption failed)");
+ xio_destroy(sasl_io);
+ saslmechanism_destroy(sasl_mechanism);
+ result = __FAILURE__;
+ }
+ else
+ {
+ instance->sasl_mechanism = sasl_mechanism;
+ instance->sasl_io = sasl_io;
+ result = RESULT_OK;
+ }
+ }
+
+ return result;
+}
+
+static void update_state(AMQP_CONNECTION_INSTANCE* instance, AMQP_CONNECTION_STATE new_state)
+{
+ if (new_state != instance->current_state)
+ {
+ AMQP_CONNECTION_STATE previous_state = instance->current_state;
+ instance->current_state = new_state;
+
+ if (instance->on_state_changed_callback != NULL)
+ {
+ instance->on_state_changed_callback(instance->on_state_changed_context, previous_state, new_state);
+ }
+ }
+}
+
+static void on_connection_io_error(void* context)
+{
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_CONNECTION_09_022: [If the connection calls back with an I/O error, `instance->on_state_changed_callback` shall be invoked if set passing code AMQP_CONNECTION_STATE_ERROR and `instance->on_state_changed_context`]
+ update_state((AMQP_CONNECTION_INSTANCE*)context, AMQP_CONNECTION_STATE_ERROR);
+}
+
+static void on_connection_state_changed(void* context, CONNECTION_STATE new_connection_state, CONNECTION_STATE previous_connection_state)
+{
+ (void)previous_connection_state;
+
+ AMQP_CONNECTION_INSTANCE* instance = (AMQP_CONNECTION_INSTANCE*)context;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_CONNECTION_09_063: [If `on_connection_state_changed` is called back, `instance->on_state_changed_callback` shall be invoked, if defined]
+ if (instance->cbs_handle == NULL || instance->sasl_io == NULL)
+ {
+ // connection is using x509 authentication.
+ // At this point uamqp's connection only raises CONNECTION_STATE_START when using X509 auth.
+ // So that should be all we expect to consider the amqp_connection_handle opened.
+ if (new_connection_state == CONNECTION_STATE_START)
+ {
+ update_state(instance, AMQP_CONNECTION_STATE_OPENED);
+ }
+ }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_CONNECTION_09_064: [If `on_connection_state_changed` new state is CONNECTION_STATE_OPENED, `instance->on_state_changed_callback` shall be invoked with state AMQP_CONNECTION_STATE_OPENED]
+ else if (new_connection_state == CONNECTION_STATE_OPENED)
+ {
+ update_state(instance, AMQP_CONNECTION_STATE_OPENED);
+ }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_CONNECTION_09_065: [If `on_connection_state_changed` new state is CONNECTION_STATE_END, `instance->on_state_changed_callback` shall be invoked with state AMQP_CONNECTION_STATE_CLOSED]
+ else if (new_connection_state == CONNECTION_STATE_END)
+ {
+ update_state(instance, AMQP_CONNECTION_STATE_CLOSED);
+ }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_CONNECTION_09_071: [If `on_connection_state_changed` new state is CONNECTION_STATE_ERROR, `instance->on_state_changed_callback` shall be invoked with state AMQP_CONNECTION_STATE_ERROR]
+ else if (new_connection_state == CONNECTION_STATE_ERROR)
+ {
+ update_state(instance, AMQP_CONNECTION_STATE_ERROR);
+ }
+}
+
+static int create_connection_handle(AMQP_CONNECTION_INSTANCE* instance)
+{
+ int result;
+ char* unique_container_id = NULL;
+ XIO_HANDLE connection_io_transport;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_CONNECTION_09_007: [If `instance->sasl_io` is defined it shall be used as parameter `xio` in connection_create2()]
+ if (instance->sasl_io != NULL)
+ {
+ connection_io_transport = instance->sasl_io;
+ }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_CONNECTION_09_018: [If `instance->sasl_io` is not defined, `instance->underlying_io_transport` shall be used as parameter `xio` in connection_create2()]
+ else
+ {
+ connection_io_transport = instance->underlying_io_transport;
+ }
+
+ if ((unique_container_id = (char*)malloc(sizeof(char) * DEFAULT_UNIQUE_ID_LENGTH + 1)) == NULL)
+ {
+ result = __LINE__;
+ LogError("Failed creating the AMQP connection (failed creating unique ID container)");
+ }
+ else
+ {
+ memset(unique_container_id, 0, sizeof(char) * DEFAULT_UNIQUE_ID_LENGTH + 1);
+
+ if (UniqueId_Generate(unique_container_id, DEFAULT_UNIQUE_ID_LENGTH) != UNIQUEID_OK)
+ {
+ result = __FAILURE__;
+ LogError("Failed creating the AMQP connection (UniqueId_Generate failed)");
+ }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_CONNECTION_09_019: [`instance->connection_handle` shall be created using connection_create2(), passing the `connection_underlying_io`, `instance->iothub_host_fqdn` and an unique string as container ID]
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_CONNECTION_09_020: [connection_create2() shall also receive `on_connection_state_changed` and `on_connection_error` callback functions]
+ else if ((instance->connection_handle = connection_create2(connection_io_transport, STRING_c_str(instance->iothub_fqdn), unique_container_id, NULL, NULL, on_connection_state_changed, (void*)instance, on_connection_io_error, (void*)instance)) == NULL)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_CONNECTION_09_021: [If connection_create2() fails, amqp_connection_create() shall fail and return NULL]
+ result = __FAILURE__;
+ LogError("Failed creating the AMQP connection (connection_create2 failed)");
+ }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_CONNECTION_09_073: [The connection idle timeout parameter shall be set to 240000 milliseconds using connection_set_idle_timeout()]
+ else if (connection_set_idle_timeout(instance->connection_handle, DEFAULT_CONNECTION_IDLE_TIMEOUT) != RESULT_OK)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_CONNECTION_09_074: [If connection_set_idle_timeout() fails, amqp_connection_create() shall fail and return NULL]
+ result = __FAILURE__;
+ LogError("Failed creating the AMQP connection (connection_set_idle_timeout failed)");
+ }
+ else
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_CONNECTION_09_023: [The connection tracing shall be set using connection_set_trace(), passing `instance->is_trace_on`]
+ connection_set_trace(instance->connection_handle, instance->is_trace_on);
+
+ result = RESULT_OK;
+ }
+ }
+
+ if (unique_container_id != NULL)
+ {
+ free(unique_container_id);
+ }
+
+ return result;
+}
+
+static int create_session_handle(AMQP_CONNECTION_INSTANCE* instance)
+{
+ int result;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_CONNECTION_09_024: [`instance->session_handle` shall be created using session_create(), passing `instance->connection_handle`]
+ if ((instance->session_handle = session_create(instance->connection_handle, NULL, NULL)) == NULL)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_CONNECTION_09_025: [If session_create() fails, amqp_connection_create() shall fail and return NULL]
+ result = __FAILURE__;
+ LogError("Failed creating the AMQP connection (connection_create2 failed)");
+ }
+ else
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_CONNECTION_09_026: [The `instance->session_handle` incoming window size shall be set as UINT_MAX using session_set_incoming_window()]
+ if (session_set_incoming_window(instance->session_handle, (uint32_t)DEFAULT_INCOMING_WINDOW_SIZE) != 0)
+ {
+ LogError("Failed to set the AMQP session incoming window size.");
+ }
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_CONNECTION_09_027: [The `instance->session_handle` outgoing window size shall be set as 100 using session_set_outgoing_window()]
+ if (session_set_outgoing_window(instance->session_handle, DEFAULT_OUTGOING_WINDOW_SIZE) != 0)
+ {
+ LogError("Failed to set the AMQP session outgoing window size.");
+ }
+
+ result = RESULT_OK;
+ }
+
+ return result;
+}
+
+static int create_cbs_handle(AMQP_CONNECTION_INSTANCE* instance)
+{
+ int result;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_CONNECTION_09_029: [`instance->cbs_handle` shall be created using cbs_create(), passing `instance->session_handle`]
+ if ((instance->cbs_handle = cbs_create(instance->session_handle, NULL, (void*)instance)) == NULL)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_CONNECTION_09_030: [If cbs_create() fails, amqp_connection_create() shall fail and return NULL]
+ result = __FAILURE__;
+ LogError("Failed to create the CBS connection.");
+ }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_CONNECTION_09_031: [`instance->cbs_handle` shall be opened using cbs_open()]
+ else if (cbs_open(instance->cbs_handle) != RESULT_OK)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_CONNECTION_09_032: [If cbs_open() fails, amqp_connection_create() shall fail and return NULL]
+ result = __FAILURE__;
+ LogError("Failed to open the connection with CBS.");
+ }
+ else
+ {
+ result = RESULT_OK;
+ }
+
+ return result;
+}
+
+
+// Public APIS:
+
+void amqp_connection_destroy(AMQP_CONNECTION_HANDLE conn_handle)
+{
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_CONNECTION_09_035: [If `conn_handle` is NULL, amqp_connection_destroy() shall fail and return]
+ if (conn_handle != NULL)
+ {
+ AMQP_CONNECTION_INSTANCE* instance = (AMQP_CONNECTION_INSTANCE*)conn_handle;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_CONNECTION_09_036: [amqp_connection_destroy() shall destroy `instance->cbs_handle` if set using cbs_destroy()]
+ if (instance->cbs_handle != NULL)
+ {
+ cbs_destroy(instance->cbs_handle);
+ }
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_CONNECTION_09_037: [amqp_connection_destroy() shall destroy `instance->session_handle` if set using session_destroy()]
+ if (instance->session_handle != NULL)
+ {
+ session_destroy(instance->session_handle);
+ }
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_CONNECTION_09_067: [amqp_connection_destroy() shall destroy `instance->connection_handle` if set using connection_destroy()]
+ if (instance->connection_handle != NULL)
+ {
+ connection_destroy(instance->connection_handle);
+ }
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_CONNECTION_09_038: [amqp_connection_destroy() shall destroy `instance->sasl_io` if set using xio_destroy()]
+ if (instance->sasl_io != NULL)
+ {
+ xio_destroy(instance->sasl_io);
+ }
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_CONNECTION_09_039: [amqp_connection_destroy() shall destroy `instance->sasl_mechanism` if set using saslmechanism_destroy()]
+ if (instance->sasl_mechanism != NULL)
+ {
+ saslmechanism_destroy(instance->sasl_mechanism);
+ }
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_CONNECTION_09_059: [amqp_connection_destroy() shall destroy `instance->iothub_host_fqdn` if set using STRING_delete()]
+ if (instance->iothub_fqdn != NULL)
+ {
+ STRING_delete(instance->iothub_fqdn);
+ }
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_CONNECTION_09_040: [amqp_connection_destroy() shall free the memory allocated for the connection instance]
+ free(instance);
+ }
+}
+
+AMQP_CONNECTION_HANDLE amqp_connection_create(AMQP_CONNECTION_CONFIG* config)
+{
+ AMQP_CONNECTION_HANDLE result;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_CONNECTION_09_001: [If `config` is NULL, amqp_connection_create() shall fail and return NULL]
+ if (config == NULL)
+ {
+ result = NULL;
+ LogError("amqp_connection_create failed (config is NULL)");
+ }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_CONNECTION_09_002: [If `config->iothub_host_fqdn` is NULL, amqp_connection_create() shall fail and return NULL]
+ else if (config->iothub_host_fqdn == NULL)
+ {
+ result = NULL;
+ LogError("amqp_connection_create failed (config->iothub_host_fqdn is NULL)");
+ }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_CONNECTION_09_003: [If `config->underlying_io_transport` is NULL, amqp_connection_create() shall fail and return NULL]
+ else if (config->underlying_io_transport == NULL)
+ {
+ result = NULL;
+ LogError("amqp_connection_create failed (config->underlying_io_transport is NULL)");
+ }
+ else
+ {
+ AMQP_CONNECTION_INSTANCE* instance;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_CONNECTION_09_057: [amqp_connection_create() shall allocate memory for an instance of the connection state]
+ if ((instance = (AMQP_CONNECTION_INSTANCE*)malloc(sizeof(AMQP_CONNECTION_INSTANCE))) == NULL)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_CONNECTION_09_058: [If malloc() fails, amqp_connection_create() shall fail and return NULL]
+ result = NULL;
+ LogError("amqp_connection_create failed (malloc failed)");
+ }
+ else
+ {
+ memset(instance, 0, sizeof(AMQP_CONNECTION_INSTANCE));
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_CONNECTION_09_005: [A copy of `config->iothub_host_fqdn` shall be saved on `instance->iothub_host_fqdn`]
+ if ((instance->iothub_fqdn = STRING_construct(config->iothub_host_fqdn)) == NULL)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_CONNECTION_09_066: [If STRING_construct() fails, amqp_connection_create() shall fail and return NULL]
+ result = NULL;
+ LogError("amqp_connection_create failed (STRING_construct failed)");
+ }
+ else
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_CONNECTION_09_006: [`config->underlying_io_transport` shall be saved on `instance->underlying_io_transport`]
+ instance->underlying_io_transport = config->underlying_io_transport;
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_CONNECTION_09_008: [`config->is_trace_on` shall be saved on `instance->is_trace_on`]
+ instance->is_trace_on = config->is_trace_on;
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_CONNECTION_09_060: [`config->on_state_changed_callback` shall be saved on `instance->on_state_changed_callback`]
+ instance->on_state_changed_callback = config->on_state_changed_callback;
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_CONNECTION_09_061: [`config->on_state_changed_context` shall be saved on `instance->on_state_changed_context`]
+ instance->on_state_changed_context = config->on_state_changed_context;
+
+ instance->current_state = AMQP_CONNECTION_STATE_CLOSED;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_CONNECTION_09_011: [If `config->create_sasl_io` is true or `config->create_cbs_connection` is true, amqp_connection_create() shall create SASL I/O]
+ if ((config->create_sasl_io || config->create_cbs_connection) && create_sasl_components(instance) != RESULT_OK)
+ {
+ result = NULL;
+ LogError("amqp_connection_create failed (failed creating the SASL components)");
+ }
+ else if (create_connection_handle(instance) != RESULT_OK)
+ {
+ result = NULL;
+ LogError("amqp_connection_create failed (failed creating the AMQP connection)");
+ }
+ else if (create_session_handle(instance) != RESULT_OK)
+ {
+ result = NULL;
+ LogError("amqp_connection_create failed (failed creating the AMQP session)");
+ }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_CONNECTION_09_028: [Only if `config->create_cbs_connection` is true, amqp_connection_create() shall create and open the CBS_HANDLE]
+ else if (config->create_cbs_connection && create_cbs_handle(instance) != RESULT_OK)
+ {
+ result = NULL;
+ LogError("amqp_connection_create failed (failed creating the CBS handle)");
+ }
+ else
+ {
+
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_CONNECTION_09_034: [If no failures occur, amqp_connection_create() shall return the handle to the connection state]
+ result = (AMQP_CONNECTION_HANDLE)instance;
+ }
+ }
+
+ if (result == NULL)
+ {
+ amqp_connection_destroy((AMQP_CONNECTION_HANDLE)instance);
+ }
+ }
+ }
+
+ return result;
+}
+
+void amqp_connection_do_work(AMQP_CONNECTION_HANDLE conn_handle)
+{
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_CONNECTION_09_041: [If `conn_handle` is NULL, amqp_connection_do_work() shall fail and return]
+ if (conn_handle != NULL)
+ {
+ AMQP_CONNECTION_INSTANCE* instance = (AMQP_CONNECTION_INSTANCE*)conn_handle;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_CONNECTION_09_042: [connection_dowork() shall be invoked passing `instance->connection_handle`]
+ connection_dowork(instance->connection_handle);
+ }
+}
+
+int amqp_connection_get_session_handle(AMQP_CONNECTION_HANDLE conn_handle, SESSION_HANDLE* session_handle)
+{
+ int result;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_CONNECTION_09_043: [If `conn_handle` is NULL, amqp_connection_get_session_handle() shall fail and return __FAILURE__]
+ if (conn_handle == NULL)
+ {
+ result = __FAILURE__;
+ LogError("amqp_connection_get_session_handle failed (conn_handle is NULL)");
+ }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_CONNECTION_09_044: [If `session_handle` is NULL, amqp_connection_get_session_handle() shall fail and return __FAILURE__]
+ else if (session_handle == NULL)
+ {
+ result = __FAILURE__;
+ LogError("amqp_connection_get_session_handle failed (session_handle is NULL)");
+ }
+ else
+ {
+ AMQP_CONNECTION_INSTANCE* instance = (AMQP_CONNECTION_INSTANCE*)conn_handle;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_CONNECTION_09_045: [`session_handle` shall be set to point to `instance->session_handle`]
+ *session_handle = instance->session_handle;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_CONNECTION_09_046: [amqp_connection_get_session_handle() shall return success code 0]
+ result = RESULT_OK;
+ }
+
+ return result;
+}
+
+int amqp_connection_get_cbs_handle(AMQP_CONNECTION_HANDLE conn_handle, CBS_HANDLE* cbs_handle)
+{
+ int result;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_CONNECTION_09_047: [If `conn_handle` is NULL, amqp_connection_get_cbs_handle() shall fail and return __FAILURE__]
+ if (conn_handle == NULL)
+ {
+ result = __FAILURE__;
+ LogError("amqp_connection_get_cbs_handle failed (conn_handle is NULL)");
+ }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_CONNECTION_09_048: [If `cbs_handle` is NULL, amqp_connection_get_cbs_handle() shall fail and return __FAILURE__]
+ else if (cbs_handle == NULL)
+ {
+ result = __FAILURE__;
+ LogError("amqp_connection_get_cbs_handle failed (parameter cbs_handle is NULL)");
+ }
+ else
+ {
+ AMQP_CONNECTION_INSTANCE* instance = (AMQP_CONNECTION_INSTANCE*)conn_handle;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_CONNECTION_09_049: [If `instance->cbs_handle` is NULL, amqp_connection_get_cbs_handle() shall fail and return __FAILURE__]
+ if (instance->cbs_handle == NULL)
+ {
+ result = __FAILURE__;
+ LogError("amqp_connection_get_cbs_handle failed (there is not a cbs_handle to be returned)");
+ }
+ else
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_CONNECTION_09_050: [`cbs_handle` shall be set to point to `instance->cbs_handle`]
+ *cbs_handle = instance->cbs_handle;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_CONNECTION_09_051: [amqp_connection_get_cbs_handle() shall return success code 0]
+ result = RESULT_OK;
+ }
+ }
+
+ return result;
+}
+
+int amqp_connection_set_logging(AMQP_CONNECTION_HANDLE conn_handle, bool is_trace_on)
+{
+ int result;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_CONNECTION_09_052: [If `conn_handle` is NULL, amqp_connection_set_logging() shall fail and return __FAILURE__]
+ if (conn_handle == NULL)
+ {
+ result = __FAILURE__;
+ LogError("amqp_connection_set_logging failed (conn_handle is NULL)");
+ }
+ else
+ {
+ AMQP_CONNECTION_INSTANCE* instance = (AMQP_CONNECTION_INSTANCE*)conn_handle;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_CONNECTION_09_053: [`instance->is_trace_on` shall be set to `is_trace_on`]
+ instance->is_trace_on = is_trace_on;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_CONNECTION_09_054: [Tracing on `instance->sasl_io` shall be set to `instance->is_trace_on` if the value has changed]
+ if (instance->sasl_io != NULL &&
+ xio_setoption(instance->sasl_io, SASL_IO_OPTION_LOG_TRACE, (const void*)&instance->is_trace_on) != RESULT_OK)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_CONNECTION_09_072: [If xio_setoption() fails, amqp_connection_set_logging() shall fail and return __FAILURE__]
+ result = __FAILURE__;
+ LogError("amqp_connection_set_logging failed (xio_setoption() failed)");
+ }
+ else
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_CONNECTION_09_055: [Tracing on `instance->connection_handle` shall be set to `instance->is_trace_on` if the value has changed]
+ connection_set_trace(instance->connection_handle, instance->is_trace_on);
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_CONNECTION_09_056: [amqp_connection_set_logging() shall return success code 0]
+ result = RESULT_OK;
+ }
+ }
+
+ return result;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/iothubtransport_amqp_connection.h Fri Mar 10 11:46:55 2017 -0800
@@ -0,0 +1,51 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+#ifndef IOTHUBTRANSPORTAMQP_AMQP_CONNECTION_H
+#define IOTHUBTRANSPORTAMQP_AMQP_CONNECTION_H
+
+#include "azure_c_shared_utility/umock_c_prod.h"
+#include "azure_c_shared_utility/xio.h"
+#include "azure_uamqp_c/session.h"
+#include "azure_uamqp_c/cbs.h"
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+typedef enum AMQP_CONNECTION_STATE_TAG
+{
+ AMQP_CONNECTION_STATE_OPENED,
+ AMQP_CONNECTION_STATE_CLOSED,
+ AMQP_CONNECTION_STATE_ERROR
+} AMQP_CONNECTION_STATE;
+
+typedef void(*ON_AMQP_CONNECTION_STATE_CHANGED)(const void* context, AMQP_CONNECTION_STATE old_state, AMQP_CONNECTION_STATE new_state);
+
+typedef struct AMQP_CONNECTION_CONFIG_TAG
+{
+ const char* iothub_host_fqdn;
+ XIO_HANDLE underlying_io_transport;
+ bool create_sasl_io;
+ bool create_cbs_connection;
+ bool is_trace_on;
+
+ ON_AMQP_CONNECTION_STATE_CHANGED on_state_changed_callback;
+ const void* on_state_changed_context;
+} AMQP_CONNECTION_CONFIG;
+
+typedef struct AMQP_CONNECTION_INSTANCE* AMQP_CONNECTION_HANDLE;
+
+MOCKABLE_FUNCTION(, AMQP_CONNECTION_HANDLE, amqp_connection_create, AMQP_CONNECTION_CONFIG*, config);
+MOCKABLE_FUNCTION(, void, amqp_connection_destroy, AMQP_CONNECTION_HANDLE, conn_handle);
+MOCKABLE_FUNCTION(, void, amqp_connection_do_work, AMQP_CONNECTION_HANDLE, conn_handle);
+MOCKABLE_FUNCTION(, int, amqp_connection_get_session_handle, AMQP_CONNECTION_HANDLE, conn_handle, SESSION_HANDLE*, session_handle);
+MOCKABLE_FUNCTION(, int, amqp_connection_get_cbs_handle, AMQP_CONNECTION_HANDLE, conn_handle, CBS_HANDLE*, cbs_handle);
+MOCKABLE_FUNCTION(, int, amqp_connection_set_logging, AMQP_CONNECTION_HANDLE, conn_handle, bool, is_trace_on);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /*IOTHUBTRANSPORTAMQP_AMQP_CONNECTION_H*/
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/iothubtransport_amqp_device.c Fri Mar 10 11:46:55 2017 -0800
@@ -0,0 +1,1351 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+#include <stdlib.h>
+#include "iothubtransport_amqp_messenger.h"
+#include "azure_c_shared_utility/optimize_size.h"
+#include "azure_c_shared_utility/gballoc.h"
+#include "azure_c_shared_utility/agenttime.h"
+#include "azure_c_shared_utility/xlogging.h"
+#include "azure_c_shared_utility/strings.h"
+#include "iothubtransport_amqp_cbs_auth.h"
+#include "iothubtransport_amqp_device.h"
+
+#define RESULT_OK 0
+#define INDEFINITE_TIME ((time_t)-1)
+#define DEFAULT_AUTH_STATE_CHANGED_TIMEOUT_SECS 60
+#define DEFAULT_MSGR_STATE_CHANGED_TIMEOUT_SECS 60
+
+static const char* DEVICE_OPTION_SAVED_AUTH_OPTIONS = "saved_device_auth_options";
+static const char* DEVICE_OPTION_SAVED_MESSENGER_OPTIONS = "saved_device_messenger_options";
+
+typedef struct DEVICE_INSTANCE_TAG
+{
+ DEVICE_CONFIG* config;
+ DEVICE_STATE state;
+
+ SESSION_HANDLE session_handle;
+ CBS_HANDLE cbs_handle;
+
+ AUTHENTICATION_HANDLE authentication_handle;
+ AUTHENTICATION_STATE auth_state;
+ AUTHENTICATION_ERROR_CODE auth_error_code;
+ time_t auth_state_last_changed_time;
+ size_t auth_state_change_timeout_secs;
+
+ MESSENGER_HANDLE messenger_handle;
+ MESSENGER_STATE msgr_state;
+ time_t msgr_state_last_changed_time;
+ size_t msgr_state_change_timeout_secs;
+
+ ON_DEVICE_C2D_MESSAGE_RECEIVED on_message_received_callback;
+ void* on_message_received_context;
+} DEVICE_INSTANCE;
+
+typedef struct DEVICE_SEND_EVENT_TASK_TAG
+{
+ ON_DEVICE_D2C_EVENT_SEND_COMPLETE on_event_send_complete_callback;
+ void* on_event_send_complete_context;
+} DEVICE_SEND_EVENT_TASK;
+
+
+// Internal state control
+
+static void update_state(DEVICE_INSTANCE* instance, DEVICE_STATE new_state)
+{
+ if (new_state != instance->state)
+ {
+ DEVICE_STATE previous_state = instance->state;
+ instance->state = new_state;
+
+ if (instance->config->on_state_changed_callback != NULL)
+ {
+ instance->config->on_state_changed_callback(instance->config->on_state_changed_context, previous_state, new_state);
+ }
+ }
+}
+
+static int is_timeout_reached(time_t start_time, size_t timeout_in_secs, int *is_timed_out)
+{
+ int result;
+
+ if (start_time == INDEFINITE_TIME)
+ {
+ LogError("Failed to verify timeout (start_time is INDEFINITE)");
+ result = __FAILURE__;
+ }
+ else
+ {
+ time_t current_time;
+
+ if ((current_time = get_time(NULL)) == INDEFINITE_TIME)
+ {
+ LogError("Failed to verify timeout (get_time failed)");
+ result = __FAILURE__;
+ }
+ else
+ {
+ if (get_difftime(current_time, start_time) >= timeout_in_secs)
+ {
+ *is_timed_out = 1;
+ }
+ else
+ {
+ *is_timed_out = 0;
+ }
+
+ result = RESULT_OK;
+ }
+ }
+
+ return result;
+}
+
+
+// Callback Handlers
+
+static D2C_EVENT_SEND_RESULT get_d2c_event_send_result_from(MESSENGER_EVENT_SEND_COMPLETE_RESULT result)
+{
+ D2C_EVENT_SEND_RESULT d2c_esr;
+
+ switch (result)
+ {
+ case MESSENGER_EVENT_SEND_COMPLETE_RESULT_OK:
+ d2c_esr = D2C_EVENT_SEND_COMPLETE_RESULT_OK;
+ break;
+ case MESSENGER_EVENT_SEND_COMPLETE_RESULT_ERROR_CANNOT_PARSE:
+ d2c_esr = D2C_EVENT_SEND_COMPLETE_RESULT_ERROR_CANNOT_PARSE;
+ break;
+ case MESSENGER_EVENT_SEND_COMPLETE_RESULT_ERROR_FAIL_SENDING:
+ d2c_esr = D2C_EVENT_SEND_COMPLETE_RESULT_ERROR_FAIL_SENDING;
+ break;
+ case MESSENGER_EVENT_SEND_COMPLETE_RESULT_ERROR_TIMEOUT:
+ d2c_esr = D2C_EVENT_SEND_COMPLETE_RESULT_ERROR_TIMEOUT;
+ break;
+ case MESSENGER_EVENT_SEND_COMPLETE_RESULT_MESSENGER_DESTROYED:
+ d2c_esr = D2C_EVENT_SEND_COMPLETE_RESULT_DEVICE_DESTROYED;
+ break;
+ default:
+ // This is not expected. All states should be mapped.
+ d2c_esr = D2C_EVENT_SEND_COMPLETE_RESULT_ERROR_UNKNOWN;
+ break;
+ };
+
+ return d2c_esr;
+}
+
+static void on_event_send_complete_messenger_callback(IOTHUB_MESSAGE_LIST* iothub_message, MESSENGER_EVENT_SEND_COMPLETE_RESULT ev_send_comp_result, void* context)
+{
+ if (iothub_message == NULL || context == NULL)
+ {
+ LogError("on_event_send_complete_messenger_callback was invoked, but either iothub_message (%p) or context (%p) are NULL", iothub_message, context);
+ }
+ else
+ {
+ DEVICE_SEND_EVENT_TASK* send_task = (DEVICE_SEND_EVENT_TASK*)context;
+
+ // Codes_SRS_DEVICE_09_059: [If `ev_send_comp_result` is MESSENGER_EVENT_SEND_COMPLETE_RESULT_OK, D2C_EVENT_SEND_COMPLETE_RESULT_OK shall be reported as `event_send_complete`]
+ // Codes_SRS_DEVICE_09_060: [If `ev_send_comp_result` is MESSENGER_EVENT_SEND_COMPLETE_RESULT_ERROR_CANNOT_PARSE, D2C_EVENT_SEND_COMPLETE_RESULT_ERROR_CANNOT_PARSE shall be reported as `event_send_complete`]
+ // Codes_SRS_DEVICE_09_061: [If `ev_send_comp_result` is MESSENGER_EVENT_SEND_COMPLETE_RESULT_ERROR_FAIL_SENDING, D2C_EVENT_SEND_COMPLETE_RESULT_ERROR_FAIL_SENDING shall be reported as `event_send_complete`]
+ // Codes_SRS_DEVICE_09_062: [If `ev_send_comp_result` is MESSENGER_EVENT_SEND_COMPLETE_RESULT_ERROR_TIMEOUT, D2C_EVENT_SEND_COMPLETE_RESULT_ERROR_TIMEOUT shall be reported as `event_send_complete`]
+ // Codes_SRS_DEVICE_09_063: [If `ev_send_comp_result` is MESSENGER_EVENT_SEND_COMPLETE_RESULT_MESSENGER_DESTROYED, D2C_EVENT_SEND_COMPLETE_RESULT_DEVICE_DESTROYED shall be reported as `event_send_complete`]
+ D2C_EVENT_SEND_RESULT device_send_result = get_d2c_event_send_result_from(ev_send_comp_result);
+
+ // Codes_SRS_DEVICE_09_064: [If provided, the user callback and context saved in `send_task` shall be invoked passing the device `event_send_complete`]
+ if (send_task->on_event_send_complete_callback != NULL)
+ {
+ send_task->on_event_send_complete_callback(iothub_message, device_send_result, send_task->on_event_send_complete_context);
+ }
+
+ // Codes_SRS_DEVICE_09_065: [The memory allocated for `send_task` shall be released]
+ free(send_task);
+ }
+}
+
+static void on_authentication_error_callback(void* context, AUTHENTICATION_ERROR_CODE error_code)
+{
+ if (context == NULL)
+ {
+ LogError("on_authentication_error_callback was invoked with error %d, but context is NULL", error_code);
+ }
+ else
+ {
+ DEVICE_INSTANCE* instance = (DEVICE_INSTANCE*)context;
+ instance->auth_error_code = error_code;
+ }
+}
+
+static void on_authentication_state_changed_callback(void* context, AUTHENTICATION_STATE previous_state, AUTHENTICATION_STATE new_state)
+{
+ if (context == NULL)
+ {
+ LogError("on_authentication_state_changed_callback was invoked with new_state %d, but context is NULL", new_state);
+ }
+ else if (new_state != previous_state)
+ {
+ DEVICE_INSTANCE* instance = (DEVICE_INSTANCE*)context;
+ instance->auth_state = new_state;
+
+ if ((instance->auth_state_last_changed_time = get_time(NULL)) == INDEFINITE_TIME)
+ {
+ LogError("Device '%s' failed to set time of last authentication state change (get_time failed)", instance->config->device_id);
+ }
+ }
+}
+
+static void on_messenger_state_changed_callback(void* context, MESSENGER_STATE previous_state, MESSENGER_STATE new_state)
+{
+ if (context == NULL)
+ {
+ LogError("on_messenger_state_changed_callback was invoked with new_state %d, but context is NULL", new_state);
+ }
+ else if (new_state != previous_state)
+ {
+ DEVICE_INSTANCE* instance = (DEVICE_INSTANCE*)context;
+ instance->msgr_state = new_state;
+
+ if ((instance->msgr_state_last_changed_time = get_time(NULL)) == INDEFINITE_TIME)
+ {
+ LogError("Device '%s' failed to set time of last messenger state change (get_time failed)", instance->config->device_id);
+ }
+ }
+}
+
+static DEVICE_MESSAGE_DISPOSITION_INFO* create_device_message_disposition_info_from(MESSENGER_MESSAGE_DISPOSITION_INFO* messenger_disposition_info)
+{
+ DEVICE_MESSAGE_DISPOSITION_INFO* device_disposition_info;
+
+ if ((device_disposition_info = (DEVICE_MESSAGE_DISPOSITION_INFO*)malloc(sizeof(DEVICE_MESSAGE_DISPOSITION_INFO))) == NULL)
+ {
+ LogError("Failed creating DEVICE_MESSAGE_DISPOSITION_INFO (malloc failed)");
+ }
+ else if (mallocAndStrcpy_s(&device_disposition_info->source, messenger_disposition_info->source) != RESULT_OK)
+ {
+ LogError("Failed creating DEVICE_MESSAGE_DISPOSITION_INFO (mallocAndStrcpy_s failed)");
+ free(device_disposition_info);
+ device_disposition_info = NULL;
+ }
+ else
+ {
+ device_disposition_info->message_id = messenger_disposition_info->message_id;
+ }
+
+ return device_disposition_info;
+}
+
+static void destroy_device_disposition_info(DEVICE_MESSAGE_DISPOSITION_INFO* disposition_info)
+{
+ free(disposition_info->source);
+ free(disposition_info);
+}
+
+static MESSENGER_MESSAGE_DISPOSITION_INFO* create_messenger_disposition_info(DEVICE_MESSAGE_DISPOSITION_INFO* device_disposition_info)
+{
+ MESSENGER_MESSAGE_DISPOSITION_INFO* messenger_disposition_info;
+
+ if ((messenger_disposition_info = (MESSENGER_MESSAGE_DISPOSITION_INFO*)malloc(sizeof(MESSENGER_MESSAGE_DISPOSITION_INFO))) == NULL)
+ {
+ LogError("Failed creating MESSENGER_MESSAGE_DISPOSITION_INFO (malloc failed)");
+ }
+ else if (mallocAndStrcpy_s(&messenger_disposition_info->source, device_disposition_info->source) != RESULT_OK)
+ {
+ LogError("Failed creating MESSENGER_MESSAGE_DISPOSITION_INFO (mallocAndStrcpy_s failed)");
+ free(messenger_disposition_info);
+ messenger_disposition_info = NULL;
+ }
+ else
+ {
+ messenger_disposition_info->message_id = device_disposition_info->message_id;
+ }
+
+ return messenger_disposition_info;
+}
+
+static void destroy_messenger_disposition_info(MESSENGER_MESSAGE_DISPOSITION_INFO* messenger_disposition_info)
+{
+ free(messenger_disposition_info->source);
+ free(messenger_disposition_info);
+}
+
+static MESSENGER_DISPOSITION_RESULT get_messenger_message_disposition_result_from(DEVICE_MESSAGE_DISPOSITION_RESULT device_disposition_result)
+{
+ MESSENGER_DISPOSITION_RESULT messenger_disposition_result;
+
+ switch (device_disposition_result)
+ {
+ case DEVICE_MESSAGE_DISPOSITION_RESULT_NONE:
+ messenger_disposition_result = MESSENGER_DISPOSITION_RESULT_NONE;
+ break;
+ case DEVICE_MESSAGE_DISPOSITION_RESULT_ACCEPTED:
+ messenger_disposition_result = MESSENGER_DISPOSITION_RESULT_ACCEPTED;
+ break;
+ case DEVICE_MESSAGE_DISPOSITION_RESULT_REJECTED:
+ messenger_disposition_result = MESSENGER_DISPOSITION_RESULT_REJECTED;
+ break;
+ case DEVICE_MESSAGE_DISPOSITION_RESULT_RELEASED:
+ messenger_disposition_result = MESSENGER_DISPOSITION_RESULT_RELEASED;
+ break;
+ default:
+ LogError("Failed to get the corresponding MESSENGER_DISPOSITION_RESULT (%d is not supported)", device_disposition_result);
+ messenger_disposition_result = MESSENGER_DISPOSITION_RESULT_RELEASED;
+ break;
+ }
+
+ return messenger_disposition_result;
+}
+
+static MESSENGER_DISPOSITION_RESULT on_messenger_message_received_callback(IOTHUB_MESSAGE_HANDLE iothub_message_handle, MESSENGER_MESSAGE_DISPOSITION_INFO* disposition_info, void* context)
+{
+ MESSENGER_DISPOSITION_RESULT msgr_disposition_result;
+
+ // Codes_SRS_DEVICE_09_070: [If `iothub_message_handle` or `context` is NULL, on_messenger_message_received_callback shall return MESSENGER_DISPOSITION_RESULT_RELEASED]
+ if (iothub_message_handle == NULL || context == NULL)
+ {
+ LogError("Failed receiving incoming C2D message (message handle (%p) or context (%p) is NULL)", iothub_message_handle, context);
+ msgr_disposition_result = MESSENGER_DISPOSITION_RESULT_RELEASED;
+ }
+ else
+ {
+ DEVICE_INSTANCE* device_instance = (DEVICE_INSTANCE*)context;
+
+ if (device_instance->on_message_received_callback == NULL)
+ {
+ LogError("Device '%s' failed receiving incoming C2D message (callback is NULL)", device_instance->config->device_id);
+ msgr_disposition_result = MESSENGER_DISPOSITION_RESULT_RELEASED;
+ }
+ else
+ {
+ DEVICE_MESSAGE_DISPOSITION_INFO* device_message_disposition_info;
+
+ // Codes_SRS_DEVICE_09_119: [A DEVICE_MESSAGE_DISPOSITION_INFO instance shall be created containing a copy of `disposition_info->source` and `disposition_info->message_id`]
+ if ((device_message_disposition_info = create_device_message_disposition_info_from(disposition_info)) == NULL)
+ {
+ // Codes_SRS_DEVICE_09_120: [If the DEVICE_MESSAGE_DISPOSITION_INFO instance fails to be created, on_messenger_message_received_callback shall return MESSENGER_DISPOSITION_RESULT_RELEASED]
+ LogError("Device '%s' failed receiving incoming C2D message (failed creating DEVICE_MESSAGE_DISPOSITION_INFO)", device_instance->config->device_id);
+ msgr_disposition_result = MESSENGER_DISPOSITION_RESULT_RELEASED;
+ }
+ else
+ {
+ // Codes_SRS_DEVICE_09_071: [The user callback shall be invoked, passing the context it provided]
+ DEVICE_MESSAGE_DISPOSITION_RESULT device_disposition_result = device_instance->on_message_received_callback(iothub_message_handle, device_message_disposition_info, device_instance->on_message_received_context);
+
+ // Codes_SRS_DEVICE_09_072: [If the user callback returns DEVICE_MESSAGE_DISPOSITION_RESULT_ACCEPTED, on_messenger_message_received_callback shall return MESSENGER_DISPOSITION_RESULT_ACCEPTED]
+ // Codes_SRS_DEVICE_09_073: [If the user callback returns DEVICE_MESSAGE_DISPOSITION_RESULT_REJECTED, on_messenger_message_received_callback shall return MESSENGER_DISPOSITION_RESULT_REJECTED]
+ // Codes_SRS_DEVICE_09_074: [If the user callback returns DEVICE_MESSAGE_DISPOSITION_RESULT_RELEASED, on_messenger_message_received_callback shall return MESSENGER_DISPOSITION_RESULT_RELEASED]
+ msgr_disposition_result = get_messenger_message_disposition_result_from(device_disposition_result);
+
+ // Codes_SRS_DEVICE_09_121: [on_messenger_message_received_callback shall release the memory allocated for DEVICE_MESSAGE_DISPOSITION_INFO]
+ destroy_device_disposition_info(device_message_disposition_info);
+ }
+ }
+ }
+
+ return msgr_disposition_result;
+}
+
+
+// Configuration Helpers
+
+static void destroy_device_config(DEVICE_CONFIG* config)
+{
+ if (config != NULL)
+ {
+ free(config->device_id);
+ free(config->iothub_host_fqdn);
+ free(config->device_primary_key);
+ free(config->device_secondary_key);
+ free(config->device_sas_token);
+ free(config);
+ }
+}
+
+static DEVICE_CONFIG* clone_device_config(DEVICE_CONFIG *config)
+{
+ DEVICE_CONFIG* new_config;
+
+ if ((new_config = (DEVICE_CONFIG*)malloc(sizeof(DEVICE_CONFIG))) == NULL)
+ {
+ LogError("Failed copying the DEVICE_CONFIG (malloc failed)");
+ }
+ else
+ {
+ int result;
+
+ memset(new_config, 0, sizeof(DEVICE_CONFIG));
+
+ if (mallocAndStrcpy_s(&new_config->device_id, config->device_id) != RESULT_OK)
+ {
+ LogError("Failed copying the DEVICE_CONFIG (failed copying device_id)");
+ result = __FAILURE__;
+ }
+ else if (mallocAndStrcpy_s(&new_config->iothub_host_fqdn, config->iothub_host_fqdn) != RESULT_OK)
+ {
+ LogError("Failed copying the DEVICE_CONFIG (failed copying iothub_host_fqdn)");
+ result = __FAILURE__;
+ }
+ else if (config->device_sas_token != NULL &&
+ mallocAndStrcpy_s(&new_config->device_sas_token, config->device_sas_token) != RESULT_OK)
+ {
+ LogError("Failed copying the DEVICE_CONFIG (failed copying device_sas_token)");
+ result = __FAILURE__;
+ }
+ else if (config->device_primary_key != NULL &&
+ mallocAndStrcpy_s(&new_config->device_primary_key, config->device_primary_key) != RESULT_OK)
+ {
+ LogError("Failed copying the DEVICE_CONFIG (failed copying device_primary_key)");
+ result = __FAILURE__;
+ }
+ else if (config->device_secondary_key != NULL &&
+ mallocAndStrcpy_s(&new_config->device_secondary_key, config->device_secondary_key) != RESULT_OK)
+ {
+ LogError("Failed copying the DEVICE_CONFIG (failed copying device_secondary_key)");
+ result = __FAILURE__;
+ }
+ else
+ {
+ new_config->authentication_mode = config->authentication_mode;
+ new_config->on_state_changed_callback = config->on_state_changed_callback;
+ new_config->on_state_changed_context = config->on_state_changed_context;
+
+ result = RESULT_OK;
+ }
+
+ if (result != RESULT_OK)
+ {
+ destroy_device_config(new_config);
+ new_config = NULL;
+ }
+ }
+
+ return new_config;
+}
+
+static void set_authentication_config(DEVICE_INSTANCE* device_instance, AUTHENTICATION_CONFIG* auth_config)
+{
+ DEVICE_CONFIG *device_config = device_instance->config;
+
+ auth_config->device_id = device_config->device_id;
+ auth_config->iothub_host_fqdn = device_config->iothub_host_fqdn;
+ auth_config->on_error_callback = on_authentication_error_callback;
+ auth_config->on_error_callback_context = device_instance;
+ auth_config->on_state_changed_callback = on_authentication_state_changed_callback;
+ auth_config->on_state_changed_callback_context = device_instance;
+ auth_config->device_primary_key = device_config->device_primary_key;
+ auth_config->device_secondary_key = device_config->device_secondary_key;
+ auth_config->device_sas_token = device_config->device_sas_token;
+}
+
+
+// Create and Destroy Helpers
+
+static void internal_destroy_device(DEVICE_INSTANCE* instance)
+{
+ if (instance != NULL)
+ {
+ if (instance->messenger_handle != NULL)
+ {
+ messenger_destroy(instance->messenger_handle);
+ }
+
+ if (instance->authentication_handle != NULL)
+ {
+ authentication_destroy(instance->authentication_handle);
+ }
+
+ destroy_device_config(instance->config);
+ free(instance);
+ }
+}
+
+static int create_authentication_instance(DEVICE_INSTANCE *instance)
+{
+ int result;
+ AUTHENTICATION_CONFIG auth_config;
+
+ set_authentication_config(instance, &auth_config);
+
+ if ((instance->authentication_handle = authentication_create(&auth_config)) == NULL)
+ {
+ LogError("Failed creating the AUTHENTICATION_HANDLE (authentication_create failed)");
+ result = __FAILURE__;
+ }
+ else
+ {
+ result = RESULT_OK;
+ }
+
+ return result;
+}
+
+static int create_messenger_instance(DEVICE_INSTANCE* instance)
+{
+ int result;
+
+ MESSENGER_CONFIG messenger_config;
+ messenger_config.device_id = instance->config->device_id;
+ messenger_config.iothub_host_fqdn = instance->config->iothub_host_fqdn;
+ messenger_config.on_state_changed_callback = on_messenger_state_changed_callback;
+ messenger_config.on_state_changed_context = instance;
+
+ if ((instance->messenger_handle = messenger_create(&messenger_config)) == NULL)
+ {
+ LogError("Failed creating the MESSENGER_HANDLE (messenger_create failed)");
+ result = __FAILURE__;
+ }
+ else
+ {
+ result = RESULT_OK;
+ }
+
+ return result;
+}
+
+// ---------- Set/Retrieve Options Helpers ----------//
+
+static void* device_clone_option(const char* name, const void* value)
+{
+ void* result;
+
+ if (name == NULL || value == NULL)
+ {
+ LogError("Failed to clone device option (either name (%p) or value (%p) is NULL)", name, value);
+ result = NULL;
+ }
+ else
+ {
+ if (strcmp(DEVICE_OPTION_SAVED_AUTH_OPTIONS, name) == 0 ||
+ strcmp(DEVICE_OPTION_SAVED_MESSENGER_OPTIONS, name) == 0)
+ {
+ if ((result = (void*)OptionHandler_Clone((OPTIONHANDLER_HANDLE)value)) == NULL)
+ {
+ LogError("Failed to clone device option (OptionHandler_Clone failed for option %s)", name);
+ }
+ }
+ else
+ {
+ LogError("Failed to clone device option (option with name '%s' is not suppported)", name);
+ result = NULL;
+ }
+ }
+
+ return result;
+}
+
+static void device_destroy_option(const char* name, const void* value)
+{
+ if (name == NULL || value == NULL)
+ {
+ LogError("Failed to destroy device option (either name (%p) or value (%p) is NULL)", name, value);
+ }
+ else
+ {
+ if (strcmp(name, DEVICE_OPTION_SAVED_AUTH_OPTIONS) == 0 ||
+ strcmp(name, DEVICE_OPTION_SAVED_MESSENGER_OPTIONS) == 0)
+ {
+ OptionHandler_Destroy((OPTIONHANDLER_HANDLE)value);
+ }
+ else
+ {
+ LogError("Failed to clone device option (option with name '%s' is not suppported)", name);
+ }
+ }
+}
+
+
+// Public APIs:
+
+DEVICE_HANDLE device_create(DEVICE_CONFIG *config)
+{
+ DEVICE_INSTANCE *instance;
+
+ // Codes_SRS_DEVICE_09_001: [If `config` or device_id or iothub_host_fqdn or on_state_changed_callback are NULL then device_create shall fail and return NULL]
+ if (config == NULL)
+ {
+ LogError("Failed creating the device instance (config is NULL)");
+ instance = NULL;
+ }
+ else if (config->device_id == NULL)
+ {
+ LogError("Failed creating the device instance (config->device_id is NULL)");
+ instance = NULL;
+ }
+ else if (config->iothub_host_fqdn == NULL)
+ {
+ LogError("Failed creating the device instance (config->iothub_host_fqdn is NULL)");
+ instance = NULL;
+ }
+ else if (config->on_state_changed_callback == NULL)
+ {
+ LogError("Failed creating the device instance (config->on_state_changed_callback is NULL)");
+ instance = NULL;
+ }
+ // Codes_SRS_DEVICE_09_002: [device_create shall allocate memory for the device instance structure]
+ else if ((instance = (DEVICE_INSTANCE*)malloc(sizeof(DEVICE_INSTANCE))) == NULL)
+ {
+ // Codes_SRS_DEVICE_09_003: [If malloc fails, device_create shall fail and return NULL]
+ LogError("Failed creating the device instance (malloc failed)");
+ }
+ else
+ {
+ int result;
+
+ memset(instance, 0, sizeof(DEVICE_INSTANCE));
+
+ // Codes_SRS_DEVICE_09_004: [All `config` parameters shall be saved into `instance`]
+ if ((instance->config = clone_device_config(config)) == NULL)
+ {
+ // Codes_SRS_DEVICE_09_005: [If any `config` parameters fail to be saved into `instance`, device_create shall fail and return NULL]
+ LogError("Failed creating the device instance for device '%s' (failed copying the configuration)", config->device_id);
+ result = __FAILURE__;
+ }
+ // Codes_SRS_DEVICE_09_006: [If `instance->authentication_mode` is DEVICE_AUTH_MODE_CBS, `instance->authentication_handle` shall be set using authentication_create()]
+ else if (instance->config->authentication_mode == DEVICE_AUTH_MODE_CBS &&
+ create_authentication_instance(instance) != RESULT_OK)
+ {
+ // Codes_SRS_DEVICE_09_007: [If the AUTHENTICATION_HANDLE fails to be created, device_create shall fail and return NULL]
+ LogError("Failed creating the device instance for device '%s' (failed creating the authentication instance)", instance->config->device_id);
+ result = __FAILURE__;
+ }
+ // Codes_SRS_DEVICE_09_008: [`instance->messenger_handle` shall be set using messenger_create()]
+ else if (create_messenger_instance(instance) != RESULT_OK)
+ {
+ // Codes_SRS_DEVICE_09_009: [If the MESSENGER_HANDLE fails to be created, device_create shall fail and return NULL]
+ LogError("Failed creating the device instance for device '%s' (failed creating the messenger instance)", instance->config->device_id);
+ result = __FAILURE__;
+ }
+ else
+ {
+ instance->auth_state = AUTHENTICATION_STATE_STOPPED;
+ instance->msgr_state = MESSENGER_STATE_STOPPED;
+ instance->state = DEVICE_STATE_STOPPED;
+ instance->auth_state_last_changed_time = INDEFINITE_TIME;
+ instance->auth_state_change_timeout_secs = DEFAULT_AUTH_STATE_CHANGED_TIMEOUT_SECS;
+ instance->msgr_state_last_changed_time = INDEFINITE_TIME;
+ instance->msgr_state_change_timeout_secs = DEFAULT_MSGR_STATE_CHANGED_TIMEOUT_SECS;
+
+ result = RESULT_OK;
+ }
+
+ if (result != RESULT_OK)
+ {
+ // Codes_SRS_DEVICE_09_010: [If device_create fails it shall release all memory it has allocated]
+ internal_destroy_device(instance);
+ instance = NULL;
+ }
+ }
+
+ // Codes_SRS_DEVICE_09_011: [If device_create succeeds it shall return a handle to its `instance` structure]
+ return (DEVICE_HANDLE)instance;
+}
+
+int device_start_async(DEVICE_HANDLE handle, SESSION_HANDLE session_handle, CBS_HANDLE cbs_handle)
+{
+ int result;
+
+ // Codes_SRS_DEVICE_09_017: [If `handle` is NULL, device_start_async shall return a non-zero result]
+ if (handle == NULL)
+ {
+ LogError("Failed starting device (handle is NULL)");
+ result = __FAILURE__;
+ }
+ else
+ {
+ DEVICE_INSTANCE* instance = (DEVICE_INSTANCE*)handle;
+
+ // Codes_SRS_DEVICE_09_018: [If the device state is not DEVICE_STATE_STOPPED, device_start_async shall return a non-zero result]
+ if (instance->state != DEVICE_STATE_STOPPED)
+ {
+ LogError("Failed starting device (device is not stopped)");
+ result = __FAILURE__;
+ }
+ // Codes_SRS_DEVICE_09_019: [If `session_handle` is NULL, device_start_async shall return a non-zero result]
+ else if (session_handle == NULL)
+ {
+ LogError("Failed starting device (session_handle is NULL)");
+ result = __FAILURE__;
+ }
+ // Codes_SRS_DEVICE_09_020: [If using CBS authentication and `cbs_handle` is NULL, device_start_async shall return a non-zero result]
+ else if (instance->config->authentication_mode == DEVICE_AUTH_MODE_CBS && cbs_handle == NULL)
+ {
+ LogError("Failed starting device (device using CBS authentication, but cbs_handle is NULL)");
+ result = __FAILURE__;
+ }
+ else
+ {
+ // Codes_SRS_DEVICE_09_021: [`session_handle` and `cbs_handle` shall be saved into the `instance`]
+ instance->session_handle = session_handle;
+ instance->cbs_handle = cbs_handle;
+
+ // Codes_SRS_DEVICE_09_022: [The device state shall be updated to DEVICE_STATE_STARTING, and state changed callback invoked]
+ update_state(instance, DEVICE_STATE_STARTING);
+
+ // Codes_SRS_DEVICE_09_023: [If no failures occur, device_start_async shall return 0]
+ result = RESULT_OK;
+ }
+ }
+
+ return result;
+}
+
+// @brief
+// stops a device instance (stops messenger and authentication) synchronously.
+// @returns
+// 0 if the function succeeds, non-zero otherwise.
+int device_stop(DEVICE_HANDLE handle)
+{
+ int result;
+
+ // Codes_SRS_DEVICE_09_024: [If `handle` is NULL, device_stop shall return a non-zero result]
+ if (handle == NULL)
+ {
+ LogError("Failed stopping device (handle is NULL)");
+ result = __FAILURE__;
+ }
+ else
+ {
+ DEVICE_INSTANCE* instance = (DEVICE_INSTANCE*)handle;
+
+ // Codes_SRS_DEVICE_09_025: [If the device state is already DEVICE_STATE_STOPPED or DEVICE_STATE_STOPPING, device_stop shall return a non-zero result]
+ if (instance->state == DEVICE_STATE_STOPPED || instance->state == DEVICE_STATE_STOPPING)
+ {
+ LogError("Failed stopping device '%s' (device is already stopped or stopping)", instance->config->device_id);
+ result = __FAILURE__;
+ }
+ else
+ {
+ // Codes_SRS_DEVICE_09_026: [The device state shall be updated to DEVICE_STATE_STOPPING, and state changed callback invoked]
+ update_state(instance, DEVICE_STATE_STOPPING);
+
+ // Codes_SRS_DEVICE_09_027: [If `instance->messenger_handle` state is not MESSENGER_STATE_STOPPED, messenger_stop shall be invoked]
+ if (instance->msgr_state != MESSENGER_STATE_STOPPED &&
+ instance->msgr_state != MESSENGER_STATE_STOPPING &&
+ messenger_stop(instance->messenger_handle) != RESULT_OK)
+ {
+ // Codes_SRS_DEVICE_09_028: [If messenger_stop fails, the `instance` state shall be updated to DEVICE_STATE_ERROR_MSG and the function shall return non-zero result]
+ LogError("Failed stopping device '%s' (messenger_stop failed)", instance->config->device_id);
+ result = __FAILURE__;
+ update_state(instance, DEVICE_STATE_ERROR_MSG);
+ }
+ // Codes_SRS_DEVICE_09_029: [If CBS authentication is used, if `instance->authentication_handle` state is not AUTHENTICATION_STATE_STOPPED, authentication_stop shall be invoked]
+ else if (instance->config->authentication_mode == DEVICE_AUTH_MODE_CBS &&
+ instance->auth_state != AUTHENTICATION_STATE_STOPPED &&
+ authentication_stop(instance->authentication_handle) != RESULT_OK)
+ {
+ // Codes_SRS_DEVICE_09_030: [If authentication_stop fails, the `instance` state shall be updated to DEVICE_STATE_ERROR_AUTH and the function shall return non-zero result]
+ LogError("Failed stopping device '%s' (authentication_stop failed)", instance->config->device_id);
+ result = __FAILURE__;
+ update_state(instance, DEVICE_STATE_ERROR_AUTH);
+ }
+ else
+ {
+ // Codes_SRS_DEVICE_09_031: [The device state shall be updated to DEVICE_STATE_STOPPED, and state changed callback invoked]
+ update_state(instance, DEVICE_STATE_STOPPED);
+
+ // Codes_SRS_DEVICE_09_032: [If no failures occur, device_stop shall return 0]
+ result = RESULT_OK;
+ }
+ }
+ }
+
+ return result;
+}
+
+void device_do_work(DEVICE_HANDLE handle)
+{
+ // Codes_SRS_DEVICE_09_033: [If `handle` is NULL, device_do_work shall return]
+ if (handle == NULL)
+ {
+ LogError("Failed to perform device_do_work (handle is NULL)");
+ }
+ else
+ {
+ // Cranking the state monster:
+ DEVICE_INSTANCE* instance = (DEVICE_INSTANCE*)handle;
+
+ if (instance->state == DEVICE_STATE_STARTING)
+ {
+ // Codes_SRS_DEVICE_09_034: [If CBS authentication is used and authentication state is AUTHENTICATION_STATE_STOPPED, authentication_start shall be invoked]
+ if (instance->config->authentication_mode == DEVICE_AUTH_MODE_CBS)
+ {
+ if (instance->auth_state == AUTHENTICATION_STATE_STOPPED)
+ {
+ if (authentication_start(instance->authentication_handle, instance->cbs_handle) != RESULT_OK)
+ {
+ // Codes_SRS_DEVICE_09_035: [If authentication_start fails, the device state shall be updated to DEVICE_STATE_ERROR_AUTH]
+ LogError("Device '%s' failed to be authenticated (authentication_start failed)", instance->config->device_id);
+
+ update_state(instance, DEVICE_STATE_ERROR_AUTH);
+ }
+ }
+ // Codes_SRS_DEVICE_09_036: [If authentication state is AUTHENTICATION_STATE_STARTING, the device shall track the time since last event change and timeout if needed]
+ else if (instance->auth_state == AUTHENTICATION_STATE_STARTING)
+ {
+ int is_timed_out;
+ if (is_timeout_reached(instance->auth_state_last_changed_time, instance->auth_state_change_timeout_secs, &is_timed_out) != RESULT_OK)
+ {
+ LogError("Device '%s' failed verifying the timeout for authentication start (is_timeout_reached failed)", instance->config->device_id);
+ update_state(instance, DEVICE_STATE_ERROR_AUTH);
+ }
+ // Codes_SRS_DEVICE_09_037: [If authentication_start times out, the device state shall be updated to DEVICE_STATE_ERROR_AUTH_TIMEOUT]
+ else if (is_timed_out == 1)
+ {
+ LogError("Device '%s' authentication did not complete starting within expected timeout (%d)", instance->config->device_id, instance->auth_state_change_timeout_secs);
+
+ update_state(instance, DEVICE_STATE_ERROR_AUTH_TIMEOUT);
+ }
+ }
+ else if (instance->auth_state == AUTHENTICATION_STATE_ERROR)
+ {
+ // Codes_SRS_DEVICE_09_038: [If authentication state is AUTHENTICATION_STATE_ERROR and error code is AUTH_FAILED, the device state shall be updated to DEVICE_STATE_ERROR_AUTH]
+ if (instance->auth_error_code == AUTHENTICATION_ERROR_AUTH_FAILED)
+ {
+ update_state(instance, DEVICE_STATE_ERROR_AUTH);
+ }
+ // Codes_SRS_DEVICE_09_039: [If authentication state is AUTHENTICATION_STATE_ERROR and error code is TIMEOUT, the device state shall be updated to DEVICE_STATE_ERROR_AUTH_TIMEOUT]
+ else // DEVICE_STATE_ERROR_TIMEOUT
+ {
+ update_state(instance, DEVICE_STATE_ERROR_AUTH_TIMEOUT);
+ }
+ }
+ // There is no AUTHENTICATION_STATE_STOPPING
+ }
+
+ // Codes_SRS_DEVICE_09_040: [Messenger shall not be started if using CBS authentication and authentication start has not completed yet]
+ if (instance->config->authentication_mode == DEVICE_AUTH_MODE_X509 || instance->auth_state == AUTHENTICATION_STATE_STARTED)
+ {
+ // Codes_SRS_DEVICE_09_041: [If messenger state is MESSENGER_STATE_STOPPED, messenger_start shall be invoked]
+ if (instance->msgr_state == MESSENGER_STATE_STOPPED)
+ {
+ // Codes_SRS_DEVICE_09_042: [If messenger_start fails, the device state shall be updated to DEVICE_STATE_ERROR_MSG]
+ if (messenger_start(instance->messenger_handle, instance->session_handle) != RESULT_OK)
+ {
+ LogError("Device '%s' messenger failed to be started (messenger_start failed)", instance->config->device_id);
+
+ update_state(instance, DEVICE_STATE_ERROR_MSG);
+ }
+ }
+ // Codes_SRS_DEVICE_09_043: [If messenger state is MESSENGER_STATE_STARTING, the device shall track the time since last event change and timeout if needed]
+ else if (instance->msgr_state == MESSENGER_STATE_STARTING)
+ {
+ int is_timed_out;
+ if (is_timeout_reached(instance->msgr_state_last_changed_time, instance->msgr_state_change_timeout_secs, &is_timed_out) != RESULT_OK)
+ {
+ LogError("Device '%s' failed verifying the timeout for messenger start (is_timeout_reached failed)", instance->config->device_id);
+
+ update_state(instance, DEVICE_STATE_ERROR_MSG);
+ }
+ // Codes_SRS_DEVICE_09_044: [If messenger_start times out, the device state shall be updated to DEVICE_STATE_ERROR_MSG]
+ else if (is_timed_out == 1)
+ {
+ LogError("Device '%s' messenger did not complete starting within expected timeout (%d)", instance->config->device_id, instance->msgr_state_change_timeout_secs);
+
+ update_state(instance, DEVICE_STATE_ERROR_MSG);
+ }
+ }
+ // Codes_SRS_DEVICE_09_045: [If messenger state is MESSENGER_STATE_ERROR, the device state shall be updated to DEVICE_STATE_ERROR_MSG]
+ else if (instance->msgr_state == MESSENGER_STATE_ERROR)
+ {
+ LogError("Device '%s' messenger failed to be started (messenger got into error state)", instance->config->device_id);
+
+ update_state(instance, DEVICE_STATE_ERROR_MSG);
+ }
+ // Codes_SRS_DEVICE_09_046: [If messenger state is MESSENGER_STATE_STARTED, the device state shall be updated to DEVICE_STATE_STARTED]
+ else if (instance->msgr_state == MESSENGER_STATE_STARTED)
+ {
+ update_state(instance, DEVICE_STATE_STARTED);
+ }
+ }
+ }
+ else if (instance->state == DEVICE_STATE_STARTED)
+ {
+ // Codes_SRS_DEVICE_09_047: [If CBS authentication is used and authentication state is not AUTHENTICATION_STATE_STARTED, the device state shall be updated to DEVICE_STATE_ERROR_AUTH]
+ if (instance->config->authentication_mode == DEVICE_AUTH_MODE_CBS &&
+ instance->auth_state != AUTHENTICATION_STATE_STARTED)
+ {
+ LogError("Device '%s' is started but authentication reported unexpected state %d", instance->config->device_id, instance->auth_state);
+
+ if (instance->auth_state != AUTHENTICATION_STATE_ERROR)
+ {
+ if (instance->auth_error_code == AUTHENTICATION_ERROR_AUTH_FAILED)
+ {
+ update_state(instance, DEVICE_STATE_ERROR_AUTH);
+ }
+ else // AUTHENTICATION_ERROR_AUTH_TIMEOUT
+ {
+ update_state(instance, DEVICE_STATE_ERROR_AUTH_TIMEOUT);
+ }
+ }
+ else
+ {
+ update_state(instance, DEVICE_STATE_ERROR_AUTH);
+ }
+ }
+ else
+ {
+ // Codes_SRS_DEVICE_09_048: [If messenger state is not MESSENGER_STATE_STARTED, the device state shall be updated to DEVICE_STATE_ERROR_MSG]
+ if (instance->msgr_state != MESSENGER_STATE_STARTED)
+ {
+ LogError("Device '%s' is started but messenger reported unexpected state %d", instance->config->device_id, instance->msgr_state);
+ update_state(instance, DEVICE_STATE_ERROR_MSG);
+ }
+ }
+ }
+
+ // Invoking the do_works():
+ if (instance->config->authentication_mode == DEVICE_AUTH_MODE_CBS)
+ {
+ if (instance->auth_state != AUTHENTICATION_STATE_STOPPED && instance->auth_state != AUTHENTICATION_STATE_ERROR)
+ {
+ // Codes_SRS_DEVICE_09_049: [If CBS is used for authentication and `instance->authentication_handle` state is not STOPPED or ERROR, authentication_do_work shall be invoked]
+ authentication_do_work(instance->authentication_handle);
+ }
+ }
+
+ if (instance->msgr_state != MESSENGER_STATE_STOPPED && instance->msgr_state != MESSENGER_STATE_ERROR)
+ {
+ // Codes_SRS_DEVICE_09_050: [If `instance->messenger_handle` state is not STOPPED or ERROR, authentication_do_work shall be invoked]
+ messenger_do_work(instance->messenger_handle);
+ }
+ }
+}
+
+void device_destroy(DEVICE_HANDLE handle)
+{
+ // Codes_SRS_DEVICE_09_012: [If `handle` is NULL, device_destroy shall return]
+ if (handle == NULL)
+ {
+ LogError("Failed destroying device handle (handle is NULL)");
+ }
+ else
+ {
+ DEVICE_INSTANCE* instance = (DEVICE_INSTANCE*)handle;
+ // Codes_SRS_DEVICE_09_013: [If the device is in state DEVICE_STATE_STARTED or DEVICE_STATE_STARTING, device_stop() shall be invoked]
+ if (instance->state == DEVICE_STATE_STARTED || instance->state == DEVICE_STATE_STARTING)
+ {
+ (void)device_stop((DEVICE_HANDLE)instance);
+ }
+
+ // Codes_SRS_DEVICE_09_014: [`instance->messenger_handle shall be destroyed using messenger_destroy()`]
+ // Codes_SRS_DEVICE_09_015: [If created, `instance->authentication_handle` shall be destroyed using authentication_destroy()`]
+ // Codes_SRS_DEVICE_09_016: [The contents of `instance->config` shall be detroyed and then it shall be freed]
+ internal_destroy_device((DEVICE_INSTANCE*)handle);
+ }
+}
+
+int device_send_event_async(DEVICE_HANDLE handle, IOTHUB_MESSAGE_LIST* message, ON_DEVICE_D2C_EVENT_SEND_COMPLETE on_device_d2c_event_send_complete_callback, void* context)
+{
+ int result;
+
+ // Codes_SRS_DEVICE_09_051: [If `handle` are `message` are NULL, device_send_event_async shall return a non-zero result]
+ if (handle == NULL || message == NULL)
+ {
+ LogError("Failed sending event (either handle (%p) or message (%p) are NULL)", handle, message);
+ result = __FAILURE__;
+ }
+ else
+ {
+ DEVICE_SEND_EVENT_TASK* send_task;
+
+ // Codes_SRS_DEVICE_09_052: [A structure (`send_task`) shall be created to track the send state of the message]
+ if ((send_task = (DEVICE_SEND_EVENT_TASK*)malloc(sizeof(DEVICE_SEND_EVENT_TASK))) == NULL)
+ {
+ // Codes_SRS_DEVICE_09_053: [If `send_task` fails to be created, device_send_event_async shall return a non-zero value]
+ LogError("Failed sending event (failed creating task to send event)");
+ result = __FAILURE__;
+ }
+ else
+ {
+ DEVICE_INSTANCE* instance = (DEVICE_INSTANCE*)handle;
+
+ // Codes_SRS_DEVICE_09_054: [`send_task` shall contain the user callback and the context provided]
+ memset(send_task, 0, sizeof(DEVICE_SEND_EVENT_TASK));
+ send_task->on_event_send_complete_callback = on_device_d2c_event_send_complete_callback;
+ send_task->on_event_send_complete_context = context;
+
+ // Codes_SRS_DEVICE_09_055: [The message shall be sent using messenger_send_async, passing `on_event_send_complete_messenger_callback` and `send_task`]
+ if (messenger_send_async(instance->messenger_handle, message, on_event_send_complete_messenger_callback, (void*)send_task) != RESULT_OK)
+ {
+ // Codes_SRS_DEVICE_09_056: [If messenger_send_async fails, device_send_event_async shall return a non-zero value]
+ LogError("Failed sending event (messenger_send_async failed)");
+ // Codes_SRS_DEVICE_09_057: [If any failures occur, device_send_event_async shall release all memory it has allocated]
+ free(send_task);
+ result = __FAILURE__;
+ }
+ else
+ {
+ // Codes_SRS_DEVICE_09_058: [If no failures occur, device_send_event_async shall return 0]
+ result = RESULT_OK;
+ }
+ }
+ }
+
+ return result;
+}
+
+int device_get_send_status(DEVICE_HANDLE handle, DEVICE_SEND_STATUS *send_status)
+{
+ int result;
+
+
+ // Codes_SRS_DEVICE_09_105: [If `handle` or `send_status` is NULL, device_get_send_status shall return a non-zero result]
+ if (handle == NULL || send_status == NULL)
+ {
+ LogError("Failed getting the device messenger send status (NULL parameter received; handle=%p, send_status=%p)", handle, send_status);
+ result = __FAILURE__;
+ }
+ else
+ {
+ DEVICE_INSTANCE* instance = (DEVICE_INSTANCE*)handle;
+ MESSENGER_SEND_STATUS messenger_send_status;
+
+ // Codes_SRS_DEVICE_09_106: [The status of `instance->messenger_handle` shall be obtained using messenger_get_send_status]
+ if (messenger_get_send_status(instance->messenger_handle, &messenger_send_status) != RESULT_OK)
+ {
+ // Codes_SRS_DEVICE_09_107: [If messenger_get_send_status fails, device_get_send_status shall return a non-zero result]
+ LogError("Failed getting the device messenger send status (messenger_get_send_status failed)");
+ result = __FAILURE__;
+ }
+ else
+ {
+ // Codes_SRS_DEVICE_09_108: [If messenger_get_send_status returns MESSENGER_SEND_STATUS_IDLE, device_get_send_status return status DEVICE_SEND_STATUS_IDLE]
+ if (messenger_send_status == MESSENGER_SEND_STATUS_IDLE)
+ {
+ *send_status = DEVICE_SEND_STATUS_IDLE;
+ }
+ // Codes_SRS_DEVICE_09_109: [If messenger_get_send_status returns MESSENGER_SEND_STATUS_BUSY, device_get_send_status return status DEVICE_SEND_STATUS_BUSY]
+ else // i.e., messenger_send_status == MESSENGER_SEND_STATUS_BUSY
+ {
+ *send_status = DEVICE_SEND_STATUS_BUSY;
+ }
+
+ // Codes_SRS_DEVICE_09_110: [If device_get_send_status succeeds, it shall return zero as result]
+ result = RESULT_OK;
+ }
+ }
+
+ return result;
+}
+
+int device_subscribe_message(DEVICE_HANDLE handle, ON_DEVICE_C2D_MESSAGE_RECEIVED on_message_received_callback, void* context)
+{
+ int result;
+
+ // Codes_SRS_DEVICE_09_066: [If `handle` or `on_message_received_callback` or `context` is NULL, device_subscribe_message shall return a non-zero result]
+ if (handle == NULL || on_message_received_callback == NULL || context == NULL)
+ {
+ LogError("Failed subscribing to C2D messages (either handle (%p), on_message_received_callback (%p) or context (%p) is NULL)",
+ handle, on_message_received_callback, context);
+ result = __FAILURE__;
+ }
+ else
+ {
+ DEVICE_INSTANCE* instance = (DEVICE_INSTANCE*)handle;
+
+ // Codes_SRS_DEVICE_09_067: [messenger_subscribe_for_messages shall be invoked passing `on_messenger_message_received_callback` and the user callback and context]
+ if (messenger_subscribe_for_messages(instance->messenger_handle, on_messenger_message_received_callback, handle) != RESULT_OK)
+ {
+ // Codes_SRS_DEVICE_09_068: [If messenger_subscribe_for_messages fails, device_subscribe_message shall return a non-zero result]
+ LogError("Failed subscribing to C2D messages (messenger_subscribe_for_messages failed)");
+ result = __FAILURE__;
+ }
+ else
+ {
+ instance->on_message_received_callback = on_message_received_callback;
+ instance->on_message_received_context = context;
+
+ // Codes_SRS_DEVICE_09_069: [If no failures occur, device_subscribe_message shall return 0]
+ result = RESULT_OK;
+ }
+ }
+
+ return result;
+}
+
+int device_unsubscribe_message(DEVICE_HANDLE handle)
+{
+ int result;
+
+ // Codes_SRS_DEVICE_09_076: [If `handle` is NULL, device_unsubscribe_message shall return a non-zero result]
+ if (handle == NULL)
+ {
+ LogError("Failed unsubscribing to C2D messages (handle is NULL)");
+ result = __FAILURE__;
+ }
+ else
+ {
+ DEVICE_INSTANCE* instance = (DEVICE_INSTANCE*)handle;
+
+ // Codes_SRS_DEVICE_09_077: [messenger_unsubscribe_for_messages shall be invoked passing `instance->messenger_handle`]
+ if (messenger_unsubscribe_for_messages(instance->messenger_handle) != RESULT_OK)
+ {
+ // Codes_SRS_DEVICE_09_078: [If messenger_unsubscribe_for_messages fails, device_unsubscribe_message shall return a non-zero result]
+ LogError("Failed unsubscribing to C2D messages (messenger_unsubscribe_for_messages failed)");
+ result = __FAILURE__;
+ }
+ else
+ {
+ // Codes_SRS_DEVICE_09_079: [If no failures occur, device_unsubscribe_message shall return 0]
+ result = RESULT_OK;
+ }
+ }
+
+ return result;
+}
+
+int device_send_message_disposition(DEVICE_HANDLE device_handle, DEVICE_MESSAGE_DISPOSITION_INFO* disposition_info, DEVICE_MESSAGE_DISPOSITION_RESULT disposition_result)
+{
+ int result;
+
+ // Codes_SRS_DEVICE_09_111: [If `device_handle` or `disposition_info` are NULL, device_send_message_disposition() shall fail and return __FAILURE__]
+ if (device_handle == NULL || disposition_info == NULL)
+ {
+ LogError("Failed sending message disposition (either device_handle (%p) or disposition_info (%p) are NULL)", device_handle, disposition_info);
+ result = __FAILURE__;
+ }
+ // Codes_SRS_DEVICE_09_112: [If `disposition_info->source` is NULL, device_send_message_disposition() shall fail and return __FAILURE__]
+ else if (disposition_info->source == NULL)
+ {
+ LogError("Failed sending message disposition (disposition_info->source is NULL)");
+ result = __FAILURE__;
+ }
+ else
+ {
+ DEVICE_INSTANCE* device = (DEVICE_INSTANCE*)device_handle;
+ MESSENGER_MESSAGE_DISPOSITION_INFO* messenger_disposition_info;
+
+ // Codes_SRS_DEVICE_09_113: [A MESSENGER_MESSAGE_DISPOSITION_INFO instance shall be created with a copy of the `source` and `message_id` contained in `disposition_info`]
+ if ((messenger_disposition_info = create_messenger_disposition_info(disposition_info)) == NULL)
+ {
+ // Codes_SRS_DEVICE_09_114: [If the MESSENGER_MESSAGE_DISPOSITION_INFO fails to be created, device_send_message_disposition() shall fail and return __FAILURE__]
+ LogError("Failed sending message disposition (failed to create MESSENGER_MESSAGE_DISPOSITION_INFO)");
+ result = __FAILURE__;
+ }
+ else
+ {
+ MESSENGER_DISPOSITION_RESULT messenger_disposition_result = get_messenger_message_disposition_result_from(disposition_result);
+
+ // Codes_SRS_DEVICE_09_115: [`messenger_send_message_disposition()` shall be invoked passing the MESSENGER_MESSAGE_DISPOSITION_INFO instance and the corresponding MESSENGER_DISPOSITION_RESULT]
+ if (messenger_send_message_disposition(device->messenger_handle, messenger_disposition_info, messenger_disposition_result) != RESULT_OK)
+ {
+ // Codes_SRS_DEVICE_09_116: [If `messenger_send_message_disposition()` fails, device_send_message_disposition() shall fail and return __FAILURE__]
+ LogError("Failed sending message disposition (messenger_send_message_disposition failed)");
+ result = __FAILURE__;
+ }
+ else
+ {
+ // Codes_SRS_DEVICE_09_118: [If no failures occurr, device_send_message_disposition() shall return 0]
+ result = RESULT_OK;
+ }
+
+ // Codes_SRS_DEVICE_09_117: [device_send_message_disposition() shall destroy the MESSENGER_MESSAGE_DISPOSITION_INFO instance]
+ destroy_messenger_disposition_info(messenger_disposition_info);
+ }
+ }
+
+ return result;
+}
+
+int device_set_retry_policy(DEVICE_HANDLE handle, IOTHUB_CLIENT_RETRY_POLICY policy, size_t retry_timeout_limit_in_seconds)
+{
+ (void)retry_timeout_limit_in_seconds;
+ (void)policy;
+ int result;
+
+ // Codes_SRS_DEVICE_09_080: [If `handle` is NULL, device_set_retry_policy shall return a non-zero result]
+ if (handle == NULL)
+ {
+ LogError("Failed setting retry policy (handle is NULL)");
+ result = __FAILURE__;
+ }
+ else
+ {
+ // Codes_SRS_DEVICE_09_081: [device_set_retry_policy shall return a non-zero result]
+ LogError("Failed setting retry policy (functionality not supported)");
+ result = __FAILURE__;
+ }
+
+ return result;
+}
+
+int device_set_option(DEVICE_HANDLE handle, const char* name, void* value)
+{
+ int result;
+
+ // Codes_SRS_DEVICE_09_082: [If `handle` or `name` or `value` are NULL, device_set_option shall return a non-zero result]
+ if (handle == NULL || name == NULL || value == NULL)
+ {
+ LogError("failed setting device option (one of the followin are NULL: _handle=%p, name=%p, value=%p)",
+ handle, name, value);
+ result = __FAILURE__;
+ }
+ else
+ {
+ DEVICE_INSTANCE* instance = (DEVICE_INSTANCE*)handle;
+
+ if (strcmp(DEVICE_OPTION_CBS_REQUEST_TIMEOUT_SECS, name) == 0 ||
+ strcmp(DEVICE_OPTION_SAS_TOKEN_REFRESH_TIME_SECS, name) == 0 ||
+ strcmp(DEVICE_OPTION_SAS_TOKEN_LIFETIME_SECS, name) == 0)
+ {
+ // Codes_SRS_DEVICE_09_083: [If `name` refers to authentication but CBS authentication is not used, device_set_option shall return a non-zero result]
+ if (instance->authentication_handle == NULL)
+ {
+ LogError("failed setting option for device '%s' (cannot set authentication option '%s'; not using CBS authentication)", instance->config->device_id, name);
+ result = __FAILURE__;
+ }
+ // Codes_SRS_DEVICE_09_084: [If `name` refers to authentication, it shall be passed along with `value` to authentication_set_option]
+ else if(authentication_set_option(instance->authentication_handle, name, value) != RESULT_OK)
+ {
+ // Codes_SRS_DEVICE_09_085: [If authentication_set_option fails, device_set_option shall return a non-zero result]
+ LogError("failed setting option for device '%s' (failed setting authentication option '%s')", instance->config->device_id, name);
+ result = __FAILURE__;
+ }
+ else
+ {
+ result = RESULT_OK;
+ }
+ }
+ else if (strcmp(DEVICE_OPTION_EVENT_SEND_TIMEOUT_SECS, name) == 0)
+ {
+ // Codes_SRS_DEVICE_09_086: [If `name` refers to messenger module, it shall be passed along with `value` to messenger_set_option]
+ if (messenger_set_option(instance->messenger_handle, name, value) != RESULT_OK)
+ {
+ // Codes_SRS_DEVICE_09_087: [If messenger_set_option fails, device_set_option shall return a non-zero result]
+ LogError("failed setting option for device '%s' (failed setting messenger option '%s')", instance->config->device_id, name);
+ result = __FAILURE__;
+ }
+ else
+ {
+ result = RESULT_OK;
+ }
+ }
+ else if (strcmp(DEVICE_OPTION_SAVED_AUTH_OPTIONS, name) == 0)
+ {
+ // Codes_SRS_DEVICE_09_088: [If `name` is DEVICE_OPTION_SAVED_AUTH_OPTIONS but CBS authentication is not being used, device_set_option shall return a non-zero result]
+ if (instance->authentication_handle == NULL)
+ {
+ LogError("failed setting option for device '%s' (cannot set authentication option '%s'; not using CBS authentication)", instance->config->device_id, name);
+ result = __FAILURE__;
+ }
+ else if (OptionHandler_FeedOptions((OPTIONHANDLER_HANDLE)value, instance->authentication_handle) != OPTIONHANDLER_OK)
+ {
+ // Codes_SRS_DEVICE_09_091: [If any call to OptionHandler_FeedOptions fails, device_set_option shall return a non-zero result]
+ LogError("failed setting option for device '%s' (OptionHandler_FeedOptions failed for authentication instance)", instance->config->device_id);
+ result = __FAILURE__;
+ }
+ else
+ {
+ result = RESULT_OK;
+ }
+ }
+ else if (strcmp(DEVICE_OPTION_SAVED_MESSENGER_OPTIONS, name) == 0)
+ {
+ // Codes_SRS_DEVICE_09_089: [If `name` is DEVICE_OPTION_SAVED_MESSENGER_OPTIONS, `value` shall be fed to `instance->messenger_handle` using OptionHandler_FeedOptions]
+ if (OptionHandler_FeedOptions((OPTIONHANDLER_HANDLE)value, instance->messenger_handle) != OPTIONHANDLER_OK)
+ {
+ // Codes_SRS_DEVICE_09_091: [If any call to OptionHandler_FeedOptions fails, device_set_option shall return a non-zero result]
+ LogError("failed setting option for device '%s' (OptionHandler_FeedOptions failed for messenger instance)", instance->config->device_id);
+ result = __FAILURE__;
+ }
+ else
+ {
+ result = RESULT_OK;
+ }
+ }
+ else if (strcmp(DEVICE_OPTION_SAVED_OPTIONS, name) == 0)
+ {
+ // Codes_SRS_DEVICE_09_090: [If `name` is DEVICE_OPTION_SAVED_OPTIONS, `value` shall be fed to `instance` using OptionHandler_FeedOptions]
+ if (OptionHandler_FeedOptions((OPTIONHANDLER_HANDLE)value, handle) != OPTIONHANDLER_OK)
+ {
+ // Codes_SRS_DEVICE_09_091: [If any call to OptionHandler_FeedOptions fails, device_set_option shall return a non-zero result]
+ LogError("failed setting option for device '%s' (OptionHandler_FeedOptions failed)", instance->config->device_id);
+ result = __FAILURE__;
+ }
+ else
+ {
+ result = RESULT_OK;
+ }
+ }
+ else
+ {
+ // Codes_SRS_DEVICE_09_092: [If no failures occur, device_set_option shall return 0]
+ LogError("failed setting option for device '%s' (option with name '%s' is not suppported)", instance->config->device_id, name);
+ result = __FAILURE__;
+ }
+ }
+
+ return result;
+}
+
+OPTIONHANDLER_HANDLE device_retrieve_options(DEVICE_HANDLE handle)
+{
+ OPTIONHANDLER_HANDLE result;
+
+ // Codes_SRS_DEVICE_09_093: [If `handle` is NULL, device_retrieve_options shall return NULL]
+ if (handle == NULL)
+ {
+ LogError("Failed to retrieve options from device instance (handle is NULL)");
+ result = NULL;
+ }
+ else
+ {
+ // Codes_SRS_DEVICE_09_094: [A OPTIONHANDLER_HANDLE instance, aka `options` shall be created using OptionHandler_Create]
+ OPTIONHANDLER_HANDLE options = OptionHandler_Create(device_clone_option, device_destroy_option, (pfSetOption)device_set_option);
+
+ if (options == NULL)
+ {
+ // Codes_SRS_DEVICE_09_095: [If OptionHandler_Create fails, device_retrieve_options shall return NULL]
+ LogError("Failed to retrieve options from device instance (OptionHandler_Create failed)");
+ result = NULL;
+ }
+ else
+ {
+ DEVICE_INSTANCE* instance = (DEVICE_INSTANCE*)handle;
+
+ OPTIONHANDLER_HANDLE dependency_options = NULL;
+
+ // Codes_SRS_DEVICE_09_096: [If CBS authentication is used, `instance->authentication_handle` options shall be retrieved using authentication_retrieve_options]
+ if (instance->authentication_handle != NULL &&
+ (dependency_options = authentication_retrieve_options(instance->authentication_handle)) == NULL)
+ {
+ // Codes_SRS_DEVICE_09_097: [If authentication_retrieve_options fails, device_retrieve_options shall return NULL]
+ LogError("Failed to retrieve options from device '%s' (failed to retrieve options from authentication instance)", instance->config->device_id);
+ result = NULL;
+ }
+ // Codes_SRS_DEVICE_09_098: [The authentication options shall be added to `options` using OptionHandler_AddOption as DEVICE_OPTION_SAVED_AUTH_OPTIONS]
+ else if (instance->authentication_handle != NULL &&
+ OptionHandler_AddOption(options, DEVICE_OPTION_SAVED_AUTH_OPTIONS, (const void*)dependency_options) != OPTIONHANDLER_OK)
+ {
+ // Codes_SRS_DEVICE_09_102: [If any call to OptionHandler_AddOption fails, device_retrieve_options shall return NULL]
+ LogError("Failed to retrieve options from device '%s' (OptionHandler_AddOption failed for option '%s')", instance->config->device_id, DEVICE_OPTION_SAVED_AUTH_OPTIONS);
+ result = NULL;
+ }
+ // Codes_SRS_DEVICE_09_099: [`instance->messenger_handle` options shall be retrieved using messenger_retrieve_options]
+ else if ((dependency_options = messenger_retrieve_options(instance->messenger_handle)) == NULL)
+ {
+ // Codes_SRS_DEVICE_09_100: [If messenger_retrieve_options fails, device_retrieve_options shall return NULL]
+ LogError("Failed to retrieve options from device '%s' (failed to retrieve options from messenger instance)", instance->config->device_id);
+ result = NULL;
+ }
+ // Codes_SRS_DEVICE_09_101: [The messenger options shall be added to `options` using OptionHandler_AddOption as DEVICE_OPTION_SAVED_MESSENGER_OPTIONS]
+ else if (OptionHandler_AddOption(options, DEVICE_OPTION_SAVED_MESSENGER_OPTIONS, (const void*)dependency_options) != OPTIONHANDLER_OK)
+ {
+ // Codes_SRS_DEVICE_09_102: [If any call to OptionHandler_AddOption fails, device_retrieve_options shall return NULL]
+ LogError("Failed to retrieve options from device '%s' (OptionHandler_AddOption failed for option '%s')", instance->config->device_id, DEVICE_OPTION_SAVED_MESSENGER_OPTIONS);
+ result = NULL;
+ }
+ else
+ {
+ // Codes_SRS_DEVICE_09_104: [If no failures occur, a handle to `options` shall be return]
+ result = options;
+ }
+
+ if (result == NULL)
+ {
+ // Codes_SRS_DEVICE_09_103: [If any failure occurs, any memory allocated by device_retrieve_options shall be destroyed]
+ OptionHandler_Destroy(options);
+ }
+ }
+ }
+
+ return result;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/iothubtransport_amqp_device.h Fri Mar 10 11:46:55 2017 -0800
@@ -0,0 +1,111 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+#ifndef IOTHUBTRANSPORTAMQP_AMQP_DEVICE_H
+#define IOTHUBTRANSPORTAMQP_AMQP_DEVICE_H
+
+#include "azure_c_shared_utility/umock_c_prod.h"
+#include "azure_c_shared_utility/optionhandler.h"
+#include "azure_uamqp_c/session.h"
+#include "azure_uamqp_c/cbs.h"
+#include "iothub_message.h"
+#include "iothub_client_private.h"
+#include "iothubtransport_amqp_device.h"
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+// @brief name of option to apply the instance obtained using device_retrieve_options
+static const char* DEVICE_OPTION_SAVED_OPTIONS = "saved_device_options";
+static const char* DEVICE_OPTION_EVENT_SEND_TIMEOUT_SECS = "event_send_timeout_secs";
+static const char* DEVICE_OPTION_CBS_REQUEST_TIMEOUT_SECS = "cbs_request_timeout_secs";
+static const char* DEVICE_OPTION_SAS_TOKEN_REFRESH_TIME_SECS = "sas_token_refresh_time_secs";
+static const char* DEVICE_OPTION_SAS_TOKEN_LIFETIME_SECS = "sas_token_lifetime_secs";
+
+typedef enum DEVICE_STATE_TAG
+{
+ DEVICE_STATE_STOPPED,
+ DEVICE_STATE_STOPPING,
+ DEVICE_STATE_STARTING,
+ DEVICE_STATE_STARTED,
+ DEVICE_STATE_ERROR_AUTH,
+ DEVICE_STATE_ERROR_AUTH_TIMEOUT,
+ DEVICE_STATE_ERROR_MSG
+} DEVICE_STATE;
+
+typedef enum DEVICE_AUTH_MODE_TAG
+{
+ DEVICE_AUTH_MODE_CBS,
+ DEVICE_AUTH_MODE_X509
+} DEVICE_AUTH_MODE;
+
+typedef enum DEVICE_SEND_STATUS_TAG
+{
+ DEVICE_SEND_STATUS_IDLE,
+ DEVICE_SEND_STATUS_BUSY
+} DEVICE_SEND_STATUS;
+
+typedef enum D2C_EVENT_SEND_RESULT_TAG
+{
+ D2C_EVENT_SEND_COMPLETE_RESULT_OK,
+ D2C_EVENT_SEND_COMPLETE_RESULT_ERROR_CANNOT_PARSE,
+ D2C_EVENT_SEND_COMPLETE_RESULT_ERROR_FAIL_SENDING,
+ D2C_EVENT_SEND_COMPLETE_RESULT_ERROR_TIMEOUT,
+ D2C_EVENT_SEND_COMPLETE_RESULT_DEVICE_DESTROYED,
+ D2C_EVENT_SEND_COMPLETE_RESULT_ERROR_UNKNOWN
+} D2C_EVENT_SEND_RESULT;
+
+typedef enum DEVICE_MESSAGE_DISPOSITION_RESULT_TAG
+{
+ DEVICE_MESSAGE_DISPOSITION_RESULT_NONE,
+ DEVICE_MESSAGE_DISPOSITION_RESULT_ACCEPTED,
+ DEVICE_MESSAGE_DISPOSITION_RESULT_REJECTED,
+ DEVICE_MESSAGE_DISPOSITION_RESULT_RELEASED
+} DEVICE_MESSAGE_DISPOSITION_RESULT;
+
+typedef struct DEVICE_MESSAGE_DISPOSITION_INFO_TAG
+{
+ unsigned long message_id;
+ char* source;
+} DEVICE_MESSAGE_DISPOSITION_INFO;
+
+typedef void(*ON_DEVICE_STATE_CHANGED)(void* context, DEVICE_STATE previous_state, DEVICE_STATE new_state);
+typedef DEVICE_MESSAGE_DISPOSITION_RESULT(*ON_DEVICE_C2D_MESSAGE_RECEIVED)(IOTHUB_MESSAGE_HANDLE message, DEVICE_MESSAGE_DISPOSITION_INFO* disposition_info, void* context);
+typedef void(*ON_DEVICE_D2C_EVENT_SEND_COMPLETE)(IOTHUB_MESSAGE_LIST* message, D2C_EVENT_SEND_RESULT result, void* context);
+
+typedef struct DEVICE_CONFIG_TAG
+{
+ char* device_id;
+ char* iothub_host_fqdn;
+ DEVICE_AUTH_MODE authentication_mode;
+ ON_DEVICE_STATE_CHANGED on_state_changed_callback;
+ void* on_state_changed_context;
+
+ char* device_primary_key;
+ char* device_secondary_key;
+ char* device_sas_token;
+} DEVICE_CONFIG;
+
+typedef struct DEVICE_INSTANCE* DEVICE_HANDLE;
+
+MOCKABLE_FUNCTION(, DEVICE_HANDLE, device_create, DEVICE_CONFIG*, config);
+MOCKABLE_FUNCTION(, void, device_destroy, DEVICE_HANDLE, handle);
+MOCKABLE_FUNCTION(, int, device_start_async, DEVICE_HANDLE, handle, SESSION_HANDLE, session_handle, CBS_HANDLE, cbs_handle);
+MOCKABLE_FUNCTION(, int, device_stop, DEVICE_HANDLE, handle);
+MOCKABLE_FUNCTION(, void, device_do_work, DEVICE_HANDLE, handle);
+MOCKABLE_FUNCTION(, int, device_send_event_async, DEVICE_HANDLE, handle, IOTHUB_MESSAGE_LIST*, message, ON_DEVICE_D2C_EVENT_SEND_COMPLETE, on_device_d2c_event_send_complete_callback, void*, context);
+MOCKABLE_FUNCTION(, int, device_get_send_status, DEVICE_HANDLE, handle, DEVICE_SEND_STATUS*, send_status);
+MOCKABLE_FUNCTION(, int, device_subscribe_message, DEVICE_HANDLE, handle, ON_DEVICE_C2D_MESSAGE_RECEIVED, on_message_received_callback, void*, context);
+MOCKABLE_FUNCTION(, int, device_unsubscribe_message, DEVICE_HANDLE, handle);
+MOCKABLE_FUNCTION(, int, device_send_message_disposition, DEVICE_HANDLE, device_handle, DEVICE_MESSAGE_DISPOSITION_INFO*, disposition_info, DEVICE_MESSAGE_DISPOSITION_RESULT, disposition_result);
+MOCKABLE_FUNCTION(, int, device_set_retry_policy, DEVICE_HANDLE, handle, IOTHUB_CLIENT_RETRY_POLICY, policy, size_t, retry_timeout_limit_in_seconds);
+MOCKABLE_FUNCTION(, int, device_set_option, DEVICE_HANDLE, handle, const char*, name, void*, value);
+MOCKABLE_FUNCTION(, OPTIONHANDLER_HANDLE, device_retrieve_options, DEVICE_HANDLE, handle);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // IOTHUBTRANSPORTAMQP_AMQP_DEVICE_H
\ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/iothubtransport_amqp_messenger.c Fri Mar 10 11:46:55 2017 -0800
@@ -0,0 +1,1887 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+#include <stdlib.h>
+#include <stdbool.h>
+#include "azure_c_shared_utility/optimize_size.h"
+#include "azure_c_shared_utility/crt_abstractions.h"
+#include "azure_c_shared_utility/gballoc.h"
+#include "azure_c_shared_utility/agenttime.h"
+#include "azure_c_shared_utility/xlogging.h"
+#include "azure_c_shared_utility/uniqueid.h"
+#include "azure_c_shared_utility/singlylinkedlist.h"
+#include "azure_uamqp_c/link.h"
+#include "azure_uamqp_c/messaging.h"
+#include "azure_uamqp_c/message_sender.h"
+#include "azure_uamqp_c/message_receiver.h"
+#include "uamqp_messaging.h"
+#include "iothub_client_private.h"
+#include "iothub_client_version.h"
+#include "iothubtransport_amqp_messenger.h"
+
+#define RESULT_OK 0
+#define INDEFINITE_TIME ((time_t)(-1))
+
+#define IOTHUB_DEVICES_PATH_FMT "%s/devices/%s"
+#define IOTHUB_EVENT_SEND_ADDRESS_FMT "amqps://%s/messages/events"
+#define IOTHUB_MESSAGE_RECEIVE_ADDRESS_FMT "amqps://%s/messages/devicebound"
+#define MESSAGE_SENDER_LINK_NAME_PREFIX "link-snd"
+#define MESSAGE_SENDER_MAX_LINK_SIZE UINT64_MAX
+#define MESSAGE_RECEIVER_LINK_NAME_PREFIX "link-rcv"
+#define MESSAGE_RECEIVER_MAX_LINK_SIZE 65536
+#define DEFAULT_EVENT_SEND_RETRY_LIMIT 10
+#define DEFAULT_EVENT_SEND_TIMEOUT_SECS 600
+#define MAX_MESSAGE_SENDER_STATE_CHANGE_TIMEOUT_SECS 300
+#define MAX_MESSAGE_RECEIVER_STATE_CHANGE_TIMEOUT_SECS 300
+#define UNIQUE_ID_BUFFER_SIZE 37
+#define STRING_NULL_TERMINATOR '\0'
+
+typedef struct MESSENGER_INSTANCE_TAG
+{
+ STRING_HANDLE device_id;
+ STRING_HANDLE iothub_host_fqdn;
+ SINGLYLINKEDLIST_HANDLE waiting_to_send;
+ SINGLYLINKEDLIST_HANDLE in_progress_list;
+ MESSENGER_STATE state;
+
+ ON_MESSENGER_STATE_CHANGED_CALLBACK on_state_changed_callback;
+ void* on_state_changed_context;
+
+ bool receive_messages;
+ ON_MESSENGER_MESSAGE_RECEIVED on_message_received_callback;
+ void* on_message_received_context;
+
+ SESSION_HANDLE session_handle;
+ LINK_HANDLE sender_link;
+ MESSAGE_SENDER_HANDLE message_sender;
+ MESSAGE_SENDER_STATE message_sender_current_state;
+ MESSAGE_SENDER_STATE message_sender_previous_state;
+ LINK_HANDLE receiver_link;
+ MESSAGE_RECEIVER_HANDLE message_receiver;
+ MESSAGE_RECEIVER_STATE message_receiver_current_state;
+ MESSAGE_RECEIVER_STATE message_receiver_previous_state;
+
+ size_t event_send_retry_limit;
+ size_t event_send_error_count;
+ size_t event_send_timeout_secs;
+ time_t last_message_sender_state_change_time;
+ time_t last_message_receiver_state_change_time;
+} MESSENGER_INSTANCE;
+
+typedef struct MESSENGER_SEND_EVENT_TASK_TAG
+{
+ IOTHUB_MESSAGE_LIST* message;
+ ON_MESSENGER_EVENT_SEND_COMPLETE on_event_send_complete_callback;
+ void* context;
+ time_t send_time;
+ MESSENGER_INSTANCE *messenger;
+ bool is_timed_out;
+} MESSENGER_SEND_EVENT_TASK;
+
+// @brief
+// Evaluates if the ammount of time since start_time is greater or lesser than timeout_in_secs.
+// @param is_timed_out
+// Set to 1 if a timeout has been reached, 0 otherwise. Not set if any failure occurs.
+// @returns
+// 0 if no failures occur, non-zero otherwise.
+static int is_timeout_reached(time_t start_time, size_t timeout_in_secs, int *is_timed_out)
+{
+ int result;
+
+ if (start_time == INDEFINITE_TIME)
+ {
+ LogError("Failed to verify timeout (start_time is INDEFINITE)");
+ result = __FAILURE__;
+ }
+ else
+ {
+ time_t current_time;
+
+ if ((current_time = get_time(NULL)) == INDEFINITE_TIME)
+ {
+ LogError("Failed to verify timeout (get_time failed)");
+ result = __FAILURE__;
+ }
+ else
+ {
+ if (get_difftime(current_time, start_time) >= timeout_in_secs)
+ {
+ *is_timed_out = 1;
+ }
+ else
+ {
+ *is_timed_out = 0;
+ }
+
+ result = RESULT_OK;
+ }
+ }
+
+ return result;
+}
+
+static STRING_HANDLE create_devices_path(STRING_HANDLE iothub_host_fqdn, STRING_HANDLE device_id)
+{
+ STRING_HANDLE devices_path;
+
+ if ((devices_path = STRING_new()) == NULL)
+ {
+ LogError("Failed creating devices_path (STRING_new failed)");
+ }
+ else
+ {
+ const char* iothub_host_fqdn_char_ptr = STRING_c_str(iothub_host_fqdn);
+ const char* device_id_char_ptr = STRING_c_str(device_id);
+ if (STRING_sprintf(devices_path, IOTHUB_DEVICES_PATH_FMT, iothub_host_fqdn_char_ptr, device_id_char_ptr) != RESULT_OK)
+ {
+ STRING_delete(devices_path);
+ devices_path = NULL;
+ LogError("Failed creating devices_path (STRING_sprintf failed)");
+ }
+ }
+
+ return devices_path;
+}
+
+static STRING_HANDLE create_event_send_address(STRING_HANDLE devices_path)
+{
+ STRING_HANDLE event_send_address;
+
+ if ((event_send_address = STRING_new()) == NULL)
+ {
+ LogError("Failed creating the event_send_address (STRING_new failed)");
+ }
+ else
+ {
+ const char* devices_path_char_ptr = STRING_c_str(devices_path);
+ if (STRING_sprintf(event_send_address, IOTHUB_EVENT_SEND_ADDRESS_FMT, devices_path_char_ptr) != RESULT_OK)
+ {
+ STRING_delete(event_send_address);
+ event_send_address = NULL;
+ LogError("Failed creating the event_send_address (STRING_sprintf failed)");
+ }
+ }
+
+ return event_send_address;
+}
+
+static STRING_HANDLE create_event_sender_source_name(STRING_HANDLE link_name)
+{
+ STRING_HANDLE source_name;
+
+ if ((source_name = STRING_new()) == NULL)
+ {
+ LogError("Failed creating the source_name (STRING_new failed)");
+ }
+ else
+ {
+ const char* link_name_char_ptr = STRING_c_str(link_name);
+ if (STRING_sprintf(source_name, "%s-source", link_name_char_ptr) != RESULT_OK)
+ {
+ STRING_delete(source_name);
+ source_name = NULL;
+ LogError("Failed creating the source_name (STRING_sprintf failed)");
+ }
+ }
+
+ return source_name;
+}
+
+static STRING_HANDLE create_message_receive_address(STRING_HANDLE devices_path)
+{
+ STRING_HANDLE message_receive_address;
+
+ if ((message_receive_address = STRING_new()) == NULL)
+ {
+ LogError("Failed creating the message_receive_address (STRING_new failed)");
+ }
+ else
+ {
+ const char* devices_path_char_ptr = STRING_c_str(devices_path);
+ if (STRING_sprintf(message_receive_address, IOTHUB_MESSAGE_RECEIVE_ADDRESS_FMT, devices_path_char_ptr) != RESULT_OK)
+ {
+ STRING_delete(message_receive_address);
+ message_receive_address = NULL;
+ LogError("Failed creating the message_receive_address (STRING_sprintf failed)");
+ }
+ }
+
+ return message_receive_address;
+}
+
+static STRING_HANDLE create_message_receiver_target_name(STRING_HANDLE link_name)
+{
+ STRING_HANDLE target_name;
+
+ if ((target_name = STRING_new()) == NULL)
+ {
+ LogError("Failed creating the target_name (STRING_new failed)");
+ }
+ else
+ {
+ const char* link_name_char_ptr = STRING_c_str(link_name);
+ if (STRING_sprintf(target_name, "%s-target", link_name_char_ptr) != RESULT_OK)
+ {
+ STRING_delete(target_name);
+ target_name = NULL;
+ LogError("Failed creating the target_name (STRING_sprintf failed)");
+ }
+ }
+
+ return target_name;
+}
+
+static STRING_HANDLE create_link_name(const char* prefix, const char* infix)
+{
+ char* unique_id;
+ STRING_HANDLE tag = NULL;
+
+ if ((unique_id = (char*)malloc(sizeof(char) * UNIQUE_ID_BUFFER_SIZE + 1)) == NULL)
+ {
+ LogError("Failed generating an unique tag (malloc failed)");
+ }
+ else
+ {
+ memset(unique_id, 0, sizeof(char) * UNIQUE_ID_BUFFER_SIZE + 1);
+
+ if (UniqueId_Generate(unique_id, UNIQUE_ID_BUFFER_SIZE) != UNIQUEID_OK)
+ {
+ LogError("Failed generating an unique tag (UniqueId_Generate failed)");
+ }
+ else if ((tag = STRING_new()) == NULL)
+ {
+ LogError("Failed generating an unique tag (STRING_new failed)");
+ }
+ else if (STRING_sprintf(tag, "%s-%s-%s", prefix, infix, unique_id) != RESULT_OK)
+ {
+ STRING_delete(tag);
+ tag = NULL;
+ LogError("Failed generating an unique tag (STRING_sprintf failed)");
+ }
+
+ free(unique_id);
+ }
+
+ return tag;
+}
+
+static void update_messenger_state(MESSENGER_INSTANCE* instance, MESSENGER_STATE new_state)
+{
+ if (new_state != instance->state)
+ {
+ MESSENGER_STATE previous_state = instance->state;
+ instance->state = new_state;
+
+ if (instance->on_state_changed_callback != NULL)
+ {
+ instance->on_state_changed_callback(instance->on_state_changed_context, previous_state, new_state);
+ }
+ }
+}
+
+static void attach_device_client_type_to_link(LINK_HANDLE link)
+{
+ fields attach_properties;
+ AMQP_VALUE device_client_type_key_name;
+ AMQP_VALUE device_client_type_value;
+ int result;
+
+ if ((attach_properties = amqpvalue_create_map()) == NULL)
+ {
+ LogError("Failed to create the map for device client type.");
+ }
+ else
+ {
+ if ((device_client_type_key_name = amqpvalue_create_symbol("com.microsoft:client-version")) == NULL)
+ {
+ LogError("Failed to create the key name for the device client type.");
+ }
+ else
+ {
+ if ((device_client_type_value = amqpvalue_create_string(CLIENT_DEVICE_TYPE_PREFIX CLIENT_DEVICE_BACKSLASH IOTHUB_SDK_VERSION)) == NULL)
+ {
+ LogError("Failed to create the key value for the device client type.");
+ }
+ else
+ {
+ if ((result = amqpvalue_set_map_value(attach_properties, device_client_type_key_name, device_client_type_value)) != 0)
+ {
+ LogError("Failed to set the property map for the device client type (error code is: %d)", result);
+ }
+ else if ((result = link_set_attach_properties(link, attach_properties)) != 0)
+ {
+ LogError("Unable to attach the device client type to the link properties (error code is: %d)", result);
+ }
+
+ amqpvalue_destroy(device_client_type_value);
+ }
+
+ amqpvalue_destroy(device_client_type_key_name);
+ }
+
+ amqpvalue_destroy(attach_properties);
+ }
+}
+
+static void destroy_event_sender(MESSENGER_INSTANCE* instance)
+{
+ if (instance->message_sender != NULL)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_060: [`instance->message_sender` shall be destroyed using messagesender_destroy()]
+ messagesender_destroy(instance->message_sender);
+ instance->message_sender = NULL;
+ instance->message_sender_current_state = MESSAGE_SENDER_STATE_IDLE;
+ instance->message_sender_previous_state = MESSAGE_SENDER_STATE_IDLE;
+ instance->last_message_sender_state_change_time = INDEFINITE_TIME;
+ }
+
+ if (instance->sender_link != NULL)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_063: [`instance->sender_link` shall be destroyed using link_destroy()]
+ link_destroy(instance->sender_link);
+ instance->sender_link = NULL;
+ }
+}
+
+static void on_event_sender_state_changed_callback(void* context, MESSAGE_SENDER_STATE new_state, MESSAGE_SENDER_STATE previous_state)
+{
+ if (context == NULL)
+ {
+ LogError("on_event_sender_state_changed_callback was invoked with a NULL context; although unexpected, this failure will be ignored");
+ }
+ else
+ {
+ if (new_state != previous_state)
+ {
+ MESSENGER_INSTANCE* instance = (MESSENGER_INSTANCE*)context;
+ instance->message_sender_current_state = new_state;
+ instance->message_sender_previous_state = previous_state;
+ instance->last_message_sender_state_change_time = get_time(NULL);
+ }
+ }
+}
+
+static int create_event_sender(MESSENGER_INSTANCE* instance)
+{
+ int result;
+
+ STRING_HANDLE link_name = NULL;
+ STRING_HANDLE source_name = NULL;
+ AMQP_VALUE source = NULL;
+ AMQP_VALUE target = NULL;
+ STRING_HANDLE devices_path = NULL;
+ STRING_HANDLE event_send_address = NULL;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_033: [A variable, named `devices_path`, shall be created concatenating `instance->iothub_host_fqdn`, "/devices/" and `instance->device_id`]
+ if ((devices_path = create_devices_path(instance->iothub_host_fqdn, instance->device_id)) == NULL)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_034: [If `devices_path` fails to be created, messenger_do_work() shall fail and return]
+ result = __FAILURE__;
+ LogError("Failed creating the message sender (failed creating the 'devices_path')");
+ }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_035: [A variable, named `event_send_address`, shall be created concatenating "amqps://", `devices_path` and "/messages/events"]
+ else if ((event_send_address = create_event_send_address(devices_path)) == NULL)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_036: [If `event_send_address` fails to be created, messenger_do_work() shall fail and return]
+ result = __FAILURE__;
+ LogError("Failed creating the message sender (failed creating the 'event_send_address')");
+ }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_037: [A `link_name` variable shall be created using an unique string label per AMQP session]
+ else if ((link_name = create_link_name(MESSAGE_SENDER_LINK_NAME_PREFIX, STRING_c_str(instance->device_id))) == NULL)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_038: [If `link_name` fails to be created, messenger_do_work() shall fail and return]
+ result = __FAILURE__;
+ LogError("Failed creating the message sender (failed creating an unique link name)");
+ }
+ else if ((source_name = create_event_sender_source_name(link_name)) == NULL)
+ {
+ result = __FAILURE__;
+ LogError("Failed creating the message sender (failed creating an unique source name)");
+ }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_039: [A `source` variable shall be created with messaging_create_source() using an unique string label per AMQP session]
+ else if ((source = messaging_create_source(STRING_c_str(source_name))) == NULL)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_040: [If `source` fails to be created, messenger_do_work() shall fail and return]
+ result = __FAILURE__;
+ LogError("Failed creating the message sender (messaging_create_source failed)");
+ }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_041: [A `target` variable shall be created with messaging_create_target() using `event_send_address`]
+ else if ((target = messaging_create_target(STRING_c_str(event_send_address))) == NULL)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_042: [If `target` fails to be created, messenger_do_work() shall fail and return]
+ result = __FAILURE__;
+ LogError("Failed creating the message sender (messaging_create_target failed)");
+ }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_043: [`instance->sender_link` shall be set using link_create(), passing `instance->session_handle`, `link_name`, "role_sender", `source` and `target` as parameters]
+ else if ((instance->sender_link = link_create(instance->session_handle, STRING_c_str(link_name), role_sender, source, target)) == NULL)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_044: [If link_create() fails, messenger_do_work() shall fail and return]
+ result = __FAILURE__;
+ LogError("Failed creating the message sender (link_create failed)");
+ }
+ else
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_047: [`instance->sender_link` maximum message size shall be set to UINT64_MAX using link_set_max_message_size()]
+ if (link_set_max_message_size(instance->sender_link, MESSAGE_SENDER_MAX_LINK_SIZE) != RESULT_OK)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_048: [If link_set_max_message_size() fails, it shall be logged and ignored.]
+ LogError("Failed setting message sender link max message size.");
+ }
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_049: [`instance->sender_link` should have a property "com.microsoft:client-version" set as `CLIENT_DEVICE_TYPE_PREFIX/IOTHUB_SDK_VERSION`, using amqpvalue_set_map_value() and link_set_attach_properties()]
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_050: [If amqpvalue_set_map_value() or link_set_attach_properties() fail, the failure shall be ignored]
+ attach_device_client_type_to_link(instance->sender_link);
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_051: [`instance->message_sender` shall be created using messagesender_create(), passing the `instance->sender_link` and `on_event_sender_state_changed_callback`]
+ if ((instance->message_sender = messagesender_create(instance->sender_link, on_event_sender_state_changed_callback, (void*)instance)) == NULL)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_052: [If messagesender_create() fails, messenger_do_work() shall fail and return]
+ result = __FAILURE__;
+ link_destroy(instance->sender_link);
+ instance->sender_link = NULL;
+ LogError("Failed creating the message sender (messagesender_create failed)");
+ }
+ else
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_053: [`instance->message_sender` shall be opened using messagesender_open()]
+ if (messagesender_open(instance->message_sender) != RESULT_OK)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_054: [If messagesender_open() fails, messenger_do_work() shall fail and return]
+ result = __FAILURE__;
+ messagesender_destroy(instance->message_sender);
+ instance->message_sender = NULL;
+ link_destroy(instance->sender_link);
+ instance->sender_link = NULL;
+ LogError("Failed opening the AMQP message sender.");
+ }
+ else
+ {
+ result = RESULT_OK;
+ }
+ }
+ }
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_055: [Before returning, messenger_do_work() shall release all the temporary memory it has allocated]
+ if (link_name != NULL)
+ STRING_delete(link_name);
+ if (source_name != NULL)
+ STRING_delete(source_name);
+ if (source != NULL)
+ amqpvalue_destroy(source);
+ if (target != NULL)
+ amqpvalue_destroy(target);
+ if (devices_path != NULL)
+ STRING_delete(devices_path);
+ if (event_send_address != NULL)
+ STRING_delete(event_send_address);
+
+ return result;
+}
+
+static void destroy_message_receiver(MESSENGER_INSTANCE* instance)
+{
+ if (instance->message_receiver != NULL)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_061: [`instance->message_receiver` shall be closed using messagereceiver_close()]
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_093: [`instance->message_receiver` shall be closed using messagereceiver_close()]
+ if (messagereceiver_close(instance->message_receiver) != RESULT_OK)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_094: [If messagereceiver_close() fails, it shall be logged and ignored]
+ LogError("Failed closing the AMQP message receiver (this failure will be ignored).");
+ }
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_062: [`instance->message_receiver` shall be destroyed using messagereceiver_destroy()]
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_095: [`instance->message_receiver` shall be destroyed using messagereceiver_destroy()]
+ messagereceiver_destroy(instance->message_receiver);
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_096: [`instance->message_receiver` shall be set to NULL]
+ instance->message_receiver = NULL;
+ instance->message_receiver_current_state = MESSAGE_RECEIVER_STATE_IDLE;
+ instance->message_receiver_previous_state = MESSAGE_RECEIVER_STATE_IDLE;
+ instance->last_message_receiver_state_change_time = INDEFINITE_TIME;
+ }
+
+ if (instance->receiver_link != NULL)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_064: [`instance->receiver_link` shall be destroyed using link_destroy()]
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_097: [`instance->receiver_link` shall be destroyed using link_destroy()]
+ link_destroy(instance->receiver_link);
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_098: [`instance->receiver_link` shall be set to NULL]
+ instance->receiver_link = NULL;
+ }
+}
+
+static void on_message_receiver_state_changed_callback(const void* context, MESSAGE_RECEIVER_STATE new_state, MESSAGE_RECEIVER_STATE previous_state)
+{
+ if (context == NULL)
+ {
+ LogError("on_message_receiver_state_changed_callback was invoked with a NULL context; although unexpected, this failure will be ignored");
+ }
+ else
+ {
+ if (new_state != previous_state)
+ {
+ MESSENGER_INSTANCE* instance = (MESSENGER_INSTANCE*)context;
+ instance->message_receiver_current_state = new_state;
+ instance->message_receiver_previous_state = previous_state;
+ instance->last_message_receiver_state_change_time = get_time(NULL);
+ }
+ }
+}
+
+static MESSENGER_MESSAGE_DISPOSITION_INFO* create_message_disposition_info(MESSENGER_INSTANCE* messenger)
+{
+ MESSENGER_MESSAGE_DISPOSITION_INFO* result;
+
+ if ((result = (MESSENGER_MESSAGE_DISPOSITION_INFO*)malloc(sizeof(MESSENGER_MESSAGE_DISPOSITION_INFO))) == NULL)
+ {
+ LogError("Failed creating MESSENGER_MESSAGE_DISPOSITION_INFO container (malloc failed)");
+ result = NULL;
+ }
+ else
+ {
+ delivery_number message_id;
+
+ if (messagereceiver_get_received_message_id(messenger->message_receiver, &message_id) != RESULT_OK)
+ {
+ LogError("Failed creating MESSENGER_MESSAGE_DISPOSITION_INFO container (messagereceiver_get_received_message_id failed)");
+ free(result);
+ result = NULL;
+ }
+ else
+ {
+ const char* link_name;
+
+ if (messagereceiver_get_link_name(messenger->message_receiver, &link_name) != RESULT_OK)
+ {
+ LogError("Failed creating MESSENGER_MESSAGE_DISPOSITION_INFO container (messagereceiver_get_link_name failed)");
+ free(result);
+ result = NULL;
+ }
+ else if (mallocAndStrcpy_s(&result->source, link_name) != RESULT_OK)
+ {
+ LogError("Failed creating MESSENGER_MESSAGE_DISPOSITION_INFO container (failed copying link name)");
+ free(result);
+ result = NULL;
+ }
+ else
+ {
+ result->message_id = message_id;
+ }
+ }
+ }
+
+ return result;
+}
+
+static void destroy_message_disposition_info(MESSENGER_MESSAGE_DISPOSITION_INFO* disposition_info)
+{
+ free(disposition_info->source);
+ free(disposition_info);
+}
+
+static AMQP_VALUE create_uamqp_disposition_result_from(MESSENGER_DISPOSITION_RESULT disposition_result)
+{
+ AMQP_VALUE uamqp_disposition_result;
+
+ if (disposition_result == MESSENGER_DISPOSITION_RESULT_NONE)
+ {
+ uamqp_disposition_result = NULL; // intentionally not sending an answer.
+ }
+ else if (disposition_result == MESSENGER_DISPOSITION_RESULT_ACCEPTED)
+ {
+ uamqp_disposition_result = messaging_delivery_accepted();
+ }
+ else if (disposition_result == MESSENGER_DISPOSITION_RESULT_RELEASED)
+ {
+ uamqp_disposition_result = messaging_delivery_released();
+ }
+ else if (disposition_result == MESSENGER_DISPOSITION_RESULT_REJECTED)
+ {
+ uamqp_disposition_result = messaging_delivery_rejected("Rejected by application", "Rejected by application");
+ }
+ else
+ {
+ LogError("Failed creating a disposition result for messagereceiver (result %d is not supported)", disposition_result);
+ uamqp_disposition_result = NULL;
+ }
+
+ return uamqp_disposition_result;
+}
+
+static AMQP_VALUE on_message_received_internal_callback(const void* context, MESSAGE_HANDLE message)
+{
+ AMQP_VALUE result;
+ int api_call_result;
+ IOTHUB_MESSAGE_HANDLE iothub_message;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_121: [An IOTHUB_MESSAGE_HANDLE shall be obtained from MESSAGE_HANDLE using IoTHubMessage_CreateFromUamqpMessage()]
+ if ((api_call_result = IoTHubMessage_CreateFromUamqpMessage(message, &iothub_message)) != RESULT_OK)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_122: [If IoTHubMessage_CreateFromUamqpMessage() fails, on_message_received_internal_callback shall return the result of messaging_delivery_rejected()]
+ result = messaging_delivery_rejected("Rejected due to failure reading AMQP message", "Failed reading AMQP message");
+
+ LogError("on_message_received_internal_callback failed (IoTHubMessage_CreateFromUamqpMessage; error = %d).", api_call_result);
+ }
+ else
+ {
+ MESSENGER_INSTANCE* instance = (MESSENGER_INSTANCE*)context;
+ MESSENGER_MESSAGE_DISPOSITION_INFO* message_disposition_info;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_186: [A MESSENGER_MESSAGE_DISPOSITION_INFO instance shall be created containing the source link name and message delivery ID]
+ if ((message_disposition_info = create_message_disposition_info(instance)) == NULL)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_187: [**If the MESSENGER_MESSAGE_DISPOSITION_INFO instance fails to be created, on_message_received_internal_callback shall return messaging_delivery_released()]
+ LogError("on_message_received_internal_callback failed (failed creating MESSENGER_MESSAGE_DISPOSITION_INFO).");
+ result = messaging_delivery_released();
+ }
+ else
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_123: [`instance->on_message_received_callback` shall be invoked passing the IOTHUB_MESSAGE_HANDLE and MESSENGER_MESSAGE_DISPOSITION_INFO instance]
+ MESSENGER_DISPOSITION_RESULT disposition_result = instance->on_message_received_callback(iothub_message, message_disposition_info, instance->on_message_received_context);
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_188: [The memory allocated for the MESSENGER_MESSAGE_DISPOSITION_INFO instance shall be released]
+ destroy_message_disposition_info(message_disposition_info);
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_125: [If `instance->on_message_received_callback` returns MESSENGER_DISPOSITION_RESULT_ACCEPTED, on_message_received_internal_callback shall return the result of messaging_delivery_accepted()]
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_126: [If `instance->on_message_received_callback` returns MESSENGER_DISPOSITION_RESULT_RELEASED, on_message_received_internal_callback shall return the result of messaging_delivery_released()]
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_127: [If `instance->on_message_received_callback` returns MESSENGER_DISPOSITION_RESULT_REJECTED, on_message_received_internal_callback shall return the result of messaging_delivery_rejected()]
+ result = create_uamqp_disposition_result_from(disposition_result);
+ }
+ }
+
+ return result;
+}
+
+static int create_message_receiver(MESSENGER_INSTANCE* instance)
+{
+ int result;
+
+ STRING_HANDLE devices_path = NULL;
+ STRING_HANDLE message_receive_address = NULL;
+ STRING_HANDLE link_name = NULL;
+ STRING_HANDLE target_name = NULL;
+ AMQP_VALUE source = NULL;
+ AMQP_VALUE target = NULL;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_068: [A variable, named `devices_path`, shall be created concatenating `instance->iothub_host_fqdn`, "/devices/" and `instance->device_id`]
+ if ((devices_path = create_devices_path(instance->iothub_host_fqdn, instance->device_id)) == NULL)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_069: [If `devices_path` fails to be created, messenger_do_work() shall fail and return]
+ result = __FAILURE__;
+ LogError("Failed creating the message receiver (failed creating the 'devices_path')");
+ }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_070: [A variable, named `message_receive_address`, shall be created concatenating "amqps://", `devices_path` and "/messages/devicebound"]
+ else if ((message_receive_address = create_message_receive_address(devices_path)) == NULL)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_071: [If `message_receive_address` fails to be created, messenger_do_work() shall fail and return]
+ result = __FAILURE__;
+ LogError("Failed creating the message receiver (failed creating the 'message_receive_address')");
+ }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_072: [A `link_name` variable shall be created using an unique string label per AMQP session]
+ else if ((link_name = create_link_name(MESSAGE_RECEIVER_LINK_NAME_PREFIX, STRING_c_str(instance->device_id))) == NULL)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_073: [If `link_name` fails to be created, messenger_do_work() shall fail and return]
+ result = __FAILURE__;
+ LogError("Failed creating the message receiver (failed creating an unique link name)");
+ }
+ else if ((target_name = create_message_receiver_target_name(link_name)) == NULL)
+ {
+ result = __FAILURE__;
+ LogError("Failed creating the message receiver (failed creating an unique target name)");
+ }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_074: [A `target` variable shall be created with messaging_create_target() using an unique string label per AMQP session]
+ else if ((target = messaging_create_target(STRING_c_str(target_name))) == NULL)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_075: [If `target` fails to be created, messenger_do_work() shall fail and return]
+ result = __FAILURE__;
+ LogError("Failed creating the message receiver (messaging_create_target failed)");
+ }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_076: [A `source` variable shall be created with messaging_create_source() using `message_receive_address`]
+ else if ((source = messaging_create_source(STRING_c_str(message_receive_address))) == NULL)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_077: [If `source` fails to be created, messenger_do_work() shall fail and return]
+ result = __FAILURE__;
+ LogError("Failed creating the message receiver (messaging_create_source failed)");
+ }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_078: [`instance->receiver_link` shall be set using link_create(), passing `instance->session_handle`, `link_name`, "role_receiver", `source` and `target` as parameters]
+ else if ((instance->receiver_link = link_create(instance->session_handle, STRING_c_str(link_name), role_receiver, source, target)) == NULL)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_079: [If link_create() fails, messenger_do_work() shall fail and return]
+ result = __FAILURE__;
+ LogError("Failed creating the message receiver (link_create failed)");
+ }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_080: [`instance->receiver_link` settle mode shall be set to "receiver_settle_mode_first" using link_set_rcv_settle_mode(), ]
+ else if (link_set_rcv_settle_mode(instance->receiver_link, receiver_settle_mode_first) != RESULT_OK)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_081: [If link_set_rcv_settle_mode() fails, messenger_do_work() shall fail and return]
+ result = __FAILURE__;
+ LogError("Failed creating the message receiver (link_set_rcv_settle_mode failed)");
+ }
+ else
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_082: [`instance->receiver_link` maximum message size shall be set to 65536 using link_set_max_message_size()]
+ if (link_set_max_message_size(instance->receiver_link, MESSAGE_RECEIVER_MAX_LINK_SIZE) != RESULT_OK)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_083: [If link_set_max_message_size() fails, it shall be logged and ignored.]
+ LogError("Failed setting message receiver link max message size.");
+ }
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_084: [`instance->receiver_link` should have a property "com.microsoft:client-version" set as `CLIENT_DEVICE_TYPE_PREFIX/IOTHUB_SDK_VERSION`, using amqpvalue_set_map_value() and link_set_attach_properties()]
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_085: [If amqpvalue_set_map_value() or link_set_attach_properties() fail, the failure shall be ignored]
+ attach_device_client_type_to_link(instance->receiver_link);
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_086: [`instance->message_receiver` shall be created using messagereceiver_create(), passing the `instance->receiver_link` and `on_messagereceiver_state_changed_callback`]
+ if ((instance->message_receiver = messagereceiver_create(instance->receiver_link, on_message_receiver_state_changed_callback, (void*)instance)) == NULL)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_087: [If messagereceiver_create() fails, messenger_do_work() shall fail and return]
+ result = __FAILURE__;
+ link_destroy(instance->receiver_link);
+ LogError("Failed creating the message receiver (messagereceiver_create failed)");
+ }
+ else
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_088: [`instance->message_receiver` shall be opened using messagereceiver_open(), passing `on_message_received_internal_callback`]
+ if (messagereceiver_open(instance->message_receiver, on_message_received_internal_callback, (const void*)instance) != RESULT_OK)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_089: [If messagereceiver_open() fails, messenger_do_work() shall fail and return]
+ result = __FAILURE__;
+ messagereceiver_destroy(instance->message_receiver);
+ link_destroy(instance->receiver_link);
+ LogError("Failed opening the AMQP message receiver.");
+ }
+ else
+ {
+ result = RESULT_OK;
+ }
+ }
+ }
+
+ if (devices_path != NULL)
+ STRING_delete(devices_path);
+ if (message_receive_address != NULL)
+ STRING_delete(message_receive_address);
+ if (link_name != NULL)
+ STRING_delete(link_name);
+ if (target_name != NULL)
+ STRING_delete(target_name);
+ if (source != NULL)
+ amqpvalue_destroy(source);
+ if (target != NULL)
+ amqpvalue_destroy(target);
+
+ return result;
+}
+
+static int move_event_to_in_progress_list(MESSENGER_SEND_EVENT_TASK* task)
+{
+ int result;
+
+ if (singlylinkedlist_add(task->messenger->in_progress_list, (void*)task) == NULL)
+ {
+ result = __FAILURE__;
+ LogError("Failed moving event to in_progress list (singlylinkedlist_add failed)");
+ }
+ else
+ {
+ result = RESULT_OK;
+ }
+
+ return result;
+}
+
+static bool find_MESSENGER_SEND_EVENT_TASK_on_list(LIST_ITEM_HANDLE list_item, const void* match_context)
+{
+ return (list_item != NULL && singlylinkedlist_item_get_value(list_item) == match_context);
+}
+
+static void remove_event_from_in_progress_list(MESSENGER_SEND_EVENT_TASK *task)
+{
+ LIST_ITEM_HANDLE list_item = singlylinkedlist_find(task->messenger->in_progress_list, find_MESSENGER_SEND_EVENT_TASK_on_list, (void*)task);
+
+ if (list_item != NULL)
+ {
+ if (singlylinkedlist_remove(task->messenger->in_progress_list, list_item) != RESULT_OK)
+ {
+ LogError("Failed removing event from in_progress list (singlylinkedlist_remove failed)");
+ }
+ }
+}
+
+static int copy_events_to_list(SINGLYLINKEDLIST_HANDLE from_list, SINGLYLINKEDLIST_HANDLE to_list)
+{
+ int result;
+ LIST_ITEM_HANDLE list_item;
+
+ result = RESULT_OK;
+ list_item = singlylinkedlist_get_head_item(from_list);
+
+ while (list_item != NULL)
+ {
+ MESSENGER_SEND_EVENT_TASK *task = (MESSENGER_SEND_EVENT_TASK*)singlylinkedlist_item_get_value(list_item);
+
+ if (singlylinkedlist_add(to_list, task) == NULL)
+ {
+ LogError("Failed copying event to destination list (singlylinkedlist_add failed)");
+ result = __FAILURE__;
+ break;
+ }
+ else
+ {
+ list_item = singlylinkedlist_get_next_item(list_item);
+ }
+ }
+
+ return result;
+}
+
+static int singlylinkedlist_clear(SINGLYLINKEDLIST_HANDLE list)
+{
+ int result;
+ LIST_ITEM_HANDLE list_item;
+
+ result = RESULT_OK;
+
+ while ((list_item = singlylinkedlist_get_head_item(list)) != NULL)
+ {
+ if (singlylinkedlist_remove(list, list_item) != RESULT_OK)
+ {
+ LogError("Failed removing items from list (%d)", list);
+ result = __FAILURE__;
+ break;
+ }
+ }
+
+ return result;
+}
+
+static int move_events_to_wait_to_send_list(MESSENGER_INSTANCE* instance)
+{
+ int result;
+ LIST_ITEM_HANDLE list_item;
+
+ if ((list_item = singlylinkedlist_get_head_item(instance->in_progress_list)) == NULL)
+ {
+ result = RESULT_OK;
+ }
+ else
+ {
+ SINGLYLINKEDLIST_HANDLE new_wait_to_send_list;
+
+ if ((new_wait_to_send_list = singlylinkedlist_create()) == NULL)
+ {
+ LogError("Failed moving events back to wait_to_send list (singlylinkedlist_create failed to create new wait_to_send_list)");
+ result = __FAILURE__;
+ }
+ else
+ {
+ SINGLYLINKEDLIST_HANDLE new_in_progress_list;
+
+ if (copy_events_to_list(instance->in_progress_list, new_wait_to_send_list) != RESULT_OK)
+ {
+ LogError("Failed moving events back to wait_to_send list (failed adding in_progress_list items to new_wait_to_send_list)");
+ singlylinkedlist_destroy(new_wait_to_send_list);
+ result = __FAILURE__;
+ }
+ else if (copy_events_to_list(instance->waiting_to_send, new_wait_to_send_list) != RESULT_OK)
+ {
+ LogError("Failed moving events back to wait_to_send list (failed adding wait_to_send items to new_wait_to_send_list)");
+ singlylinkedlist_destroy(new_wait_to_send_list);
+ result = __FAILURE__;
+ }
+ else if ((new_in_progress_list = singlylinkedlist_create()) == NULL)
+ {
+ LogError("Failed moving events back to wait_to_send list (singlylinkedlist_create failed to create new in_progress_list)");
+ singlylinkedlist_destroy(new_wait_to_send_list);
+ result = __FAILURE__;
+ }
+ else
+ {
+ singlylinkedlist_destroy(instance->waiting_to_send);
+ singlylinkedlist_destroy(instance->in_progress_list);
+ instance->waiting_to_send = new_wait_to_send_list;
+ instance->in_progress_list = new_in_progress_list;
+ result = RESULT_OK;
+ }
+ }
+ }
+
+ return result;
+}
+
+static void internal_on_event_send_complete_callback(void* context, MESSAGE_SEND_RESULT send_result)
+{
+ if (context != NULL)
+ {
+ MESSENGER_SEND_EVENT_TASK* task = (MESSENGER_SEND_EVENT_TASK*)context;
+
+ if (task->is_timed_out == false)
+ {
+ MESSENGER_EVENT_SEND_COMPLETE_RESULT messenger_send_result;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_107: [If no failure occurs, `task->on_event_send_complete_callback` shall be invoked with result EVENT_SEND_COMPLETE_RESULT_OK]
+ if (send_result == MESSAGE_SEND_OK)
+ {
+ messenger_send_result = MESSENGER_EVENT_SEND_COMPLETE_RESULT_OK;
+ }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_108: [If a failure occurred, `task->on_event_send_complete_callback` shall be invoked with result EVENT_SEND_COMPLETE_RESULT_ERROR_FAIL_SENDING]
+ else
+ {
+ messenger_send_result = MESSENGER_EVENT_SEND_COMPLETE_RESULT_ERROR_FAIL_SENDING;
+ }
+
+ task->on_event_send_complete_callback(task->message, messenger_send_result, (void*)task->context);
+ }
+ else
+ {
+ LogInfo("messenger on_event_send_complete_callback invoked for timed out event %p; not firing upper layer callback.", task->message);
+ }
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_128: [`task` shall be removed from `instance->in_progress_list`]
+ remove_event_from_in_progress_list(task);
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_130: [`task` shall be destroyed using free()]
+ free(task);
+ }
+}
+
+static MESSENGER_SEND_EVENT_TASK* get_next_event_to_send(MESSENGER_INSTANCE* instance)
+{
+ MESSENGER_SEND_EVENT_TASK* task;
+ LIST_ITEM_HANDLE list_item;
+
+ if ((list_item = singlylinkedlist_get_head_item(instance->waiting_to_send)) == NULL)
+ {
+ task = NULL;
+ }
+ else
+ {
+ task = (MESSENGER_SEND_EVENT_TASK*)singlylinkedlist_item_get_value(list_item);
+
+ if (singlylinkedlist_remove(instance->waiting_to_send, list_item) != RESULT_OK)
+ {
+ LogError("Failed removing item from waiting_to_send list (singlylinkedlist_remove failed)");
+ }
+ }
+
+ return task;
+}
+
+static int send_pending_events(MESSENGER_INSTANCE* instance)
+{
+ int result = RESULT_OK;
+
+ MESSENGER_SEND_EVENT_TASK* task;
+
+ while ((task = get_next_event_to_send(instance)) != NULL)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_153: [messenger_do_work() shall move each event to be sent from `instance->wait_to_send_list` to `instance->in_progress_list`]
+ if (move_event_to_in_progress_list(task) != RESULT_OK)
+ {
+ result = __FAILURE__;
+ task->on_event_send_complete_callback(task->message, MESSENGER_EVENT_SEND_COMPLETE_RESULT_ERROR_FAIL_SENDING, (void*)task->context);
+ break;
+ }
+ else
+ {
+ int uamqp_result;
+ MESSAGE_HANDLE amqp_message = NULL;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_154: [A MESSAGE_HANDLE shall be obtained out of the event's IOTHUB_MESSAGE_HANDLE instance by using message_create_from_iothub_message()]
+ if ((uamqp_result = message_create_from_iothub_message(task->message->messageHandle, &amqp_message)) != RESULT_OK)
+ {
+ LogError("Failed sending event message (failed creating AMQP message; error: %d).", uamqp_result);
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_155: [If message_create_from_iothub_message() fails, `task->on_event_send_complete_callback` shall be invoked with result EVENT_SEND_COMPLETE_RESULT_ERROR_CANNOT_PARSE]
+ task->on_event_send_complete_callback(task->message, MESSENGER_EVENT_SEND_COMPLETE_RESULT_ERROR_CANNOT_PARSE, (void*)task->context);
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_160: [If any failure occurs the event shall be removed from `instance->in_progress_list` and destroyed]
+ remove_event_from_in_progress_list(task);
+ free(task);
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_156: [If message_create_from_iothub_message() fails, messenger_do_work() shall skip to the next event to be sent]
+ }
+ else
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_157: [The MESSAGE_HANDLE shall be submitted for sending using messagesender_send(), passing `internal_on_event_send_complete_callback`]
+ uamqp_result = messagesender_send(instance->message_sender, amqp_message, internal_on_event_send_complete_callback, task);
+ task->send_time = get_time(NULL);
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_159: [The MESSAGE_HANDLE shall be destroyed using message_destroy().]
+ message_destroy(amqp_message);
+
+ if (uamqp_result != RESULT_OK)
+ {
+ LogError("Failed sending event (messagesender_send failed; error: %d)", uamqp_result);
+
+ result = __FAILURE__;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_158: [If messagesender_send() fails, `task->on_event_send_complete_callback` shall be invoked with result EVENT_SEND_COMPLETE_RESULT_ERROR_FAIL_SENDING]
+ task->on_event_send_complete_callback(task->message, MESSENGER_EVENT_SEND_COMPLETE_RESULT_ERROR_FAIL_SENDING, (void*)task->context);
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_160: [If any failure occurs the event shall be removed from `instance->in_progress_list` and destroyed]
+ remove_event_from_in_progress_list(task);
+ free(task);
+
+ break;
+ }
+ }
+ }
+ }
+
+ return result;
+}
+
+// @brief
+// Goes through each task in in_progress_list and checks if the events timed out to be sent.
+// @remarks
+// If an event is timed out, it is marked as such but not removed, and the upper layer callback is invoked.
+// @returns
+// 0 if no failures occur, non-zero otherwise.
+static int process_event_send_timeouts(MESSENGER_INSTANCE* instance)
+{
+ int result = RESULT_OK;
+
+ if (instance->event_send_timeout_secs > 0)
+ {
+ LIST_ITEM_HANDLE list_item = singlylinkedlist_get_head_item(instance->in_progress_list);
+
+ while (list_item != NULL)
+ {
+ MESSENGER_SEND_EVENT_TASK* task = (MESSENGER_SEND_EVENT_TASK*)singlylinkedlist_item_get_value(list_item);
+
+ if (task->is_timed_out == false)
+ {
+ int is_timed_out;
+
+ if (is_timeout_reached(task->send_time, instance->event_send_timeout_secs, &is_timed_out) == RESULT_OK)
+ {
+ if (is_timed_out)
+ {
+ task->is_timed_out = true;
+
+ if (task->on_event_send_complete_callback != NULL)
+ {
+ task->on_event_send_complete_callback(task->message, MESSENGER_EVENT_SEND_COMPLETE_RESULT_ERROR_TIMEOUT, task->context);
+ }
+ }
+ }
+ else
+ {
+ LogError("messenger failed to evaluate event send timeout of event %d", task->message);
+ result = __FAILURE__;
+ }
+ }
+
+ list_item = singlylinkedlist_get_next_item(list_item);
+ }
+ }
+
+ return result;
+}
+
+// @brief
+// Removes all the timed out events from the in_progress_list, without invoking callbacks or detroying the messages.
+static void remove_timed_out_events(MESSENGER_INSTANCE* instance)
+{
+ LIST_ITEM_HANDLE list_item = singlylinkedlist_get_head_item(instance->in_progress_list);
+
+ while (list_item != NULL)
+ {
+ MESSENGER_SEND_EVENT_TASK* task = (MESSENGER_SEND_EVENT_TASK*)singlylinkedlist_item_get_value(list_item);
+
+ if (task->is_timed_out == true)
+ {
+ remove_event_from_in_progress_list(task);
+
+ free(task);
+ }
+
+ list_item = singlylinkedlist_get_next_item(list_item);
+ }
+}
+
+
+// ---------- Set/Retrieve Options Helpers ----------//
+
+static void* messenger_clone_option(const char* name, const void* value)
+{
+ void* result;
+
+ if (name == NULL)
+ {
+ LogError("Failed to clone messenger option (name is NULL)");
+ result = NULL;
+ }
+ else if (value == NULL)
+ {
+ LogError("Failed to clone messenger option (value is NULL)");
+ result = NULL;
+ }
+ else
+ {
+ if (strcmp(MESSENGER_OPTION_EVENT_SEND_TIMEOUT_SECS, name) == 0 ||
+ strcmp(MESSENGER_OPTION_SAVED_OPTIONS, name) == 0)
+ {
+ result = (void*)value;
+ }
+ else
+ {
+ LogError("Failed to clone messenger option (option with name '%s' is not suppported)", name);
+ result = NULL;
+ }
+ }
+
+ return result;
+}
+
+static void messenger_destroy_option(const char* name, const void* value)
+{
+ if (name == NULL)
+ {
+ LogError("Failed to destroy messenger option (name is NULL)");
+ }
+ else if (value == NULL)
+ {
+ LogError("Failed to destroy messenger option (value is NULL)");
+ }
+ else
+ {
+ // Nothing to be done for the supported options.
+ }
+}
+
+
+// Public API:
+
+int messenger_subscribe_for_messages(MESSENGER_HANDLE messenger_handle, ON_MESSENGER_MESSAGE_RECEIVED on_message_received_callback, void* context)
+{
+ int result;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_016: [If `messenger_handle` is NULL, messenger_subscribe_for_messages() shall fail and return __FAILURE__]
+ if (messenger_handle == NULL)
+ {
+ result = __FAILURE__;
+ LogError("messenger_subscribe_for_messages failed (messenger_handle is NULL)");
+ }
+ else
+ {
+ MESSENGER_INSTANCE* instance = (MESSENGER_INSTANCE*)messenger_handle;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_017: [If `instance->receive_messages` is already true, messenger_subscribe_for_messages() shall fail and return __FAILURE__]
+ if (instance->receive_messages)
+ {
+ result = __FAILURE__;
+ LogError("messenger_subscribe_for_messages failed (messenger already subscribed)");
+ }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_018: [If `on_message_received_callback` is NULL, messenger_subscribe_for_messages() shall fail and return __FAILURE__]
+ else if (on_message_received_callback == NULL)
+ {
+ result = __FAILURE__;
+ LogError("messenger_subscribe_for_messages failed (on_message_received_callback is NULL)");
+ }
+ else
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_019: [`on_message_received_callback` shall be saved on `instance->on_message_received_callback`]
+ instance->on_message_received_callback = on_message_received_callback;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_020: [`context` shall be saved on `instance->on_message_received_context`]
+ instance->on_message_received_context = context;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_021: [messenger_subscribe_for_messages() shall set `instance->receive_messages` to true]
+ instance->receive_messages = true;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_022: [If no failures occurr, messenger_subscribe_for_messages() shall return 0]
+ result = RESULT_OK;
+ }
+ }
+
+ return result;
+}
+
+int messenger_unsubscribe_for_messages(MESSENGER_HANDLE messenger_handle)
+{
+ int result;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_023: [If `messenger_handle` is NULL, messenger_unsubscribe_for_messages() shall fail and return __FAILURE__]
+ if (messenger_handle == NULL)
+ {
+ result = __FAILURE__;
+ LogError("messenger_unsubscribe_for_messages failed (messenger_handle is NULL)");
+ }
+ else
+ {
+ MESSENGER_INSTANCE* instance = (MESSENGER_INSTANCE*)messenger_handle;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_024: [If `instance->receive_messages` is already false, messenger_unsubscribe_for_messages() shall fail and return __FAILURE__]
+ if (instance->receive_messages == false)
+ {
+ result = __FAILURE__;
+ LogError("messenger_unsubscribe_for_messages failed (messenger is not subscribed)");
+ }
+ else
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_025: [messenger_unsubscribe_for_messages() shall set `instance->receive_messages` to false]
+ instance->receive_messages = false;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_026: [messenger_unsubscribe_for_messages() shall set `instance->on_message_received_callback` to NULL]
+ instance->on_message_received_callback = NULL;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_027: [messenger_unsubscribe_for_messages() shall set `instance->on_message_received_context` to NULL]
+ instance->on_message_received_context = NULL;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_028: [If no failures occurr, messenger_unsubscribe_for_messages() shall return 0]
+ result = RESULT_OK;
+ }
+ }
+
+ return result;
+}
+
+int messenger_send_message_disposition(MESSENGER_HANDLE messenger_handle, MESSENGER_MESSAGE_DISPOSITION_INFO* disposition_info, MESSENGER_DISPOSITION_RESULT disposition_result)
+{
+ int result;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_179: [If `messenger_handle` or `disposition_info` are NULL, messenger_send_message_disposition() shall fail and return __FAILURE__]
+ if (messenger_handle == NULL || disposition_info == NULL)
+ {
+ LogError("Failed sending message disposition (either messenger_handle (%p) or disposition_info (%p) are NULL)", messenger_handle, disposition_info);
+ result = __FAILURE__;
+ }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_180: [If `disposition_info->source` is NULL, messenger_send_message_disposition() shall fail and return __FAILURE__]
+ else if (disposition_info->source == NULL)
+ {
+ LogError("Failed sending message disposition (disposition_info->source is NULL)");
+ result = __FAILURE__;
+ }
+ else
+ {
+ MESSENGER_INSTANCE* messenger = (MESSENGER_INSTANCE*)messenger_handle;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_189: [If `messenger_handle->message_receiver` is NULL, messenger_send_message_disposition() shall fail and return __FAILURE__]
+ if (messenger->message_receiver == NULL)
+ {
+ LogError("Failed sending message disposition (message_receiver is not created; check if it is subscribed)");
+ result = __FAILURE__;
+ }
+ else
+ {
+ AMQP_VALUE uamqp_disposition_result;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_181: [An AMQP_VALUE disposition result shall be created corresponding to the `disposition_result` provided]
+ if ((uamqp_disposition_result = create_uamqp_disposition_result_from(disposition_result)) == NULL)
+ {
+ LogError("Failed sending message disposition (disposition result %d is not supported)", disposition_result);
+ result = __FAILURE__;
+ }
+ else
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_182: [`messagereceiver_send_message_disposition()` shall be invoked passing `disposition_info->source`, `disposition_info->message_id` and the corresponding AMQP_VALUE disposition result]
+ if (messagereceiver_send_message_disposition(messenger->message_receiver, disposition_info->source, disposition_info->message_id, uamqp_disposition_result) != RESULT_OK)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_183: [If `messagereceiver_send_message_disposition()` fails, messenger_send_message_disposition() shall fail and return __FAILURE__]
+ LogError("Failed sending message disposition (messagereceiver_send_message_disposition failed)");
+ result = __FAILURE__;
+ }
+ else
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_185: [If no failures occurr, messenger_send_message_disposition() shall return 0]
+ result = RESULT_OK;
+ }
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_184: [messenger_send_message_disposition() shall destroy the AMQP_VALUE disposition result]
+ amqpvalue_destroy(uamqp_disposition_result);
+ }
+ }
+ }
+
+ return result;
+}
+
+int messenger_send_async(MESSENGER_HANDLE messenger_handle, IOTHUB_MESSAGE_LIST* message, ON_MESSENGER_EVENT_SEND_COMPLETE on_messenger_event_send_complete_callback, void* context)
+{
+ int result;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_134: [If `messenger_handle` is NULL, messenger_send_async() shall fail and return a non-zero value]
+ if (messenger_handle == NULL)
+ {
+ LogError("Failed sending event (messenger_handle is NULL)");
+ result = __FAILURE__;
+ }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_135: [If `message` is NULL, messenger_send_async() shall fail and return a non-zero value]
+ else if (message == NULL)
+ {
+ LogError("Failed sending event (message is NULL)");
+ result = __FAILURE__;
+ }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_136: [If `on_event_send_complete_callback` is NULL, messenger_send_async() shall fail and return a non-zero value]
+ else if (on_messenger_event_send_complete_callback == NULL)
+ {
+ LogError("Failed sending event (on_event_send_complete_callback is NULL)");
+ result = __FAILURE__;
+ }
+ else
+ {
+ MESSENGER_SEND_EVENT_TASK *task;
+ MESSENGER_INSTANCE *instance = (MESSENGER_INSTANCE*)messenger_handle;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_137: [messenger_send_async() shall allocate memory for a MESSENGER_SEND_EVENT_TASK structure (aka `task`)]
+ if ((task = (MESSENGER_SEND_EVENT_TASK*)malloc(sizeof(MESSENGER_SEND_EVENT_TASK))) == NULL)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_138: [If malloc() fails, messenger_send_async() shall fail and return a non-zero value]
+ LogError("Failed sending event (failed to create struct for task; malloc failed)");
+ result = __FAILURE__;
+ }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_100: [`task` shall be added to `instance->waiting_to_send` using singlylinkedlist_add()]
+ else if (singlylinkedlist_add(instance->waiting_to_send, task) == NULL)
+ {
+ LogError("Failed sending event (singlylinkedlist_add failed)");
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_139: [If singlylinkedlist_add() fails, messenger_send_async() shall fail and return a non-zero value]
+ result = __FAILURE__;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_142: [If any failure occurs, messenger_send_async() shall free any memory it has allocated]
+ free(task);
+ }
+ else
+ {
+ memset(task, 0, sizeof(MESSENGER_SEND_EVENT_TASK));
+ task->message = message;
+ task->on_event_send_complete_callback = on_messenger_event_send_complete_callback;
+ task->context = context;
+ task->send_time = INDEFINITE_TIME;
+ task->messenger = instance;
+ task->is_timed_out = false;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_143: [If no failures occur, messenger_send_async() shall return zero]
+ result = RESULT_OK;
+ }
+ }
+
+ return result;
+}
+
+int messenger_get_send_status(MESSENGER_HANDLE messenger_handle, MESSENGER_SEND_STATUS* send_status)
+{
+ int result;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_144: [If `messenger_handle` is NULL, messenger_get_send_status() shall fail and return a non-zero value]
+ if (messenger_handle == NULL)
+ {
+ LogError("messenger_get_send_status failed (messenger_handle is NULL)");
+ result = __FAILURE__;
+ }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_145: [If `send_status` is NULL, messenger_get_send_status() shall fail and return a non-zero value]
+ else if (send_status == NULL)
+ {
+ LogError("messenger_get_send_status failed (send_status is NULL)");
+ result = __FAILURE__;
+ }
+ else
+ {
+ MESSENGER_INSTANCE* instance = (MESSENGER_INSTANCE*)messenger_handle;
+ LIST_ITEM_HANDLE wts_list_head = singlylinkedlist_get_head_item(instance->waiting_to_send);
+ LIST_ITEM_HANDLE ip_list_head = singlylinkedlist_get_head_item(instance->in_progress_list);
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_147: [If `instance->in_progress_list` and `instance->wait_to_send_list` are empty, send_status shall be set to MESSENGER_SEND_STATUS_IDLE]
+ if (wts_list_head == NULL && ip_list_head == NULL)
+ {
+ *send_status = MESSENGER_SEND_STATUS_IDLE;
+ }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_148: [Otherwise, send_status shall be set to MESSENGER_SEND_STATUS_BUSY]
+ else
+ {
+ *send_status = MESSENGER_SEND_STATUS_BUSY;
+ }
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_149: [If no failures occur, messenger_get_send_status() shall return 0]
+ result = RESULT_OK;
+ }
+
+ return result;
+}
+
+int messenger_start(MESSENGER_HANDLE messenger_handle, SESSION_HANDLE session_handle)
+{
+ int result;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_029: [If `messenger_handle` is NULL, messenger_start() shall fail and return __FAILURE__]
+ if (messenger_handle == NULL)
+ {
+ result = __FAILURE__;
+ LogError("messenger_start failed (messenger_handle is NULL)");
+ }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_030: [If `session_handle` is NULL, messenger_start() shall fail and return __FAILURE__]
+ else if (session_handle == NULL)
+ {
+ result = __FAILURE__;
+ LogError("messenger_start failed (session_handle is NULL)");
+ }
+ else
+ {
+ MESSENGER_INSTANCE* instance = (MESSENGER_INSTANCE*)messenger_handle;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_031: [If `instance->state` is not MESSENGER_STATE_STOPPED, messenger_start() shall fail and return __FAILURE__]
+ if (instance->state != MESSENGER_STATE_STOPPED)
+ {
+ result = __FAILURE__;
+ LogError("messenger_start failed (current state is %d; expected MESSENGER_STATE_STOPPED)", instance->state);
+ }
+ else
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_032: [`session_handle` shall be saved on `instance->session_handle`]
+ instance->session_handle = session_handle;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_115: [If no failures occurr, `instance->state` shall be set to MESSENGER_STATE_STARTING, and `instance->on_state_changed_callback` invoked if provided]
+ update_messenger_state(instance, MESSENGER_STATE_STARTING);
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_056: [If no failures occurr, messenger_start() shall return 0]
+ result = RESULT_OK;
+ }
+ }
+
+ return result;
+}
+
+int messenger_stop(MESSENGER_HANDLE messenger_handle)
+{
+ int result;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_057: [If `messenger_handle` is NULL, messenger_stop() shall fail and return a non-zero value]
+ if (messenger_handle == NULL)
+ {
+ result = __FAILURE__;
+ LogError("messenger_stop failed (messenger_handle is NULL)");
+ }
+ else
+ {
+ MESSENGER_INSTANCE* instance = (MESSENGER_INSTANCE*)messenger_handle;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_058: [If `instance->state` is MESSENGER_STATE_STOPPED, messenger_stop() shall fail and return a non-zero value]
+ if (instance->state == MESSENGER_STATE_STOPPED)
+ {
+ result = __FAILURE__;
+ LogError("messenger_stop failed (messenger is already stopped)");
+ }
+ else
+ {
+ update_messenger_state(instance, MESSENGER_STATE_STOPPING);
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_152: [messenger_stop() shall close and destroy `instance->message_sender` and `instance->message_receiver`]
+ destroy_event_sender(instance);
+ destroy_message_receiver(instance);
+
+ remove_timed_out_events(instance);
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_162: [messenger_stop() shall move all items from `instance->in_progress_list` to the beginning of `instance->wait_to_send_list`]
+ if (move_events_to_wait_to_send_list(instance) != RESULT_OK)
+ {
+ LogError("Messenger failed to move events in progress back to wait_to_send list");
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_163: [If not all items from `instance->in_progress_list` can be moved back to `instance->wait_to_send_list`, `instance->state` shall be set to MESSENGER_STATE_ERROR, and `instance->on_state_changed_callback` invoked]
+ update_messenger_state(instance, MESSENGER_STATE_ERROR);
+ result = __FAILURE__;
+ }
+ else
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_164: [If all items get successfuly moved back to `instance->wait_to_send_list`, `instance->state` shall be set to MESSENGER_STATE_STOPPED, and `instance->on_state_changed_callback` invoked]
+ update_messenger_state(instance, MESSENGER_STATE_STOPPED);
+ result = RESULT_OK;
+ }
+ }
+ }
+
+ return result;
+}
+
+// @brief
+// Sets the messenger module state based on the state changes from messagesender and messagereceiver
+static void process_state_changes(MESSENGER_INSTANCE* instance)
+{
+ // Note: messagesender and messagereceiver are still not created or already destroyed
+ // when state is MESSENGER_STATE_STOPPED, so no checking is needed there.
+
+ if (instance->state == MESSENGER_STATE_STARTED)
+ {
+ if (instance->message_sender_current_state != MESSAGE_SENDER_STATE_OPEN)
+ {
+ LogError("messagesender reported unexpected state %d while messenger was started", instance->message_sender_current_state);
+ update_messenger_state(instance, MESSENGER_STATE_ERROR);
+ }
+ else if (instance->message_receiver != NULL && instance->message_receiver_current_state != MESSAGE_RECEIVER_STATE_OPEN)
+ {
+ if (instance->message_receiver_current_state == MESSAGE_RECEIVER_STATE_OPENING)
+ {
+ int is_timed_out;
+ if (is_timeout_reached(instance->last_message_receiver_state_change_time, MAX_MESSAGE_RECEIVER_STATE_CHANGE_TIMEOUT_SECS, &is_timed_out) != RESULT_OK)
+ {
+ LogError("messenger got an error (failed to verify messagereceiver start timeout)");
+ update_messenger_state(instance, MESSENGER_STATE_ERROR);
+ }
+ else if (is_timed_out == 1)
+ {
+ LogError("messenger got an error (messagereceiver failed to start within expected timeout (%d secs))", MAX_MESSAGE_RECEIVER_STATE_CHANGE_TIMEOUT_SECS);
+ update_messenger_state(instance, MESSENGER_STATE_ERROR);
+ }
+ }
+ else if (instance->message_receiver_current_state == MESSAGE_RECEIVER_STATE_ERROR ||
+ instance->message_receiver_current_state == MESSAGE_RECEIVER_STATE_IDLE)
+ {
+ LogError("messagereceiver reported unexpected state %d while messenger is starting", instance->message_receiver_current_state);
+ update_messenger_state(instance, MESSENGER_STATE_ERROR);
+ }
+ }
+ }
+ else
+ {
+ if (instance->state == MESSENGER_STATE_STARTING)
+ {
+ if (instance->message_sender_current_state == MESSAGE_SENDER_STATE_OPEN)
+ {
+ update_messenger_state(instance, MESSENGER_STATE_STARTED);
+ }
+ else if (instance->message_sender_current_state == MESSAGE_SENDER_STATE_OPENING)
+ {
+ int is_timed_out;
+ if (is_timeout_reached(instance->last_message_sender_state_change_time, MAX_MESSAGE_SENDER_STATE_CHANGE_TIMEOUT_SECS, &is_timed_out) != RESULT_OK)
+ {
+ LogError("messenger failed to start (failed to verify messagesender start timeout)");
+ update_messenger_state(instance, MESSENGER_STATE_ERROR);
+ }
+ else if (is_timed_out == 1)
+ {
+ LogError("messenger failed to start (messagesender failed to start within expected timeout (%d secs))", MAX_MESSAGE_SENDER_STATE_CHANGE_TIMEOUT_SECS);
+ update_messenger_state(instance, MESSENGER_STATE_ERROR);
+ }
+ }
+ // For this module, the only valid scenario where messagesender state is IDLE is if
+ // the messagesender hasn't been created yet or already destroyed.
+ else if ((instance->message_sender_current_state == MESSAGE_SENDER_STATE_ERROR) ||
+ (instance->message_sender_current_state == MESSAGE_SENDER_STATE_CLOSING) ||
+ (instance->message_sender_current_state == MESSAGE_SENDER_STATE_IDLE && instance->message_sender != NULL))
+ {
+ LogError("messagesender reported unexpected state %d while messenger is starting", instance->message_sender_current_state);
+ update_messenger_state(instance, MESSENGER_STATE_ERROR);
+ }
+ }
+ // message sender and receiver are stopped/destroyed synchronously, so no need for state control.
+ }
+}
+
+void messenger_do_work(MESSENGER_HANDLE messenger_handle)
+{
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_065: [If `messenger_handle` is NULL, messenger_do_work() shall fail and return]
+ if (messenger_handle == NULL)
+ {
+ LogError("messenger_do_work failed (messenger_handle is NULL)");
+ }
+ else
+ {
+ MESSENGER_INSTANCE* instance = (MESSENGER_INSTANCE*)messenger_handle;
+
+ process_state_changes(instance);
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_151: [If `instance->state` is MESSENGER_STATE_STARTING, messenger_do_work() shall create and open `instance->message_sender`]
+ if (instance->state == MESSENGER_STATE_STARTING)
+ {
+ if (instance->message_sender == NULL)
+ {
+ if (create_event_sender(instance) != RESULT_OK)
+ {
+ update_messenger_state(instance, MESSENGER_STATE_ERROR);
+ }
+ }
+ }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_066: [If `instance->state` is not MESSENGER_STATE_STARTED, messenger_do_work() shall return]
+ else if (instance->state == MESSENGER_STATE_STARTED)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_067: [If `instance->receive_messages` is true and `instance->message_receiver` is NULL, a message_receiver shall be created]
+ if (instance->receive_messages == true &&
+ instance->message_receiver == NULL &&
+ create_message_receiver(instance) != RESULT_OK)
+ {
+ LogError("messenger_do_work warning (failed creating the message receiver [%s])", STRING_c_str(instance->device_id));
+ }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_092: [If `instance->receive_messages` is false and `instance->message_receiver` is not NULL, it shall be destroyed]
+ else if (instance->receive_messages == false && instance->message_receiver != NULL)
+ {
+ destroy_message_receiver(instance);
+ }
+
+ if (process_event_send_timeouts(instance) != RESULT_OK)
+ {
+ update_messenger_state(instance, MESSENGER_STATE_ERROR);
+ }
+ else if (send_pending_events(instance) != RESULT_OK && instance->event_send_retry_limit > 0)
+ {
+ instance->event_send_error_count++;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_161: [If messenger_do_work() fail sending events for `instance->event_send_retry_limit` times in a row, it shall invoke `instance->on_state_changed_callback`, if provided, with error code MESSENGER_STATE_ERROR]
+ if (instance->event_send_error_count >= instance->event_send_retry_limit)
+ {
+ LogError("messenger_do_work failed (failed sending events; reached max number of consecutive attempts)");
+ update_messenger_state(instance, MESSENGER_STATE_ERROR);
+ }
+ }
+ else
+ {
+ instance->event_send_error_count = 0;
+ }
+ }
+ }
+}
+
+void messenger_destroy(MESSENGER_HANDLE messenger_handle)
+{
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_109: [If `messenger_handle` is NULL, messenger_destroy() shall fail and return]
+ if (messenger_handle == NULL)
+ {
+ LogError("messenger_destroy failed (messenger_handle is NULL)");
+ }
+ else
+ {
+ LIST_ITEM_HANDLE list_node;
+ MESSENGER_INSTANCE* instance = (MESSENGER_INSTANCE*)messenger_handle;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_110: [If the `instance->state` is not MESSENGER_STATE_STOPPED, messenger_destroy() shall invoke messenger_stop()]
+ if (instance->state != MESSENGER_STATE_STOPPED)
+ {
+ (void)messenger_stop(messenger_handle);
+ }
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_111: [All elements of `instance->in_progress_list` and `instance->wait_to_send_list` shall be removed, invoking `task->on_event_send_complete_callback` for each with EVENT_SEND_COMPLETE_RESULT_MESSENGER_DESTROYED]
+
+ // Note: yes messenger_stop() tried to move all events from in_progress_list to wait_to_send_list,
+ // but we need to iterate through in case any events failed to be moved.
+ while ((list_node = singlylinkedlist_get_head_item(instance->in_progress_list)) != NULL)
+ {
+ MESSENGER_SEND_EVENT_TASK* task = (MESSENGER_SEND_EVENT_TASK*)singlylinkedlist_item_get_value(list_node);
+
+ (void)singlylinkedlist_remove(instance->in_progress_list, list_node);
+
+ if (task != NULL)
+ {
+ task->on_event_send_complete_callback(task->message, MESSENGER_EVENT_SEND_COMPLETE_RESULT_MESSENGER_DESTROYED, (void*)task->context);
+ free(task);
+ }
+ }
+
+ while ((list_node = singlylinkedlist_get_head_item(instance->waiting_to_send)) != NULL)
+ {
+ MESSENGER_SEND_EVENT_TASK* task = (MESSENGER_SEND_EVENT_TASK*)singlylinkedlist_item_get_value(list_node);
+
+ (void)singlylinkedlist_remove(instance->waiting_to_send, list_node);
+
+ if (task != NULL)
+ {
+ task->on_event_send_complete_callback(task->message, MESSENGER_EVENT_SEND_COMPLETE_RESULT_MESSENGER_DESTROYED, (void*)task->context);
+ free(task);
+ }
+ }
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_150: [`instance->in_progress_list` and `instance->wait_to_send_list` shall be destroyed using singlylinkedlist_destroy()]
+ singlylinkedlist_destroy(instance->waiting_to_send);
+ singlylinkedlist_destroy(instance->in_progress_list);
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_112: [`instance->iothub_host_fqdn` shall be destroyed using STRING_delete()]
+ STRING_delete(instance->iothub_host_fqdn);
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_113: [`instance->device_id` shall be destroyed using STRING_delete()]
+ STRING_delete(instance->device_id);
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_114: [messenger_destroy() shall destroy `instance` with free()]
+ (void)free(instance);
+ }
+}
+
+MESSENGER_HANDLE messenger_create(const MESSENGER_CONFIG* messenger_config)
+{
+ MESSENGER_HANDLE handle;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_001: [If parameter `messenger_config` is NULL, messenger_create() shall return NULL]
+ if (messenger_config == NULL)
+ {
+ handle = NULL;
+ LogError("messenger_create failed (messenger_config is NULL)");
+ }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_002: [If `messenger_config->device_id` is NULL, messenger_create() shall return NULL]
+ else if (messenger_config->device_id == NULL)
+ {
+ handle = NULL;
+ LogError("messenger_create failed (messenger_config->device_id is NULL)");
+ }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_003: [If `messenger_config->iothub_host_fqdn` is NULL, messenger_create() shall return NULL]
+ else if (messenger_config->iothub_host_fqdn == NULL)
+ {
+ handle = NULL;
+ LogError("messenger_create failed (messenger_config->iothub_host_fqdn is NULL)");
+ }
+ else
+ {
+ MESSENGER_INSTANCE* instance;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_006: [messenger_create() shall allocate memory for the messenger instance structure (aka `instance`)]
+ if ((instance = (MESSENGER_INSTANCE*)malloc(sizeof(MESSENGER_INSTANCE))) == NULL)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_007: [If malloc() fails, messenger_create() shall fail and return NULL]
+ handle = NULL;
+ LogError("messenger_create failed (messenger_config->wait_to_send_list is NULL)");
+ }
+ else
+ {
+ memset(instance, 0, sizeof(MESSENGER_INSTANCE));
+ instance->state = MESSENGER_STATE_STOPPED;
+ instance->message_sender_current_state = MESSAGE_SENDER_STATE_IDLE;
+ instance->message_sender_previous_state = MESSAGE_SENDER_STATE_IDLE;
+ instance->message_receiver_current_state = MESSAGE_RECEIVER_STATE_IDLE;
+ instance->message_receiver_previous_state = MESSAGE_RECEIVER_STATE_IDLE;
+ instance->event_send_retry_limit = DEFAULT_EVENT_SEND_RETRY_LIMIT;
+ instance->event_send_timeout_secs = DEFAULT_EVENT_SEND_TIMEOUT_SECS;
+ instance->last_message_sender_state_change_time = INDEFINITE_TIME;
+ instance->last_message_receiver_state_change_time = INDEFINITE_TIME;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_008: [messenger_create() shall save a copy of `messenger_config->device_id` into `instance->device_id`]
+ if ((instance->device_id = STRING_construct(messenger_config->device_id)) == NULL)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_009: [If STRING_construct() fails, messenger_create() shall fail and return NULL]
+ handle = NULL;
+ LogError("messenger_create failed (device_id could not be copied; STRING_construct failed)");
+ }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_010: [messenger_create() shall save a copy of `messenger_config->iothub_host_fqdn` into `instance->iothub_host_fqdn`]
+ else if ((instance->iothub_host_fqdn = STRING_construct(messenger_config->iothub_host_fqdn)) == NULL)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_011: [If STRING_construct() fails, messenger_create() shall fail and return NULL]
+ handle = NULL;
+ LogError("messenger_create failed (iothub_host_fqdn could not be copied; STRING_construct failed)");
+ }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_165: [`instance->wait_to_send_list` shall be set using singlylinkedlist_create()]
+ else if ((instance->waiting_to_send = singlylinkedlist_create()) == NULL)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_166: [If singlylinkedlist_create() fails, messenger_create() shall fail and return NULL]
+ handle = NULL;
+ LogError("messenger_create failed (singlylinkedlist_create failed to create wait_to_send_list)");
+ }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_132: [`instance->in_progress_list` shall be set using singlylinkedlist_create()]
+ else if ((instance->in_progress_list = singlylinkedlist_create()) == NULL)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_133: [If singlylinkedlist_create() fails, messenger_create() shall fail and return NULL]
+ handle = NULL;
+ LogError("messenger_create failed (singlylinkedlist_create failed to create in_progress_list)");
+ }
+ else
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_013: [`messenger_config->on_state_changed_callback` shall be saved into `instance->on_state_changed_callback`]
+ instance->on_state_changed_callback = messenger_config->on_state_changed_callback;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_014: [`messenger_config->on_state_changed_context` shall be saved into `instance->on_state_changed_context`]
+ instance->on_state_changed_context = messenger_config->on_state_changed_context;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_015: [If no failures occurr, messenger_create() shall return a handle to `instance`]
+ handle = (MESSENGER_HANDLE)instance;
+ }
+ }
+
+ if (handle == NULL)
+ {
+ messenger_destroy((MESSENGER_HANDLE)instance);
+ }
+ }
+
+ return handle;
+}
+
+int messenger_set_option(MESSENGER_HANDLE messenger_handle, const char* name, void* value)
+{
+ int result;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_167: [If `messenger_handle` or `name` or `value` is NULL, messenger_set_option shall fail and return a non-zero value]
+ if (messenger_handle == NULL || name == NULL || value == NULL)
+ {
+ LogError("messenger_set_option failed (one of the followin are NULL: messenger_handle=%p, name=%p, value=%p)",
+ messenger_handle, name, value);
+ result = __FAILURE__;
+ }
+ else
+ {
+ MESSENGER_INSTANCE* instance = (MESSENGER_INSTANCE*)messenger_handle;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_168: [If name matches MESSENGER_OPTION_EVENT_SEND_TIMEOUT_SECS, `value` shall be saved on `instance->event_send_timeout_secs`]
+ if (strcmp(MESSENGER_OPTION_EVENT_SEND_TIMEOUT_SECS, name) == 0)
+ {
+ instance->event_send_timeout_secs = *((size_t*)value);
+ result = RESULT_OK;
+ }
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_169: [If name matches MESSENGER_OPTION_SAVED_OPTIONS, `value` shall be applied using OptionHandler_FeedOptions]
+ else if (strcmp(MESSENGER_OPTION_SAVED_OPTIONS, name) == 0)
+ {
+ if (OptionHandler_FeedOptions((OPTIONHANDLER_HANDLE)value, messenger_handle) != OPTIONHANDLER_OK)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_170: [If OptionHandler_FeedOptions fails, messenger_set_option shall fail and return a non-zero value]
+ LogError("messenger_set_option failed (OptionHandler_FeedOptions failed)");
+ result = __FAILURE__;
+ }
+ else
+ {
+ result = RESULT_OK;
+ }
+ }
+ else
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_171: [If name does not match any supported option, authentication_set_option shall fail and return a non-zero value]
+ LogError("messenger_set_option failed (option with name '%s' is not suppported)", name);
+ result = __FAILURE__;
+ }
+ }
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_172: [If no errors occur, messenger_set_option shall return 0]
+ return result;
+}
+
+OPTIONHANDLER_HANDLE messenger_retrieve_options(MESSENGER_HANDLE messenger_handle)
+{
+ OPTIONHANDLER_HANDLE result;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_173: [If `messenger_handle` is NULL, messenger_retrieve_options shall fail and return NULL]
+ if (messenger_handle == NULL)
+ {
+ LogError("Failed to retrieve options from messenger instance (messenger_handle is NULL)");
+ result = NULL;
+ }
+ else
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_174: [An OPTIONHANDLER_HANDLE instance shall be created using OptionHandler_Create]
+ OPTIONHANDLER_HANDLE options = OptionHandler_Create(messenger_clone_option, messenger_destroy_option, (pfSetOption)messenger_set_option);
+
+ if (options == NULL)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_175: [If an OPTIONHANDLER_HANDLE instance fails to be created, messenger_retrieve_options shall fail and return NULL]
+ LogError("Failed to retrieve options from messenger instance (OptionHandler_Create failed)");
+ result = NULL;
+ }
+ else
+ {
+ MESSENGER_INSTANCE* instance = (MESSENGER_INSTANCE*)messenger_handle;
+
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_176: [Each option of `instance` shall be added to the OPTIONHANDLER_HANDLE instance using OptionHandler_AddOption]
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_177: [If OptionHandler_AddOption fails, messenger_retrieve_options shall fail and return NULL]
+ if (OptionHandler_AddOption(options, MESSENGER_OPTION_EVENT_SEND_TIMEOUT_SECS, (void*)&instance->event_send_timeout_secs) != OPTIONHANDLER_OK)
+ {
+ LogError("Failed to retrieve options from messenger instance (OptionHandler_Create failed for option '%s')", MESSENGER_OPTION_EVENT_SEND_TIMEOUT_SECS);
+ result = NULL;
+ }
+ else
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_179: [If no failures occur, messenger_retrieve_options shall return the OPTIONHANDLER_HANDLE instance]
+ result = options;
+ }
+
+ if (result == NULL)
+ {
+ // Codes_SRS_IOTHUBTRANSPORT_AMQP_MESSENGER_09_178: [If messenger_retrieve_options fails, any allocated memory shall be freed]
+ OptionHandler_Destroy(options);
+ }
+ }
+ }
+
+ return result;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/iothubtransport_amqp_messenger.h Fri Mar 10 11:46:55 2017 -0800
@@ -0,0 +1,91 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+#ifndef IOTHUBTRANSPORT_AMQP_MESSENGER
+#define IOTHUBTRANSPORT_AMQP_MESSENGER
+
+#include "azure_c_shared_utility/umock_c_prod.h"
+#include "azure_c_shared_utility/optionhandler.h"
+#include "azure_uamqp_c/session.h"
+#include "iothub_client_private.h"
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+
+static const char* MESSENGER_OPTION_EVENT_SEND_TIMEOUT_SECS = "event_send_timeout_secs";
+static const char* MESSENGER_OPTION_SAVED_OPTIONS = "saved_messenger_options";
+
+typedef struct MESSENGER_INSTANCE* MESSENGER_HANDLE;
+
+typedef enum MESSENGER_SEND_STATUS_TAG
+{
+ MESSENGER_SEND_STATUS_IDLE,
+ MESSENGER_SEND_STATUS_BUSY
+} MESSENGER_SEND_STATUS;
+
+typedef enum MESSENGER_EVENT_SEND_COMPLETE_RESULT_TAG
+{
+ MESSENGER_EVENT_SEND_COMPLETE_RESULT_OK,
+ MESSENGER_EVENT_SEND_COMPLETE_RESULT_ERROR_CANNOT_PARSE,
+ MESSENGER_EVENT_SEND_COMPLETE_RESULT_ERROR_FAIL_SENDING,
+ MESSENGER_EVENT_SEND_COMPLETE_RESULT_ERROR_TIMEOUT,
+ MESSENGER_EVENT_SEND_COMPLETE_RESULT_MESSENGER_DESTROYED
+} MESSENGER_EVENT_SEND_COMPLETE_RESULT;
+
+typedef enum MESSENGER_DISPOSITION_RESULT_TAG
+{
+ MESSENGER_DISPOSITION_RESULT_NONE,
+ MESSENGER_DISPOSITION_RESULT_ACCEPTED,
+ MESSENGER_DISPOSITION_RESULT_REJECTED,
+ MESSENGER_DISPOSITION_RESULT_RELEASED
+} MESSENGER_DISPOSITION_RESULT;
+
+typedef enum MESSENGER_STATE_TAG
+{
+ MESSENGER_STATE_STARTING,
+ MESSENGER_STATE_STARTED,
+ MESSENGER_STATE_STOPPING,
+ MESSENGER_STATE_STOPPED,
+ MESSENGER_STATE_ERROR
+} MESSENGER_STATE;
+
+typedef struct MESSENGER_MESSAGE_DISPOSITION_INFO_TAG
+{
+ delivery_number message_id;
+ char* source;
+} MESSENGER_MESSAGE_DISPOSITION_INFO;
+
+typedef void(*ON_MESSENGER_EVENT_SEND_COMPLETE)(IOTHUB_MESSAGE_LIST* iothub_message_list, MESSENGER_EVENT_SEND_COMPLETE_RESULT messenger_event_send_complete_result, void* context);
+typedef void(*ON_MESSENGER_STATE_CHANGED_CALLBACK)(void* context, MESSENGER_STATE previous_state, MESSENGER_STATE new_state);
+typedef MESSENGER_DISPOSITION_RESULT(*ON_MESSENGER_MESSAGE_RECEIVED)(IOTHUB_MESSAGE_HANDLE message, MESSENGER_MESSAGE_DISPOSITION_INFO* disposition_info, void* context);
+
+typedef struct MESSENGER_CONFIG_TAG
+{
+ char* device_id;
+ char* iothub_host_fqdn;
+ ON_MESSENGER_STATE_CHANGED_CALLBACK on_state_changed_callback;
+ void* on_state_changed_context;
+} MESSENGER_CONFIG;
+
+MOCKABLE_FUNCTION(, MESSENGER_HANDLE, messenger_create, const MESSENGER_CONFIG*, messenger_config);
+MOCKABLE_FUNCTION(, int, messenger_send_async, MESSENGER_HANDLE, messenger_handle, IOTHUB_MESSAGE_LIST*, message, ON_MESSENGER_EVENT_SEND_COMPLETE, on_messenger_event_send_complete_callback, void*, context);
+MOCKABLE_FUNCTION(, int, messenger_subscribe_for_messages, MESSENGER_HANDLE, messenger_handle, ON_MESSENGER_MESSAGE_RECEIVED, on_message_received_callback, void*, context);
+MOCKABLE_FUNCTION(, int, messenger_unsubscribe_for_messages, MESSENGER_HANDLE, messenger_handle);
+MOCKABLE_FUNCTION(, int, messenger_send_message_disposition, MESSENGER_HANDLE, messenger_handle, MESSENGER_MESSAGE_DISPOSITION_INFO*, disposition_info, MESSENGER_DISPOSITION_RESULT, disposition_result);
+MOCKABLE_FUNCTION(, int, messenger_get_send_status, MESSENGER_HANDLE, messenger_handle, MESSENGER_SEND_STATUS*, send_status);
+MOCKABLE_FUNCTION(, int, messenger_start, MESSENGER_HANDLE, messenger_handle, SESSION_HANDLE, session_handle);
+MOCKABLE_FUNCTION(, int, messenger_stop, MESSENGER_HANDLE, messenger_handle);
+MOCKABLE_FUNCTION(, void, messenger_do_work, MESSENGER_HANDLE, messenger_handle);
+MOCKABLE_FUNCTION(, void, messenger_destroy, MESSENGER_HANDLE, messenger_handle);
+MOCKABLE_FUNCTION(, int, messenger_set_option, MESSENGER_HANDLE, messenger_handle, const char*, name, void*, value);
+MOCKABLE_FUNCTION(, OPTIONHANDLER_HANDLE, messenger_retrieve_options, MESSENGER_HANDLE, messenger_handle);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /*IOTHUBTRANSPORT_AMQP_MESSENGER*/
--- a/iothubtransportamqp.c Fri Feb 24 14:00:00 2017 -0800
+++ b/iothubtransportamqp.c Fri Mar 10 11:46:55 2017 -0800
@@ -15,6 +15,9 @@
TLSIO_CONFIG tls_io_config;
tls_io_config.hostname = fqdn;
tls_io_config.port = DEFAULT_IOTHUB_AMQP_PORT;
+ tls_io_config.underlying_io_interface = NULL;
+ tls_io_config.underlying_io_parameters = NULL;
+
// Codes_SRS_IOTHUBTRANSPORTAMQP_09_002: [getTLSIOTransport shall get `io_interface_description` using platform_get_default_tlsio())]
const IO_INTERFACE_DESCRIPTION* io_interface_description = platform_get_default_tlsio();
@@ -120,6 +123,7 @@
static int IoTHubTransportAMQP_SetRetryPolicy(TRANSPORT_LL_HANDLE handle, IOTHUB_CLIENT_RETRY_POLICY retryPolicy, size_t retryTimeoutLimitInSeconds)
{
+ // Codes_SRS_IOTHUBTRANSPORTAMQP_09_020: [IoTHubTransportAMQP_SetRetryPolicy shall call into the IoTHubTransport_AMQP_Common_SetRetryPolicy().]
return IoTHubTransport_AMQP_Common_SetRetryPolicy(handle, retryPolicy, retryTimeoutLimitInSeconds);
}
@@ -129,8 +133,15 @@
return IoTHubTransport_AMQP_Common_GetHostname(handle);
}
+static IOTHUB_CLIENT_RESULT IoTHubTransportAMQP_SendMessageDisposition(MESSAGE_CALLBACK_INFO* message_data, IOTHUBMESSAGE_DISPOSITION_RESULT disposition)
+{
+ // Codes_SRS_IOTHUBTRANSPORTAMQP_10_001: [IoTHubTransportAMQP_SendMessageDisposition shall send the message disposition by calling into the IoTHubTransport_AMQP_Common_SendMessageDispostion().]
+ return IoTHubTransport_AMQP_Common_SendMessageDisposition(message_data, disposition);
+}
+
static TRANSPORT_PROVIDER thisTransportProvider =
{
+ IoTHubTransportAMQP_SendMessageDisposition, /*pfIotHubTransport_Send_Message_Disposition IoTHubTransport_Send_Message_Disposition;*/
IoTHubTransportAMQP_Subscribe_DeviceMethod, /*pfIoTHubTransport_Subscribe_DeviceMethod IoTHubTransport_Subscribe_DeviceMethod;*/
IoTHubTransportAMQP_Unsubscribe_DeviceMethod, /*pfIoTHubTransport_Unsubscribe_DeviceMethod IoTHubTransport_Unsubscribe_DeviceMethod;*/
IoTHubTransportAMQP_DeviceMethod_Response,
@@ -151,6 +162,7 @@
};
/* Codes_SRS_IOTHUBTRANSPORTAMQP_09_019: [This function shall return a pointer to a structure of type TRANSPORT_PROVIDER having the following values for it's fields:
+IoTHubTransport_SendMessageDisposition = IoTHubTransportAMQP_SendMessageDisposition
IoTHubTransport_Subscribe_DeviceMethod = IoTHubTransportAMQP_Subscribe_DeviceMethod
IoTHubTransport_Unsubscribe_DeviceMethod = IoTHubTransportAMQP_Unsubscribe_DeviceMethod
IoTHubTransport_Subscribe_DeviceTwin = IoTHubTransportAMQP_Subscribe_DeviceTwin
@@ -162,6 +174,7 @@
IoTHubTransport_Subscribe = IoTHubTransportAMQP_Subscribe
IoTHubTransport_Unsubscribe = IoTHubTransportAMQP_Unsubscribe
IoTHubTransport_DoWork = IoTHubTransportAMQP_DoWork
+IoTHubTransport_SetRetryPolicy = IoTHubTransportAMQP_SetRetryPolicy
IoTHubTransport_SetOption = IoTHubTransportAMQP_SetOption]*/
extern const TRANSPORT_PROVIDER* AMQP_Protocol(void)
{
--- a/iothubtransportamqp_auth.c Fri Feb 24 14:00:00 2017 -0800
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,721 +0,0 @@
-// Copyright (c) Microsoft. All rights reserved.
-// Licensed under the MIT license. See LICENSE file in the project root for full license information.
-
-#include "iothubtransportamqp_auth.h"
-#include "azure_c_shared_utility/optimize_size.h"
-#include "azure_c_shared_utility/agenttime.h"
-
-#define RESULT_OK 0
-#define INDEFINITE_TIME ((time_t)(-1))
-#define SAS_TOKEN_TYPE "servicebus.windows.net:sastoken"
-
-typedef struct AMQP_TRANSPORT_CBS_STATE_TAG
-{
- // A component of the SAS token. Currently this must be an empty string.
- STRING_HANDLE sasTokenKeyName;
- // Time when the current SAS token was created, in seconds since epoch.
- size_t current_sas_token_create_time;
- // Time when the current SAS token was put to CBS, in seconds since epoch.
- size_t current_sas_token_put_time;
-} AMQP_TRANSPORT_CBS_STATE;
-
-typedef struct AUTHENTICATION_STATE_TAG
-{
- STRING_HANDLE device_id;
-
- STRING_HANDLE iot_hub_host_fqdn;
-
- const AMQP_TRANSPORT_CBS_CONNECTION* cbs_connection;
-
- AMQP_TRANSPORT_CREDENTIAL credential;
-
- AMQP_TRANSPORT_CBS_STATE cbs_state;
-
- AUTHENTICATION_STATUS status;
-} AUTHENTICATION_STATE;
-
-static int getSecondsSinceEpoch(size_t* seconds)
-{
- int result;
- time_t current_time;
-
- if ((current_time = get_time(NULL)) == INDEFINITE_TIME)
- {
- LogError("Failed getting the current local time (get_time() failed)");
- result = __FAILURE__;
- }
- else
- {
- *seconds = (size_t)get_difftime(current_time, (time_t)0);
-
- result = RESULT_OK;
- }
-
- return result;
-}
-
-static void on_put_token_complete(void* context, CBS_OPERATION_RESULT operation_result, unsigned int status_code, const char* status_description)
-{
-#ifdef NO_LOGGING
- UNUSED(status_code);
- UNUSED(status_description);
-#endif
-
- AUTHENTICATION_STATE* auth_state = (AUTHENTICATION_STATE*)context;
-
- if (operation_result == CBS_OPERATION_RESULT_OK)
- {
- // Codes_IOTHUBTRANSPORTAMQP_AUTH_09_041: [When cbs_put_token() calls back, if the result is CBS_OPERATION_RESULT_OK the state status shall be set to AUTHENTICATION_STATUS_OK]
- auth_state->status = AUTHENTICATION_STATUS_OK;
- }
- else
- {
- // Codes_IOTHUBTRANSPORTAMQP_AUTH_09_042: [When cbs_put_token() calls back, if the result is not CBS_OPERATION_RESULT_OK the state status shall be set to AUTHENTICATION_STATUS_FAILURE]
- auth_state->status = AUTHENTICATION_STATUS_FAILURE;
- LogError("CBS reported status code %u, error: %s for put token operation", status_code, status_description);
- }
-}
-
-static void on_delete_token_complete(void* context, CBS_OPERATION_RESULT operation_result, unsigned int status_code, const char* status_description)
-{
-#ifdef NO_LOGGING
- UNUSED(status_code);
- UNUSED(status_description);
-#endif
-
- AUTHENTICATION_STATE* auth_state = (AUTHENTICATION_STATE*)context;
-
- if (operation_result == CBS_OPERATION_RESULT_OK)
- {
- auth_state->status = AUTHENTICATION_STATUS_IDLE;
- }
- else
- {
- // Codes_IOTHUBTRANSPORTAMQP_AUTH_09_066: [If cbs_delete_token calls back with result different than CBS_OPERATION_RESULT_OK, authentication_reset() shall set the state status to AUTHENTICATION_STATUS_FAILURE]
- auth_state->status = AUTHENTICATION_STATUS_FAILURE;
- LogError("CBS reported status code %u, error: %s for delete token operation", status_code, status_description);
- }
-}
-
-static int handSASTokenToCbs(AUTHENTICATION_STATE* auth_state, STRING_HANDLE cbs_audience, STRING_HANDLE sasToken, size_t current_time_in_sec_since_epoch)
-{
- int result;
-
- // Codes_IOTHUBTRANSPORTAMQP_AUTH_09_035: [The SAS token provided shall be sent to CBS using cbs_put_token(), using `servicebus.windows.net:sastoken` as token type and `devices_path` as audience]
- // Codes_IOTHUBTRANSPORTAMQP_AUTH_09_028: [The SAS token shall be sent to CBS using cbs_put_token(), using `servicebus.windows.net:sastoken` as token type and `devices_path` as audience]
- if (cbs_put_token(auth_state->cbs_connection->cbs_handle, SAS_TOKEN_TYPE, STRING_c_str(cbs_audience), STRING_c_str(sasToken), on_put_token_complete, auth_state) != RESULT_OK)
- {
- LogError("Failed applying new SAS token to CBS.");
- result = __FAILURE__;
- }
- else
- {
- // Codes_IOTHUBTRANSPORTAMQP_AUTH_09_029: [If cbs_put_token() succeeds, authentication_authenticate() shall set the state status to AUTHENTICATION_STATUS_IN_PROGRESS]
- // Codes_IOTHUBTRANSPORTAMQP_AUTH_09_037: [If cbs_put_token() succeeds, authentication_authenticate() shall set the state status to AUTHENTICATION_STATUS_IN_PROGRESS]
- auth_state->status = AUTHENTICATION_STATUS_IN_PROGRESS;
- // Codes_IOTHUBTRANSPORTAMQP_AUTH_09_030: [If cbs_put_token() succeeds, authentication_authenticate() shall set `current_sas_token_put_time` with the current time]
- // Codes_IOTHUBTRANSPORTAMQP_AUTH_09_038: [If cbs_put_token() succeeds, authentication_authenticate() shall set `current_sas_token_put_time` with the current time]
- auth_state->cbs_state.current_sas_token_put_time = current_time_in_sec_since_epoch;
- result = RESULT_OK;
- }
-
- return result;
-}
-
-static int verifyAuthenticationTimeout(AUTHENTICATION_STATE* auth_state, bool* timeout_reached)
-{
- int result;
- size_t currentTimeInSeconds;
-
- if (getSecondsSinceEpoch(¤tTimeInSeconds) != RESULT_OK)
- {
- LogError("Failed getting the current time to verify if the SAS token needs to be refreshed.");
- *timeout_reached = true;
- result = __FAILURE__;
- }
- else
- {
- // Codes_IOTHUBTRANSPORTAMQP_AUTH_09_046: [The authentication timeout shall be computed comparing the last time a SAS token was put (`current_sas_token_put_time`) to `cbs_request_timeout`]
- // Codes_IOTHUBTRANSPORTAMQP_AUTH_09_052: [The authentication timeout shall be computed comparing the last time the SAS token was put (`current_sas_token_put_time`) to `cbs_request_timeout`]
- *timeout_reached = ((currentTimeInSeconds - auth_state->cbs_state.current_sas_token_put_time) * 1000 >= auth_state->cbs_connection->cbs_request_timeout) ? true : false;
- result = RESULT_OK;
- }
-
- return result;
-}
-
-static bool isSasTokenRefreshRequired(AUTHENTICATION_STATE* auth_state)
-{
- bool result;
- size_t currentTimeInSeconds;
- if (auth_state->credential.type == DEVICE_SAS_TOKEN)
- {
- result = false;
- }
- else if (getSecondsSinceEpoch(¤tTimeInSeconds) != RESULT_OK)
- {
- LogError("Failed getting the current time to verify if the SAS token needs to be refreshed.");
- result = true; // Fail safe.
- }
- else
- {
- // Codes_IOTHUBTRANSPORTAMQP_AUTH_09_049: [The SAS token expiration shall be computed comparing its create time to `sas_token_refresh_time`]
- result = ((currentTimeInSeconds - auth_state->cbs_state.current_sas_token_create_time) >= (auth_state->cbs_connection->sas_token_refresh_time / 1000)) ? true : false;
- }
-
- return result;
-}
-
-AUTHENTICATION_STATE_HANDLE authentication_create(const AUTHENTICATION_CONFIG* config)
-{
- AUTHENTICATION_STATE* auth_state = NULL;
- bool cleanup_required = true;
-
- // Codes_SRS_IOTHUBTRANSPORTAMQP_AUTH_09_001: [If parameter config, config->device_id, config->iot_hub_host_fqdn or config->cbs_connection are NULL, authentication_create() shall fail and return NULL.]
- if (config == NULL)
- {
- LogError("Failed creating the authentication state (config is NULL)");
- }
- else if (config->device_id == NULL)
- {
- LogError("Failed creating the authentication state (device_id is NULL)");
- }
- else if (config->iot_hub_host_fqdn == NULL)
- {
- LogError("Failed creating the authentication state (iot_hub_host_fqdn is NULL)");
- }
- else if (config->cbs_connection == NULL)
- {
- LogError("Failed creating the authentication state (cbs_connection handle is NULL)");
- }
- // Codes_SRS_IOTHUBTRANSPORTAMQP_AUTH_09_002: [authentication_create() shall allocate memory for a new authenticate state structure AUTHENTICATION_STATE.]
- else if ((auth_state = (AUTHENTICATION_STATE*)malloc(sizeof(AUTHENTICATION_STATE))) == NULL)
- {
- // Codes_SRS_IOTHUBTRANSPORTAMQP_AUTH_09_003: [If malloc() fails, authentication_create() shall fail and return NULL.]
- LogError("Failed creating the authentication state (malloc failed)");
- }
- else
- {
- auth_state->device_id = NULL;
- // Codes_IOTHUBTRANSPORTAMQP_AUTH_09_005: [authentication_create() shall save a reference to the `cbs_connection` into the AUTHENTICATION_STATE instance.]
- auth_state->cbs_connection = config->cbs_connection;
- auth_state->credential.type = CREDENTIAL_NOT_BUILD;
- auth_state->credential.data.deviceKey = NULL;
- auth_state->credential.data.deviceSasToken = NULL;
- auth_state->credential.data.x509credential.x509certificate = NULL;
- auth_state->credential.data.x509credential.x509privatekey = NULL;
- auth_state->cbs_state.sasTokenKeyName = NULL;
- // Codes_IOTHUBTRANSPORTAMQP_AUTH_09_004: [authentication_create() shall set the initial status of AUTHENTICATION_STATE as AUTHENTICATION_STATUS_IDLE.]
- auth_state->status = AUTHENTICATION_STATUS_IDLE;
-
- // Codes_IOTHUBTRANSPORTAMQP_AUTH_09_006: [authentication_create() shall save a copy of `device_config->deviceId` into the AUTHENTICATION_STATE instance.]
- if ((auth_state->device_id = STRING_construct(config->device_id)) == NULL)
- {
- // Codes_IOTHUBTRANSPORTAMQP_AUTH_09_007: [If STRING_construct() fails to copy `device_config->deviceId`, authentication_create() shall fail and return NULL]
- LogError("Failed creating the authentication state (could not copy the deviceId, STRING_construct failed)");
- }
- // Codes_IOTHUBTRANSPORTAMQP_AUTH_09_008: [authentication_create() shall save a copy of `iot_hub_host_fqdn` into the AUTHENTICATION_STATE instance.]
- else if ((auth_state->iot_hub_host_fqdn = STRING_construct(config->iot_hub_host_fqdn)) == NULL)
- {
- // Codes_IOTHUBTRANSPORTAMQP_AUTH_09_009: [If STRING_clone() fails to copy `iot_hub_host_fqdn`, authentication_create() shall fail and return NULL]
- LogError("Failed creating the authentication state (could not clone the devices_path)");
- }
- else
- {
- if (config->device_sas_token != NULL)
- {
- // Codes_IOTHUBTRANSPORTAMQP_AUTH_09_013: [If the credential type is DEVICE_SAS_TOKEN or DEVICE_KEY and parameter cbs_connection is NULL, authentication_create() shall fail and return NULL]
- if (auth_state->cbs_connection == NULL)
- {
- LogError("authentication_create() failed (credential type is DEVICE_SAS_TOKEN but cbs_connection handle is NULL).");
- }
- // Codes_IOTHUBTRANSPORTAMQP_AUTH_09_014: [If the credential type is DEVICE_SAS_TOKEN or DEVICE_KEY, authentication_create() shall set sasTokenKeyName in the AUTHENTICATION_STATE as a non-NULL empty string.]
- else if ((auth_state->cbs_state.sasTokenKeyName = STRING_new()) == NULL)
- {
- // Codes_IOTHUBTRANSPORTAMQP_AUTH_09_015: [If STRING_new() fails and cannot create sasTokenKeyName, authentication_create() shall fail and return NULL]
- LogError("Failed to allocate device_state->sasTokenKeyName.");
- }
- // Codes_IOTHUBTRANSPORTAMQP_AUTH_09_010: [If `device_config->deviceSasToken` is not NULL, authentication_create() shall save a copy into the AUTHENTICATION_STATE instance.]
- else if ((auth_state->credential.data.deviceSasToken = STRING_construct(config->device_sas_token)) == NULL)
- {
- // Codes_IOTHUBTRANSPORTAMQP_AUTH_09_011: [If STRING_construct() fails to copy `device_config->deviceSasToken`, authentication_create() shall fail and return NULL]
- LogError("unable to STRING_construct for deviceSasToken");
- }
- else
- {
- // Codes_IOTHUBTRANSPORTAMQP_AUTH_09_012: [If `device_config->deviceSasToken` is not NULL, authentication_create() shall set the credential type in the AUTHENTICATION_STATE as DEVICE_SAS_TOKEN.]
- auth_state->credential.type = DEVICE_SAS_TOKEN;
- auth_state->cbs_state.current_sas_token_create_time = 0;
- cleanup_required = false;
- }
-
- }
- else if (config->device_key != NULL)
- {
- // Codes_IOTHUBTRANSPORTAMQP_AUTH_09_013: [If the credential type is DEVICE_SAS_TOKEN or DEVICE_KEY and parameter cbs_connection is NULL, authentication_create() shall fail and return NULL]
- if (auth_state->cbs_connection == NULL)
- {
- LogError("authentication_create() failed (credential type is DEVICE_KEY but cbs_connection handle is NULL).");
- }
- // Codes_IOTHUBTRANSPORTAMQP_AUTH_09_014: [If the credential type is DEVICE_SAS_TOKEN or DEVICE_KEY, authentication_create() shall set sasTokenKeyName in the AUTHENTICATION_STATE as a non-NULL empty string.]
- else if ((auth_state->cbs_state.sasTokenKeyName = STRING_new()) == NULL)
- {
- // Codes_IOTHUBTRANSPORTAMQP_AUTH_09_015: [If STRING_new() fails and cannot create sasTokenKeyName, authentication_create() shall fail and return NULL]
- LogError("Failed to allocate device_state->sasTokenKeyName.");
- }
- // Codes_IOTHUBTRANSPORTAMQP_AUTH_09_016: [If `device_config->deviceKey` is not NULL, authentication_create() shall save a copy into the AUTHENTICATION_STATE instance.]
- else if ((auth_state->credential.data.deviceKey = STRING_construct(config->device_key)) == NULL)
- {
- // Codes_IOTHUBTRANSPORTAMQP_AUTH_09_017: [If STRING_construct() fails to copy `device_config->deviceKey`, authentication_create() shall fail and return NULL]
- LogError("unable to STRING_construct for a deviceKey");
- }
- else
- {
- // Codes_IOTHUBTRANSPORTAMQP_AUTH_09_018: [If `device_config->deviceKey` is not NULL, authentication_create() shall set the credential type in the AUTHENTICATION_STATE as DEVICE_KEY.]
- auth_state->credential.type = DEVICE_KEY;
- auth_state->cbs_state.current_sas_token_create_time = 0;
- cleanup_required = false;
- }
- }
- else
- {
- // Codes_IOTHUBTRANSPORTAMQP_AUTH_09_020: [If the credential type is X509 and parameter cbs_connection is not NULL, authentication_create() shall fail and return NULL]
- if (auth_state->cbs_connection == NULL)
- {
- LogError("authentication_create() failed (credential type is X509 but cbs_connection handle is not NULL).");
- }
- else
- {
- // Codes_IOTHUBTRANSPORTAMQP_AUTH_09_019: [If `device_config->deviceKey` and `device_config->deviceSasToken` are NULL, authentication_create() shall set the credential type in the AUTHENTICATION_STATE as X509]
- auth_state->credential.type = X509;
- auth_state->credential.data.x509credential.x509certificate = NULL;
- auth_state->credential.data.x509credential.x509privatekey = NULL;
- cleanup_required = false;
- }
- }
- }
- }
-
- // Codes_IOTHUBTRANSPORTAMQP_AUTH_09_021: [If any failure occurs, authentication_create() shall free any memory it allocated previously, including the AUTHENTICATION_STATE]
- if (cleanup_required)
- {
- if (auth_state->credential.data.deviceKey != NULL)
- STRING_delete(auth_state->credential.data.deviceKey);
- if (auth_state->iot_hub_host_fqdn != NULL)
- STRING_delete(auth_state->iot_hub_host_fqdn);
- if (auth_state->credential.data.deviceSasToken != NULL)
- STRING_delete(auth_state->credential.data.deviceSasToken);
- if (auth_state->cbs_state.sasTokenKeyName != NULL)
- STRING_delete(auth_state->cbs_state.sasTokenKeyName);
- if (auth_state != NULL)
- free(auth_state);
- }
-
- // Codes_IOTHUBTRANSPORTAMQP_AUTH_09_022: [If no failure occurs, authentication_create() shall return a reference to the AUTHENTICATION_STATE handle]
- return (AUTHENTICATION_STATE_HANDLE)auth_state;
-}
-
-static STRING_HANDLE concatenate_3_strings(const char* prefix, const char* infix, const char* suffix)
-{
- STRING_HANDLE result = NULL;
- char* concat;
- size_t totalLength = strlen(prefix) + strlen(infix) + strlen(suffix) + 1; // One extra for \0.
-
- if ((concat = (char*)malloc(totalLength)) != NULL)
- {
- (void)strcpy(concat, prefix);
- (void)strcat(concat, infix);
- (void)strcat(concat, suffix);
- result = STRING_construct(concat);
- free(concat);
- }
- else
- {
- result = NULL;
- }
-
- return result;
-}
-
-static STRING_HANDLE create_devices_path(STRING_HANDLE device_id, STRING_HANDLE iothub_host_fqdn)
-{
- STRING_HANDLE devices_path;
-
- if ((devices_path = concatenate_3_strings(STRING_c_str(device_id), "/devices/", STRING_c_str(iothub_host_fqdn))) == NULL)
- {
- LogError("Could not create the devices_path parameter (concatenate_3_strings failed)");
- }
-
- return devices_path;
-}
-
-int authentication_authenticate(AUTHENTICATION_STATE_HANDLE authentication_state_handle)
-{
- int result;
-
- // Codes_SRS_IOTHUBTRANSPORTAMQP_AUTH_09_023: [If authentication_state is NULL, authentication_authenticate() shall fail and return an error code]
- if (authentication_state_handle == NULL)
- {
- result = __FAILURE__;
- LogError("Failed to authenticate device (the authentication_state_handle is NULL)");
- }
- else
- {
- AUTHENTICATION_STATE* auth_state = (AUTHENTICATION_STATE*)authentication_state_handle;
-
- switch (auth_state->credential.type)
- {
- // Codes_IOTHUBTRANSPORTAMQP_AUTH_09_024: [If the credential type is DEVICE_KEY, authentication_authenticate() shall create a SAS token and put it to CBS]
- case DEVICE_KEY:
- {
- size_t currentTimeInSeconds;
-
- if ((result = getSecondsSinceEpoch(¤tTimeInSeconds)) != RESULT_OK)
- {
- LogError("Failed getting current time to compute the SAS token creation time (%d).", result);
- result = __FAILURE__;
- }
- else
- {
- STRING_HANDLE devices_path = NULL;
- STRING_HANDLE newSASToken = NULL;
-
- // Codes_IOTHUBTRANSPORTAMQP_AUTH_09_026: [The SAS token expiration time shall be calculated adding `sas_token_lifetime` to the current number of seconds since epoch time UTC]
- size_t new_expiry_time = currentTimeInSeconds + (auth_state->cbs_connection->sas_token_lifetime / 1000);
-
- // Codes_SRS_IOTHUBTRANSPORTAMQP_AUTH_09_076: [A STRING_HANDLE, referred to as `devices_path`, shall be created from the following parts: iot_hub_host_fqdn + "/devices/" + device_id]
- if ((devices_path = create_devices_path(auth_state->iot_hub_host_fqdn, auth_state->device_id)) == NULL)
- {
- // Codes_SRS_IOTHUBTRANSPORTAMQP_AUTH_09_077: [If `devices_path` failed to be created, authentication_authenticate() shall fail and return an error code]
- LogError("Failed to authenticate device (could not create the devices_path parameter)");
- result = __FAILURE__;
- }
- // Codes_IOTHUBTRANSPORTAMQP_AUTH_09_025: [The SAS token shall be created using SASToken_Create(), passing the deviceKey, device_path, sasTokenKeyName and expiration time as arguments]
- else if ((newSASToken = SASToken_Create(auth_state->credential.data.deviceKey, devices_path, auth_state->cbs_state.sasTokenKeyName, new_expiry_time)) == NULL)
- {
- // Codes_IOTHUBTRANSPORTAMQP_AUTH_09_027: [If SASToken_Create() fails, authentication_authenticate() shall fail and return an error code]
- LogError("Could not generate a new SAS token for the CBS.");
- result = __FAILURE__;
- }
- else
- {
- auth_state->cbs_state.current_sas_token_create_time = currentTimeInSeconds;
-
- if (handSASTokenToCbs(auth_state, devices_path, newSASToken, currentTimeInSeconds) != 0)
- {
- // Codes_IOTHUBTRANSPORTAMQP_AUTH_09_032: [If cbs_put_token() fails, authentication_authenticate() shall fail and return an error code]
- LogError("unable to send the new SASToken to CBS");
- result = __FAILURE__;
- }
- else
- {
- // Codes_IOTHUBTRANSPORTAMQP_AUTH_09_033: [If cbs_put_token() succeeds, authentication_authenticate() shall return success code 0]
- result = RESULT_OK;
- }
-
- // Codes_IOTHUBTRANSPORTAMQP_AUTH_09_031: [authentication_authenticate() shall free the memory allocated for the new SAS token using STRING_delete()]
- STRING_delete(newSASToken);
- }
-
- if (devices_path != NULL)
- STRING_delete(devices_path);
- }
-
- break;
- }
- // Codes_IOTHUBTRANSPORTAMQP_AUTH_09_034: [If the credential type is DEVICE_SAS_TOKEN, authentication_authenticate() shall put the SAS token provided to CBS]
- case DEVICE_SAS_TOKEN:
- {
- size_t currentTimeInSeconds;
-
- if ((result = getSecondsSinceEpoch(¤tTimeInSeconds)) != RESULT_OK)
- {
- LogError("Failed getting current time to compute the SAS token creation time (%d).", result);
- result = __FAILURE__;
- }
- else
- {
- STRING_HANDLE devices_path = NULL;
-
- // Codes_SRS_IOTHUBTRANSPORTAMQP_AUTH_09_078: [A STRING_HANDLE, referred to as `devices_path`, shall be created from the following parts: iot_hub_host_fqdn + "/devices/" + device_id]
- if ((devices_path = create_devices_path(auth_state->iot_hub_host_fqdn, auth_state->device_id)) == NULL)
- {
- // Codes_SRS_IOTHUBTRANSPORTAMQP_AUTH_09_079: [If `devices_path` failed to be created, authentication_authenticate() shall fail and return an error code]
- LogError("Failed to authenticate device (could not create the devices_path parameter)");
- result = __FAILURE__;
- }
- else if (handSASTokenToCbs(auth_state, devices_path, auth_state->credential.data.deviceSasToken, currentTimeInSeconds) != 0)
- {
- // Codes_IOTHUBTRANSPORTAMQP_AUTH_09_036: [If cbs_put_token() fails, authentication_authenticate() shall fail and return an error code]
- LogError("unable to send the new SASToken to CBS");
- result = __FAILURE__;
- }
- else
- {
- // Codes_IOTHUBTRANSPORTAMQP_AUTH_09_040: [If cbs_put_token() succeeds, authentication_authenticate() shall return success code 0]
- result = RESULT_OK;
- }
-
- if (devices_path != NULL)
- STRING_delete(devices_path);
- }
- break;
- }
- // Codes_IOTHUBTRANSPORTAMQP_AUTH_09_043: [If the credential type is X509, authentication_authenticate() shall not perform any authentication and return a success code]
- case X509:
- if (auth_state->status != AUTHENTICATION_STATUS_IDLE)
- {
- LogError("Failed to authenticate device [X509] (authentication status %d is invalid)", auth_state->status);
- result = __FAILURE__;
- }
- else
- {
- auth_state->status = AUTHENTICATION_STATUS_OK;
- result = RESULT_OK;
- }
- break;
- default:
- result = __FAILURE__;
- LogError("Failed to authenticate the device (unexpected credential type %d)", auth_state->credential.type);
- break;
- }
- }
-
- return result;
-}
-
-AUTHENTICATION_STATUS authentication_get_status(AUTHENTICATION_STATE_HANDLE authentication_state_handle)
-{
- AUTHENTICATION_STATUS auth_status = AUTHENTICATION_STATUS_IDLE;
-
- // Codes_IOTHUBTRANSPORTAMQP_AUTH_09_044: [If authentication_state is NULL, authentication_get_status() shall fail and return AUTHENTICATION_STATUS_NONE]
- if (authentication_state_handle == NULL)
- {
- LogError("Failed retrieving the authentication status (the authentication_state_handle is NULL)");
- auth_status = AUTHENTICATION_STATUS_NONE;
- }
- else
- {
- AUTHENTICATION_STATE* auth_state = (AUTHENTICATION_STATE*)authentication_state_handle;
-
- switch (auth_state->credential.type)
- {
- case DEVICE_KEY:
- // Codes_IOTHUBTRANSPORTAMQP_AUTH_09_045: [If the credential type is DEVICE_KEY and current status is AUTHENTICATION_STATUS_IN_PROGRESS, authentication_get_status() shall check for authentication timeout]
- if (auth_state->status == AUTHENTICATION_STATUS_IN_PROGRESS)
- {
- bool timeout_reached;
-
- if (verifyAuthenticationTimeout(auth_state, &timeout_reached) != RESULT_OK)
- {
- LogError("Failed retrieving the status of the authentication (failed verifying if the authentication is expired)");
- auth_state->status = AUTHENTICATION_STATUS_FAILURE;
- }
- else if (timeout_reached)
- {
- // Codes_IOTHUBTRANSPORTAMQP_AUTH_09_047: [If authentication has timed out, authentication_get_status() shall set the status of the state to AUTHENTICATION_STATUS_TIMEOUT]
- auth_state->status = AUTHENTICATION_STATUS_TIMEOUT;
- }
- }
- // Codes_IOTHUBTRANSPORTAMQP_AUTH_09_048: [If the credential type is DEVICE_KEY and current status is AUTHENTICATION_STATUS_OK, authentication_get_status() shall check if SAS token must be refreshed]
- else if (auth_state->status == AUTHENTICATION_STATUS_OK && isSasTokenRefreshRequired(auth_state))
- {
- // Codes_IOTHUBTRANSPORTAMQP_AUTH_09_050: [If the SAS token must be refreshed, authentication_get_status() shall set the status of the state to AUTHENTICATION_STATUS_REFRESH_REQUIRED]
- auth_state->status = AUTHENTICATION_STATUS_REFRESH_REQUIRED;
- }
- break;
- case DEVICE_SAS_TOKEN:
- if (auth_state->status == AUTHENTICATION_STATUS_IN_PROGRESS)
- {
- // Codes_IOTHUBTRANSPORTAMQP_AUTH_09_051: [If the credential type is DEVICE_SAS_TOKEN and current status is AUTHENTICATION_STATUS_IN_PROGRESS, authentication_get_status() shall check for authentication timeout]
- bool timeout_reached;
- if (verifyAuthenticationTimeout(auth_state, &timeout_reached) != RESULT_OK)
- {
- LogError("Failed retrieving the status of the authentication (failed verifying if the authentication is expired)");
- auth_state->status = AUTHENTICATION_STATUS_FAILURE;
- }
- else if (timeout_reached)
- {
- // Codes_IOTHUBTRANSPORTAMQP_AUTH_09_053: [If authentication has timed out, authentication_get_status() shall set the status of the state to AUTHENTICATION_STATUS_TIMEOUT]
- auth_state->status = AUTHENTICATION_STATUS_TIMEOUT;
- }
- }
- break;
- case X509:
- break;
- default:
- LogError("Failed to retrieve the authentication status (unexpected credential type %d)", auth_state->credential.type);
- break;
- }
-
- // Codes_IOTHUBTRANSPORTAMQP_AUTH_09_054: [After checks and updates, authentication_get_status() shall return the status saved on the AUTHENTICATION_STATE]
- auth_status = auth_state->status;
- }
-
- return auth_status;
-}
-
-AMQP_TRANSPORT_CREDENTIAL* authentication_get_credential(AUTHENTICATION_STATE_HANDLE authentication_state_handle)
-{
- AMQP_TRANSPORT_CREDENTIAL* credential;
-
- // Codes_SRS_IOTHUBTRANSPORTAMQP_AUTH_09_056: [If authentication_state is NULL, authentication_get_credential() shall fail and return NULL]
- if (authentication_state_handle == NULL)
- {
- LogError("authentication_get_credential() failed (authentication_state_handle is NULL)");
- credential = NULL;
- }
- // Codes_SRS_IOTHUBTRANSPORTAMQP_AUTH_09_055: [If authentication_state is not NULL, authentication_get_credential() shall return a reference to credentials saved in the AUTHENTICATION_STATE]
- else
- {
- AUTHENTICATION_STATE* auth_state = (AUTHENTICATION_STATE*)authentication_state_handle;
- credential = &auth_state->credential;
- }
-
- return credential;
-}
-
-int authentication_refresh(AUTHENTICATION_STATE_HANDLE authentication_state_handle)
-{
- int result;
-
- // Codes_IOTHUBTRANSPORTAMQP_AUTH_09_057: [If authentication_state is NULL, authentication_refresh() shall fail and return error code]
- if (authentication_state_handle == NULL)
- {
- LogError("authentication_refresh() failed (authentication_state_handle is NULL)");
- result = __FAILURE__;
- }
- else
- {
- AUTHENTICATION_STATE* auth_state = (AUTHENTICATION_STATE*)authentication_state_handle;
-
- // Codes_IOTHUBTRANSPORTAMQP_AUTH_09_058: [If the credential type is X509, authentication_refresh() shall return with success code 0]
- if (auth_state->credential.type == X509)
- {
- result = RESULT_OK;
- }
- // Codes_IOTHUBTRANSPORTAMQP_AUTH_09_059: [authentication_refresh() shall invoke authentication_authenticate(), passing the authentication_state handle]
- else if ((result = authentication_authenticate(authentication_state_handle)) != RESULT_OK)
- {
- // Codes_IOTHUBTRANSPORTAMQP_AUTH_09_060: [if authentication_authenticate() fails, authentication_refresh() shall fail and return an error code]
- LogError("Failed refreshing authentication (%d).", result);
- result = __FAILURE__;
- }
- }
-
- return result;
-}
-
-int authentication_reset(AUTHENTICATION_STATE_HANDLE authentication_state_handle)
-{
- int result;
-
- // Codes_IOTHUBTRANSPORTAMQP_AUTH_09_061: [If authentication_state is NULL, authentication_reset() shall fail and return an error code]
- if (authentication_state_handle == NULL)
- {
- LogError("Failed to reset the authentication state (authentication_state is NULL)");
- result = __FAILURE__;
- }
- else
- {
- AUTHENTICATION_STATE* auth_state = (AUTHENTICATION_STATE*)authentication_state_handle;
-
- switch (auth_state->credential.type)
- {
- case DEVICE_KEY:
- case DEVICE_SAS_TOKEN:
- {
- STRING_HANDLE devices_path = NULL;
-
- if (auth_state->status == AUTHENTICATION_STATUS_FAILURE || auth_state->status == AUTHENTICATION_STATUS_REFRESH_REQUIRED || auth_state->status == AUTHENTICATION_STATUS_TIMEOUT)
- {
- // Codes_IOTHUBTRANSPORTAMQP_AUTH_09_063: [If the authentication_state status is AUTHENTICATION_STATUS_FAILURE or AUTHENTICATION_STATUS_REFRESH_REQUIRED, authentication_reset() shall set the status to AUTHENTICATION_STATUS_IDLE]
- auth_state->status = AUTHENTICATION_STATUS_IDLE;
- result = RESULT_OK;
- }
- else if (auth_state->status != AUTHENTICATION_STATUS_OK && auth_state->status != AUTHENTICATION_STATUS_IN_PROGRESS)
- {
- LogError("Failed to reset the authentication state (authentication status is invalid: %i)", auth_state->status);
- result = __FAILURE__;
- }
- // Codes_IOTHUBTRANSPORTAMQP_AUTH_09_064: [If the authentication_state status is AUTHENTICATION_STATUS_OK or AUTHENTICATION_STATUS_IN_PROGRESS, authentication_reset() delete the previous token using cbs_delete_token()]
- else if ((devices_path = create_devices_path(auth_state->iot_hub_host_fqdn, auth_state->device_id)) == NULL)
- {
- LogError("Failed to reset the authenticate state (could not create the devices_path parameter for cbs_delete_token)");
- result = __FAILURE__;
- }
- else if (cbs_delete_token(auth_state->cbs_connection->cbs_handle, STRING_c_str(devices_path), SAS_TOKEN_TYPE, on_delete_token_complete, auth_state) != RESULT_OK)
- {
- // Codes_IOTHUBTRANSPORTAMQP_AUTH_09_065: [If cbs_delete_token fails, authentication_reset() shall fail and return an error code]
- LogError("Failed to reset the authentication state (failed deleting the current SAS token from CBS)");
- result = __FAILURE__;
- }
- else
- {
- // Codes_IOTHUBTRANSPORTAMQP_AUTH_09_067: [If no error occurs, authentication_reset() shall return 0]
- auth_state->status = AUTHENTICATION_STATUS_IDLE;
- auth_state->cbs_state.current_sas_token_create_time = 0;
- result = RESULT_OK;
- }
- break;
- }
- case X509:
- // Codes_IOTHUBTRANSPORTAMQP_AUTH_09_062: [If the credential type is X509, authentication_reset() shall set the status to AUTHENTICATION_STATUS_IDLE and return with success code 0]
- auth_state->status = AUTHENTICATION_STATUS_IDLE;
- result = RESULT_OK;
- break;
- default:
- result = __FAILURE__;
- LogError("Failed to reset the authentication state (unexpected credential type %d)", auth_state->credential.type);
- break;
- }
- }
-
- return result;
-}
-
-void authentication_destroy(AUTHENTICATION_STATE_HANDLE authentication_state_handle)
-{
- // Codes_IOTHUBTRANSPORTAMQP_AUTH_09_068: [If authentication_state is NULL, authentication_destroy() shall fail and return]
- if (authentication_state_handle == NULL)
- {
- LogError("Failed to destroy the authentication state (authentication_state is NULL)");
- }
- else
- {
- AUTHENTICATION_STATE* auth_state = (AUTHENTICATION_STATE*)authentication_state_handle;
-
- // Codes_IOTHUBTRANSPORTAMQP_AUTH_09_069: [authentication_destroy() shall destroy the AUTHENTICATION_STATE->device_id using STRING_delete()]
- STRING_delete(auth_state->device_id);
- // Codes_IOTHUBTRANSPORTAMQP_AUTH_09_070: [authentication_destroy() shall destroy the AUTHENTICATION_STATE->iot_hub_host_fqdn using STRING_delete()]
- STRING_delete(auth_state->iot_hub_host_fqdn);
-
- switch (auth_state->credential.type)
- {
- case (CREDENTIAL_NOT_BUILD):
- case(X509):
- // Codes_IOTHUBTRANSPORTAMQP_AUTH_09_075: [authentication_destroy() shall free the AUTHENTICATION_STATE]
- free(auth_state);
- break;
- case(DEVICE_KEY):
- // Codes_IOTHUBTRANSPORTAMQP_AUTH_09_071: [If the credential type is DEVICE_KEY, authentication_destroy() shall destroy `deviceKey` in AUTHENTICATION_STATE using STRING_delete()]
- STRING_delete(auth_state->credential.data.deviceKey);
- // Codes_IOTHUBTRANSPORTAMQP_AUTH_09_072: [If the credential type is DEVICE_KEY, authentication_destroy() shall destroy `sasTokenKeyName` in AUTHENTICATION_STATE using STRING_delete()]
- STRING_delete(auth_state->cbs_state.sasTokenKeyName);
- // Codes_IOTHUBTRANSPORTAMQP_AUTH_09_075: [authentication_destroy() shall free the AUTHENTICATION_STATE]
- free(auth_state);
- break;
- case(DEVICE_SAS_TOKEN):
- // Codes_IOTHUBTRANSPORTAMQP_AUTH_09_073: [If the credential type is DEVICE_SAS_TOKEN, authentication_destroy() shall destroy `deviceSasToken` in AUTHENTICATION_STATE using STRING_delete()]
- STRING_delete(auth_state->credential.data.deviceSasToken);
- // Codes_IOTHUBTRANSPORTAMQP_AUTH_09_074: [If the credential type is DEVICE_SAS_TOKEN, authentication_destroy() shall destroy `sasTokenKeyName` in AUTHENTICATION_STATE using STRING_delete()]
- STRING_delete(auth_state->cbs_state.sasTokenKeyName);
- // Codes_IOTHUBTRANSPORTAMQP_AUTH_09_075: [authentication_destroy() shall free the AUTHENTICATION_STATE]
- free(auth_state);
- break;
- default:
- LogError("Failed to destroy the authentication state (unexpected credential type %d)", auth_state->credential.type);
- break;
- }
- }
-}
--- a/iothubtransportamqp_auth.h Fri Feb 24 14:00:00 2017 -0800
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,152 +0,0 @@
-// Copyright (c) Microsoft. All rights reserved.
-// Licensed under the MIT license. See LICENSE file in the project root for full license information.
-
-#ifndef IOTHUBTRANSPORTAMQP_AUTH_H
-#define IOTHUBTRANSPORTAMQP_AUTH_H
-
-#include <stdlib.h>
-#include <stdint.h>
-#include "azure_c_shared_utility/strings.h"
-#include "azure_c_shared_utility/sastoken.h"
-#include "azure_c_shared_utility/xio.h"
-#include "azure_uamqp_c/cbs.h"
-#include "azure_uamqp_c/sasl_mechanism.h"
-#include "iothub_transport_ll.h"
-#include "azure_c_shared_utility/umock_c_prod.h"
-
-#ifdef __cplusplus
-extern "C"
-{
-#endif
-
-typedef struct AMQP_TRANSPORT_CBS_CONNECTION_TAG
-{
- // How long a SAS token created by the transport is valid, in milliseconds.
- size_t sas_token_lifetime;
- // Maximum period of time for the transport to wait before refreshing the SAS token it created previously, in milliseconds.
- size_t sas_token_refresh_time;
- // Maximum time the transport waits for uAMQP cbs_put_token() to complete before marking it a failure, in milliseconds.
- size_t cbs_request_timeout;
-
- // AMQP SASL I/O transport created on top of the TLS I/O layer.
- XIO_HANDLE sasl_io;
- // AMQP SASL I/O mechanism to be used.
- SASL_MECHANISM_HANDLE sasl_mechanism;
- // Connection instance with the Azure IoT CBS.
- CBS_HANDLE cbs_handle;
-} AMQP_TRANSPORT_CBS_CONNECTION;
-
-typedef struct AUTHENTICATION_CONFIG_TAG
-{
- const char* device_id;
- const char* device_key;
- const char* device_sas_token;
- const char* iot_hub_host_fqdn;
- const AMQP_TRANSPORT_CBS_CONNECTION* cbs_connection;
-
-} AUTHENTICATION_CONFIG;
-
-typedef enum AUTHENTICATION_STATUS_TAG
-{
- AUTHENTICATION_STATUS_IDLE,
- AUTHENTICATION_STATUS_IN_PROGRESS,
- AUTHENTICATION_STATUS_TIMEOUT,
- AUTHENTICATION_STATUS_REFRESH_REQUIRED,
- AUTHENTICATION_STATUS_FAILURE,
- AUTHENTICATION_STATUS_OK,
- AUTHENTICATION_STATUS_NONE
-} AUTHENTICATION_STATUS;
-
-typedef enum AMQP_TRANSPORT_CREDENTIAL_TYPE_TAG
-{
- CREDENTIAL_NOT_BUILD,
- X509,
- DEVICE_KEY,
- DEVICE_SAS_TOKEN,
-} AMQP_TRANSPORT_CREDENTIAL_TYPE;
-
-typedef struct X509_CREDENTIAL_TAG
-{
- const char* x509certificate;
- const char* x509privatekey;
-} X509_CREDENTIAL;
-
-typedef union AMQP_TRANSPORT_CREDENTIAL_DATA_TAG
-{
- // Key associated to the device to be used.
- STRING_HANDLE deviceKey;
-
- // SAS associated to the device to be used.
- STRING_HANDLE deviceSasToken;
-
- // X509
- X509_CREDENTIAL x509credential;
-} AMQP_TRANSPORT_CREDENTIAL_DATA;
-
-typedef struct AMQP_TRANSPORT_CREDENTIAL_TAG
-{
- AMQP_TRANSPORT_CREDENTIAL_TYPE type;
- AMQP_TRANSPORT_CREDENTIAL_DATA data;
-} AMQP_TRANSPORT_CREDENTIAL;
-
-struct AUTHENTICATION_STATE;
-typedef struct AUTHENTICATION_STATE* AUTHENTICATION_STATE_HANDLE;
-
-/** @brief Creates a state holder for all authentication-related information and connections.
-*
-* @returns an instance of the AUTHENTICATION_STATE_HANDLE if succeeds, NULL if any failure occurs.
-*/
-MOCKABLE_FUNCTION(, AUTHENTICATION_STATE_HANDLE, authentication_create, const AUTHENTICATION_CONFIG*, config);
-
-/** @brief Establishes the first authentication for the device in the transport it is registered to.
-*
-* @details If SAS token or key are used, creates a cbs instance for the transport if it does not have one,
-* and puts a SAS token in (creates one if key is used, or applies the SAS token if provided by user).
-* If certificates are used, they are set on the tls_io instance of the transport.
-*
-* @returns 0 if it succeeds, non-zero if it fails.
-*/
-MOCKABLE_FUNCTION(, int, authentication_authenticate, AUTHENTICATION_STATE_HANDLE, authentication_state);
-
-/** @brief Indicates if the device is authenticated successfuly, if authentication is in progress or completed with failure.
-*
-* @returns A flag indicating the current authentication status of the device.
-*/
-MOCKABLE_FUNCTION(, AUTHENTICATION_STATUS, authentication_get_status, AUTHENTICATION_STATE_HANDLE, authentication_state);
-
-/** @brief Gets the credential stored by the handle for authenticating the device.
-*
-* @returns A AMQP_TRANSPORT_CREDENTIAL with the credentials type and data.
-*/
-MOCKABLE_FUNCTION(, AMQP_TRANSPORT_CREDENTIAL*, authentication_get_credential, AUTHENTICATION_STATE_HANDLE, authentication_state);
-
-/** @brief Refreshes the authentication if needed.
-*
-* @details If SAS key is used, a new token is generated and put to cbs if the previous generated token is expired.
-*
-* @returns 0 if it succeeds, non-zero if it fails.
-*/
-MOCKABLE_FUNCTION(, int, authentication_refresh, AUTHENTICATION_STATE_HANDLE, authentication_state);
-
-/** @brief Resets the state of the authentication.
-*
-* @details Causes the status of the authentication state to be reset to IDLE (similar to when the state is created).
-*
-* @returns 0 if it succeeds, non-zero if it fails.
-*/
-MOCKABLE_FUNCTION(, int, authentication_reset, AUTHENTICATION_STATE_HANDLE, authentication_state);
-
-/** @brief De-authenticates the device and destroy the state instance.
-*
-* @details Closes the subscription to cbs if in use, destroys the cbs instance if it is the last device registered.
-* No action is taken if certificate-based authentication if used.
-*
-* @returns Nothing.
-*/
-MOCKABLE_FUNCTION(, void, authentication_destroy, AUTHENTICATION_STATE_HANDLE, authentication_state);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif /*IOTHUBTRANSPORTAMQP_AUTH_H*/
\ No newline at end of file
