// 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 <stdint.h>
#include <stdbool.h>
#include "azure_c_shared_utility/gballoc.h"
#include "azure_c_shared_utility/platform.h"
#include "azure_c_shared_utility/xlogging.h"
#include "azure_c_shared_utility/crt_abstractions.h"
#include "azure_c_shared_utility/tlsio.h"
#include "azure_c_shared_utility/shared_util_options.h"
#include "azure_c_shared_utility/http_proxy_io.h"
#include "azure_c_shared_utility/urlencode.h"
#include "azure_c_shared_utility/http_proxy_io.h"
#include "azure_c_shared_utility/strings.h"
#include "azure_c_shared_utility/azure_base64.h"

#include "azure_uamqp_c/message_sender.h"
#include "azure_uamqp_c/message_receiver.h"
#include "azure_uamqp_c/message.h"
#include "azure_uamqp_c/messaging.h"
#include "azure_uamqp_c/saslclientio.h"
#include "azure_uamqp_c/sasl_plain.h"
#include "azure_uamqp_c/amqp_definitions_application_properties.h"

#include "azure_prov_client/internal/prov_sasl_tpm.h"
#include "azure_prov_client/prov_client_const.h"
#include "azure_prov_client/internal/prov_transport_amqp_common.h"

#define AMQP_MAX_SENDER_MSG_SIZE    UINT64_MAX
#define AMQP_MAX_RECV_MSG_SIZE      65536

static const char* const AMQP_ADDRESS_FMT = "amqps://%s/%s/registrations/%s";
static const char* const AMQP_SASL_USERNAME_FMT = "%s/registrations/%s";
static const char* const KEY_NAME_VALUE = "registration";

static const char* const AMQP_REGISTER_ME = "iotdps-register";
static const char* const AMQP_OPERATION_STATUS = "iotdps-get-operationstatus";

static const char* const AMQP_API_VERSION_KEY = "com.microsoft:api-version";

static const char* const AMQP_OP_TYPE_PROPERTY = "iotdps-operation-type";
static const char* const AMQP_OPERATION_ID = "iotdps-operation-id";

typedef enum AMQP_TRANSPORT_STATE_TAG
{
    AMQP_STATE_IDLE,
    AMQP_STATE_DISCONNECTED,
    AMQP_STATE_CONNECTING,
    AMQP_STATE_CONNECTED,
    AMQP_STATE_ERROR
} AMQP_TRANSPORT_STATE;

typedef enum TRANSPORT_CLIENT_STATE_TAG
{
    TRANSPORT_CLIENT_STATE_IDLE,

    TRANSPORT_CLIENT_STATE_REG_SEND,
    TRANSPORT_CLIENT_STATE_REG_SENT,
    TRANSPORT_CLIENT_STATE_REG_RECV,

    TRANSPORT_CLIENT_STATE_STATUS_SEND,
    TRANSPORT_CLIENT_STATE_STATUS_SENT,
    TRANSPORT_CLIENT_STATE_STATUS_RECV,


    TRANSPORT_CLIENT_STATE_ERROR
} TRANSPORT_CLIENT_STATE;

typedef struct PROV_TRANSPORT_AMQP_INFO_TAG
{
    PROV_DEVICE_TRANSPORT_REGISTER_CALLBACK register_data_cb;
    void* user_ctx;
    PROV_DEVICE_TRANSPORT_STATUS_CALLBACK status_cb;
    void* status_ctx;
    PROV_TRANSPORT_CHALLENGE_CALLBACK challenge_cb;
    void* challenge_ctx;
    PROV_TRANSPORT_JSON_PARSE json_parse_cb;
    PROV_TRANSPORT_CREATE_JSON_PAYLOAD json_create_cb;
    void* json_ctx;

    char* hostname;

    HTTP_PROXY_OPTIONS proxy_option;

    char* x509_cert;
    char* private_key;

    char* certificate;

    BUFFER_HANDLE ek;
    BUFFER_HANDLE srk;
    char* registration_id;
    char* scope_id;
    char* sas_token;
    char* operation_id;

    char* api_version;

    char* payload_data;

    bool log_trace;
    uint32_t retry_after_value;

    TRANSPORT_HSM_TYPE hsm_type;

    PROV_AMQP_TRANSPORT_IO transport_io_cb;

    TRANSPORT_CLIENT_STATE transport_state;
    AMQP_TRANSPORT_STATE amqp_state;

    MESSAGE_RECEIVER_STATE msg_recv_state;
    MESSAGE_SENDER_STATE msg_send_state;

    SASL_MECHANISM_HANDLE sasl_handler;
    SASLCLIENTIO_CONFIG sasl_io_config;
    CONNECTION_HANDLE connection;
    XIO_HANDLE transport_io;
    XIO_HANDLE underlying_io;
    SESSION_HANDLE session;
    LINK_HANDLE sender_link;
    LINK_HANDLE receiver_link;
    MESSAGE_SENDER_HANDLE msg_sender;
    MESSAGE_RECEIVER_HANDLE msg_receiver;

    PROV_TRANSPORT_ERROR_CALLBACK error_cb;
    void* error_ctx;
} PROV_TRANSPORT_AMQP_INFO;

static char* on_sasl_tpm_challenge_cb(BUFFER_HANDLE data_handle, void* user_ctx)
{
    char* result;
    PROV_TRANSPORT_AMQP_INFO* amqp_info = (PROV_TRANSPORT_AMQP_INFO*)user_ctx;
    if (amqp_info == NULL)
    {
        LogError("Bad argument user_ctx NULL");
        result = NULL;
    }
    else if (amqp_info->challenge_cb == NULL)
    {
        LogError("challenge callback is NULL, device needs to register");
        result = NULL;
    }
    else
    {
        const unsigned char* data = BUFFER_u_char(data_handle);
        size_t length = BUFFER_length(data_handle);

        result = amqp_info->challenge_cb(data, length, KEY_NAME_VALUE, amqp_info->challenge_ctx);
        if (result == NULL)
        {
            LogError("Failure challenge_cb");
            amqp_info->transport_state = TRANSPORT_CLIENT_STATE_ERROR;
            amqp_info->amqp_state = AMQP_STATE_ERROR;
        }
    }
    return result;
}

static void on_message_sender_state_changed_callback(void* context, MESSAGE_SENDER_STATE new_state, MESSAGE_SENDER_STATE previous_state)
{
    if (context == NULL)
    {
        LogError("on_message_sender_state_changed_callback was invoked with a NULL context");
    }
    else
    {
        if (new_state != previous_state)
        {
            PROV_TRANSPORT_AMQP_INFO* amqp_info = (PROV_TRANSPORT_AMQP_INFO*)context;
            amqp_info->msg_send_state = new_state;
            switch (amqp_info->msg_send_state)
            {
                case MESSAGE_SENDER_STATE_IDLE:
                case MESSAGE_SENDER_STATE_OPENING:
                case MESSAGE_SENDER_STATE_CLOSING:
                    break;
                case MESSAGE_SENDER_STATE_OPEN:
                    if (amqp_info->msg_recv_state == MESSAGE_RECEIVER_STATE_OPEN)
                    {
                        amqp_info->amqp_state = AMQP_STATE_CONNECTED;
                        if (amqp_info->status_cb != NULL)
                        {
                            amqp_info->status_cb(PROV_DEVICE_TRANSPORT_STATUS_CONNECTED, amqp_info->retry_after_value, amqp_info->status_ctx);
                        }
                    }
                    break;
                case MESSAGE_SENDER_STATE_ERROR:
                    amqp_info->transport_state = TRANSPORT_CLIENT_STATE_ERROR;
                    amqp_info->amqp_state = AMQP_STATE_ERROR;
                    break;
                default:
                    LogError("on_message_sender_state_changed_callback received unexpected state %d", amqp_info->msg_recv_state);

                    amqp_info->amqp_state = AMQP_STATE_ERROR;
                    amqp_info->transport_state = TRANSPORT_CLIENT_STATE_ERROR;
                    if (amqp_info->status_cb != NULL)
                    {
                        amqp_info->status_cb(PROV_DEVICE_TRANSPORT_STATUS_ERROR, amqp_info->retry_after_value, amqp_info->status_ctx);
                    }
            }
        }
    }
}

static void on_message_receiver_state_changed_callback(const void* user_ctx, MESSAGE_RECEIVER_STATE new_state, MESSAGE_RECEIVER_STATE previous_state)
{
    if (user_ctx == NULL)
    {
        LogError("on_message_receiver_state_changed_callback was invoked with a NULL context");
    }
    else
    {
        if (new_state != previous_state)
        {
            PROV_TRANSPORT_AMQP_INFO* amqp_info = (PROV_TRANSPORT_AMQP_INFO*)user_ctx;
            amqp_info->msg_recv_state = new_state;
            switch (amqp_info->msg_recv_state)
            {
            case MESSAGE_RECEIVER_STATE_IDLE:
            case MESSAGE_RECEIVER_STATE_OPENING:
            case MESSAGE_RECEIVER_STATE_CLOSING:
                break;
            case MESSAGE_RECEIVER_STATE_OPEN:
                if (amqp_info->msg_send_state == MESSAGE_SENDER_STATE_OPEN)
                {
                    amqp_info->amqp_state = AMQP_STATE_CONNECTED;
                    if (amqp_info->status_cb != NULL)
                    {
                        amqp_info->status_cb(PROV_DEVICE_TRANSPORT_STATUS_CONNECTED, amqp_info->retry_after_value, amqp_info->status_ctx);
                    }
                }
                break;
            case MESSAGE_RECEIVER_STATE_ERROR:
                amqp_info->transport_state = TRANSPORT_CLIENT_STATE_ERROR;
                amqp_info->amqp_state = AMQP_STATE_ERROR;
                break;
            default:
                LogError("on_message_receiver_state_changed_callback received unexpected state %d", amqp_info->msg_recv_state);

                amqp_info->amqp_state = AMQP_STATE_ERROR;
                amqp_info->transport_state = TRANSPORT_CLIENT_STATE_ERROR;
                if (amqp_info->status_cb != NULL)
                {
                    amqp_info->status_cb(PROV_DEVICE_TRANSPORT_STATUS_ERROR, amqp_info->retry_after_value, amqp_info->status_ctx);
                }
            }
        }
    }
}

static void get_error_retry_after(PROV_TRANSPORT_AMQP_INFO* amqp_info, AMQP_VALUE delivery_state)
{
    if (amqpvalue_get_type(delivery_state) == AMQP_TYPE_LIST)
    {
        uint32_t list_count = 0;
        if (amqpvalue_get_list_item_count(delivery_state, &list_count) == 0)
        {
            for (uint32_t index = 0; index < list_count; index++)
            {
                AMQP_VALUE list_item_value = amqpvalue_get_list_item(delivery_state, index);
                if (list_item_value != NULL && amqpvalue_get_type(list_item_value) == AMQP_TYPE_DESCRIBED)
                {
                    // Currently this comes back as ulong from the amqp value
                    AMQP_VALUE desc = amqpvalue_get_inplace_descriptor(list_item_value);
                    if (desc != NULL && amqpvalue_get_type(desc) == AMQP_TYPE_ULONG)
                    {
                        uint64_t desc_value;
                        if (amqpvalue_get_ulong(desc, &desc_value) == 0)
                        {
                            // If the value is greater than the max throttle time (5 min)
                            // then set it to max throttle time
                            if (desc_value > MAX_PROV_GET_THROTTLE_TIME)
                            {
                                amqp_info->retry_after_value = MAX_PROV_GET_THROTTLE_TIME;
                            }
                            else
                            {
                                amqp_info->retry_after_value = (uint32_t)desc_value;
                            }
                            break;
                        }
                    }
                }
            }
        }
    }
}

static void on_amqp_send_complete(void* user_ctx, MESSAGE_SEND_RESULT send_result, AMQP_VALUE delivery_state)
{
    if (user_ctx == NULL)
    {
        LogError("on_amqp_send_complete was invoked with a NULL context");
    }
    else
    {
        PROV_TRANSPORT_AMQP_INFO* amqp_info = (PROV_TRANSPORT_AMQP_INFO*)user_ctx;
        if (send_result != MESSAGE_SEND_OK)
        {
            (void)delivery_state;
            // To be included when a issue in the service is fixed.
            amqp_info->transport_state = TRANSPORT_CLIENT_STATE_ERROR;
            amqp_info->amqp_state = AMQP_STATE_ERROR;
        }
    }
}

static STRING_HANDLE construct_link_address(PROV_TRANSPORT_AMQP_INFO* amqp_info)
{
    STRING_HANDLE result;
    result = STRING_construct_sprintf(AMQP_ADDRESS_FMT, amqp_info->hostname, amqp_info->scope_id, amqp_info->registration_id);
    if (result == NULL)
    {
        LogError("Failure constructing event address");
    }
    return result;
}

static int get_retry_after_property(PROV_TRANSPORT_AMQP_INFO* amqp_info, MESSAGE_HANDLE message)
{
    int result;
    AMQP_VALUE app_prop = NULL;
    AMQP_VALUE prop_desc = NULL;
    uint32_t prop_count = 0;

    if (message_get_application_properties(message, &app_prop) != 0)
    {
        LogError("Failure getting application property");
        result = MU_FAILURE;
    }
    else if (app_prop == NULL)
    {
        result = 0;
    }
    else
    {
        if ((prop_desc = amqpvalue_get_inplace_described_value(app_prop)) == NULL)
        {
            LogError("Failure getting application property description");
            application_properties_destroy(app_prop);
            result = MU_FAILURE;
        }
        else if (amqpvalue_get_map_pair_count(prop_desc, &prop_count) != 0)
        {
            LogError("Failure getting application property count");
            application_properties_destroy(app_prop);
            result = MU_FAILURE;
        }
        else
        {
            bool found_value = false;
            result = MU_FAILURE;
            for (uint32_t index = 0; index < prop_count; index++)
            {
                AMQP_VALUE map_key_name = NULL;
                AMQP_VALUE map_key_value = NULL;
                const char* key_name;

                if ((amqpvalue_get_map_key_value_pair(prop_desc, index, &map_key_name, &map_key_value)) != 0)
                {
                    LogError("Failed reading the key/value pair from the uAMQP property map.");
                    result = MU_FAILURE;
                }
                else
                {
                    if ((result = amqpvalue_get_string(map_key_name, &key_name)) != 0)
                    {
                        LogError("Failed parsing the uAMQP property name.");
                        result = MU_FAILURE;
                    }
                    else if (key_name != NULL && strcmp(key_name, RETRY_AFTER_KEY_VALUE) == 0)
                    {
                        const char* key_value;
                        int32_t val_int;

                        // The AMQP type is either int or string, for more information
                        // on this see prov_transport.h:parse_retry_after_value function.
                        AMQP_TYPE type = amqpvalue_get_type(map_key_value);
                        if (type == AMQP_TYPE_INT)
                        {
                            if (amqpvalue_get_int(map_key_value, &val_int) != 0)
                            {
                                LogError("Failed parsing the uAMQP property value.");
                                result = MU_FAILURE;
                            }
                            else
                            {
                                amqp_info->retry_after_value = (uint32_t)val_int;
                                found_value = true;
                                result = 0;
                            }
                        }
                        else if (type == AMQP_TYPE_STRING)
                        {
                            if (amqpvalue_get_string(map_key_value, &key_value) != 0)
                            {
                                LogError("Failed parsing the uAMQP property value.");
                                result = MU_FAILURE;
                            }
                            else
                            {
                                amqp_info->retry_after_value = parse_retry_after_value(key_value);
                                found_value = true;
                                result = 0;
                            }
                        }
                        else
                        {
                            LogError("Failed parsing the uAMQP property value.");
                            result = MU_FAILURE;
                        }
                    }
                    amqpvalue_destroy(map_key_value);
                    amqpvalue_destroy(map_key_name);
                }
                if (found_value)
                {
                    // If the message retry-after only got through it once
                    break;
                }
            }
            application_properties_destroy(app_prop);
        }
    }
    return result;
}

static AMQP_VALUE on_message_recv_callback(const void* user_ctx, MESSAGE_HANDLE message)
{
    AMQP_VALUE result;
    if (user_ctx == NULL)
    {
        LogError("on_message_recv_callback was invoked with a NULL context");
        result = messaging_delivery_rejected("SDK error", "user context NULL");
    }
    else
    {
        MESSAGE_BODY_TYPE body_type;
        PROV_TRANSPORT_AMQP_INFO* amqp_info = (PROV_TRANSPORT_AMQP_INFO*)user_ctx;

        if (message_get_body_type(message, &body_type) != 0)
        {
            LogError("message body type has failed");
            amqp_info->transport_state = TRANSPORT_CLIENT_STATE_ERROR;
            amqp_info->amqp_state = AMQP_STATE_ERROR;
        }
        else if (body_type != MESSAGE_BODY_TYPE_DATA)
        {
            LogError("invalid message type of %d", body_type);
            amqp_info->transport_state = TRANSPORT_CLIENT_STATE_ERROR;
            amqp_info->amqp_state = AMQP_STATE_ERROR;
        }
        else
        {
            BINARY_DATA binary_data;
            if (amqp_info->payload_data != NULL)
            {
                free(amqp_info->payload_data);
                amqp_info->payload_data = NULL;
            }

            if (message_get_body_amqp_data_in_place(message, 0, &binary_data) != 0)
            {
                LogError("failure getting message from amqp body");
                amqp_info->transport_state = TRANSPORT_CLIENT_STATE_ERROR;
                amqp_info->amqp_state = AMQP_STATE_ERROR;
            }
            else if ((amqp_info->payload_data = malloc(binary_data.length + 1)) == NULL)
            {
                LogError("failure allocating payload data");
                amqp_info->transport_state = TRANSPORT_CLIENT_STATE_ERROR;
                amqp_info->amqp_state = AMQP_STATE_ERROR;
            }
            else
            {
                // Get the retry after field on failures this value will
                // be set to the default value
                (void)get_retry_after_property(amqp_info, message);

                memset(amqp_info->payload_data, 0, binary_data.length + 1);
                memcpy(amqp_info->payload_data, binary_data.bytes, binary_data.length);
                if (amqp_info->transport_state == TRANSPORT_CLIENT_STATE_REG_SENT)
                {
                    amqp_info->transport_state = TRANSPORT_CLIENT_STATE_REG_RECV;
                }
                else
                {
                    amqp_info->transport_state = TRANSPORT_CLIENT_STATE_STATUS_RECV;
                }
            }
        }
        result = messaging_delivery_accepted();
    }
    return result;
}

static int set_amqp_msg_property(AMQP_VALUE uamqp_prop_map, const char* key, const char* value)
{
    int result;
    AMQP_VALUE uamqp_key;
    AMQP_VALUE uamqp_value;
    if ((uamqp_key = amqpvalue_create_string(key)) == NULL)
    {
        LogError("Failed to create property key string.");
        result = MU_FAILURE;
    }
    else if ((uamqp_value = amqpvalue_create_string(value)) == NULL)
    {
        amqpvalue_destroy(uamqp_key);
        LogError("Failed to create property value string.");
        result = MU_FAILURE;
    }
    else
    {
        if (amqpvalue_set_map_value(uamqp_prop_map, uamqp_key, uamqp_value) != 0)
        {
            LogError("Failed to set map value.");
            result = MU_FAILURE;
        }
        else
        {
            result = 0;
        }
        amqpvalue_destroy(uamqp_key);
        amqpvalue_destroy(uamqp_value);
    }
    return result;
}

static char* construct_json_data(PROV_TRANSPORT_AMQP_INFO* amqp_info)
{
    char* result;
    bool data_success = true;
    STRING_HANDLE encoded_srk = NULL;
    STRING_HANDLE encoded_ek = NULL;
    const char* ek_value = NULL;
    const char* srk_value = NULL;

    // For TPM we need to add tpm encoded values
    if (amqp_info->hsm_type == TRANSPORT_HSM_TYPE_TPM)
    {
        if ((encoded_ek = Azure_Base64_Encode(amqp_info->ek)) == NULL)
        {
            LogError("Failure encoding ek");
            data_success = false;
        }
        else if ((encoded_srk = Azure_Base64_Encode(amqp_info->srk)) == NULL)
        {
            LogError("Failure encoding srk");
            data_success = false;
        }
        else
        {
            ek_value = STRING_c_str(encoded_ek);
            srk_value = STRING_c_str(encoded_srk);
        }
    }

    if (data_success)
    {
        result = amqp_info->json_create_cb(ek_value, srk_value, amqp_info->json_ctx);
    }
    else
    {
        result = NULL;
    }

    if (encoded_srk != NULL)
    {
        STRING_delete(encoded_srk);
    }
    if (encoded_ek != NULL)
    {
        STRING_delete(encoded_ek);
    }
    return result;
}

static int send_amqp_message(PROV_TRANSPORT_AMQP_INFO* amqp_info, const char* msg_type)
{
    int result;
    MESSAGE_HANDLE uamqp_message;
    char* json_data;

    if ((uamqp_message = message_create()) == NULL)
    {
        LogError("Failed allocating the uAMQP message.");
        result = MU_FAILURE;
    }
    else if ((json_data = construct_json_data(amqp_info)) == NULL)
    {
        LogError("Failed constructing json payload");
        message_destroy(uamqp_message);
        result = MU_FAILURE;
    }
    else
    {
        AMQP_VALUE uamqp_prop_map;

        BINARY_DATA binary_data;
        binary_data.bytes = (const unsigned char*)json_data;
        binary_data.length = strlen(json_data);

        if ((uamqp_prop_map = amqpvalue_create_map()) == NULL)
        {
            LogError("Failed to create uAMQP map for the properties.");
            result = MU_FAILURE;
        }
        else if (message_add_body_amqp_data(uamqp_message, binary_data) != 0)
        {
            LogError("Failed to adding amqp msg body.");
            amqpvalue_destroy(uamqp_prop_map);
            result = MU_FAILURE;
        }
        else if (set_amqp_msg_property(uamqp_prop_map, AMQP_OP_TYPE_PROPERTY, msg_type) != 0)
        {
            LogError("Failed to adding amqp type property to message.");
            result = MU_FAILURE;
            message_destroy(uamqp_message);
            amqpvalue_destroy(uamqp_prop_map);
        }
        else
        {
            if (amqp_info->operation_id != NULL)
            {
                if (set_amqp_msg_property(uamqp_prop_map, AMQP_OPERATION_ID, amqp_info->operation_id) != 0)
                {
                    LogError("Failed to adding operation status property to message.");
                    result = MU_FAILURE;
                    message_destroy(uamqp_message);
                }
                else
                {
                    result = 0;
                }
            }
            else
            {
                result = 0;
            }

            if (result == 0)
            {
                if (message_set_application_properties(uamqp_message, uamqp_prop_map) != 0)
                {
                    LogError("Failed to set map value.");
                    result = MU_FAILURE;
                }
                else if (messagesender_send_async(amqp_info->msg_sender, uamqp_message, on_amqp_send_complete, amqp_info, 0) == NULL)
                {
                    LogError("Failed to send message.");
                    result = MU_FAILURE;
                }
                else
                {
                    result = 0;
                }
            }
        }
        amqpvalue_destroy(uamqp_prop_map);
        message_destroy(uamqp_message);
        free(json_data);
    }
    return result;
}

static int add_link_properties(LINK_HANDLE amqp_link, const char* key, const char* value)
{
    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.");
        result = MU_FAILURE;
    }
    else
    {
        if ((device_client_type_key_name = amqpvalue_create_symbol(key)) == NULL)
        {
            LogError("Failed to create the key name for the device client type.");
            result = MU_FAILURE;
        }
        else
        {
            if ((device_client_type_value = amqpvalue_create_string(value)) == NULL)
            {
                LogError("Failed to create the key value for the device client type.");
                result = MU_FAILURE;
            }
            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);
                    result = MU_FAILURE;
                }
                else if ((result = link_set_attach_properties(amqp_link, attach_properties)) != 0)
                {
                    LogError("Unable to attach the device client type to the link properties (error code is: %d)", result);
                    result = MU_FAILURE;
                }
                else
                {
                    result = 0;
                }
                amqpvalue_destroy(device_client_type_value);
            }
            amqpvalue_destroy(device_client_type_key_name);
        }
        amqpvalue_destroy(attach_properties);
    }
    return result;
}

static int create_sender_link(PROV_TRANSPORT_AMQP_INFO* amqp_info)
{
    int result;
    AMQP_VALUE msg_source;
    AMQP_VALUE msg_target;
    STRING_HANDLE event_address;

    if ((event_address = construct_link_address(amqp_info)) == NULL)
    {
        LogError("Failure constructing amqp link address");
        result = MU_FAILURE;
    }
    else
    {
        if ((msg_source = messaging_create_source("remote_endpoint")) == NULL)
        {
            LogError("Failure retrieving messaging create source");
            result = MU_FAILURE;
        }
        else if ((msg_target = messaging_create_target(STRING_c_str(event_address))) == NULL)
        {
            LogError("Failure creating messaging target");
            amqpvalue_destroy(msg_source);
            result = MU_FAILURE;
        }
        else if ((amqp_info->sender_link = link_create(amqp_info->session, "sender_link", role_sender, msg_source, msg_target)) == NULL)
        {
            LogError("Failure creating link");
            amqpvalue_destroy(msg_source);
            amqpvalue_destroy(msg_target);
            result = MU_FAILURE;
        }
        else
        {
            if (add_link_properties(amqp_info->sender_link, AMQP_API_VERSION_KEY, PROV_API_VERSION))
            {
                LogError("Failure adding link property");
                link_destroy(amqp_info->sender_link);
                amqp_info->sender_link = NULL;
                result = MU_FAILURE;
            }
            else if (link_set_max_message_size(amqp_info->sender_link, AMQP_MAX_SENDER_MSG_SIZE) != 0)
            {
                LogError("Failure setting sender link max size");
                link_destroy(amqp_info->sender_link);
                amqp_info->sender_link = NULL;
                result = MU_FAILURE;
            }
            else if ((amqp_info->msg_sender = messagesender_create(amqp_info->sender_link, on_message_sender_state_changed_callback, amqp_info)) == NULL)
            {
                LogError("Failure creating message sender");
                link_destroy(amqp_info->sender_link);
                amqp_info->sender_link = NULL;
                result = MU_FAILURE;
            }
            else if (messagesender_open(amqp_info->msg_sender) != 0)
            {
                LogError("Failure opening message sender");
                messagesender_destroy(amqp_info->msg_sender);
                amqp_info->msg_sender = NULL;
                link_destroy(amqp_info->sender_link);
                amqp_info->sender_link = NULL;
                result = MU_FAILURE;
            }
            else
            {
                result = 0;
            }
            amqpvalue_destroy(msg_target);
            amqpvalue_destroy(msg_source);
        }
        STRING_delete(event_address);
    }
    return result;
}

static int create_receiver_link(PROV_TRANSPORT_AMQP_INFO* amqp_info)
{
    int result;
    AMQP_VALUE msg_source;
    AMQP_VALUE msg_target;
    STRING_HANDLE event_address;

    if ((event_address = construct_link_address(amqp_info)) == NULL)
    {
        LogError("Failure constructing amqp link address");
        result = MU_FAILURE;
    }
    else
    {
        if ((msg_source = messaging_create_source(STRING_c_str(event_address))) == NULL)
        {
            LogError("Failure retrieving messaging create source");
            result = MU_FAILURE;
        }
        else if ((msg_target = messaging_create_target("local_endpoint")) == NULL)
        {
            LogError("Failure creating messaging target");
            amqpvalue_destroy(msg_source);
            result = MU_FAILURE;
        }
        else if ((amqp_info->receiver_link = link_create(amqp_info->session, "recv_link", role_receiver, msg_source, msg_target)) == NULL)
        {
            LogError("Failure creating link");
            amqpvalue_destroy(msg_source);
            amqpvalue_destroy(msg_target);
            result = MU_FAILURE;
        }
        else
        {
            link_set_rcv_settle_mode(amqp_info->receiver_link, receiver_settle_mode_first);
            if (add_link_properties(amqp_info->receiver_link, AMQP_API_VERSION_KEY, PROV_API_VERSION))
            {
                LogError("Failure adding link property");
                link_destroy(amqp_info->sender_link);
                amqp_info->sender_link = NULL;
                result = MU_FAILURE;
            }
            else if (link_set_max_message_size(amqp_info->receiver_link, AMQP_MAX_RECV_MSG_SIZE) != 0)
            {
                LogError("Failure setting max message size");
                link_destroy(amqp_info->receiver_link);
                amqp_info->receiver_link = NULL;
                result = MU_FAILURE;
            }
            else if ((amqp_info->msg_receiver = messagereceiver_create(amqp_info->receiver_link, on_message_receiver_state_changed_callback, (void*)amqp_info)) == NULL)
            {
                LogError("Failure creating message receiver");
                link_destroy(amqp_info->receiver_link);
                amqp_info->receiver_link = NULL;
                result = MU_FAILURE;
            }
            else if (messagereceiver_open(amqp_info->msg_receiver, on_message_recv_callback, (void*)amqp_info) != 0)
            {
                LogError("Failure opening message receiver");
                link_destroy(amqp_info->receiver_link);
                amqp_info->receiver_link = NULL;
                result = MU_FAILURE;
            }
            else
            {
                result = 0;
            }

            amqpvalue_destroy(msg_source);
            amqpvalue_destroy(msg_target);
        }
        STRING_delete(event_address);
    }
    return result;
}

static int create_transport_io_object(PROV_TRANSPORT_AMQP_INFO* amqp_info)
{
    int result;
    if (amqp_info->underlying_io == NULL)
    {
        HTTP_PROXY_OPTIONS* transport_proxy;
        SASL_MECHANISM_HANDLE* sasl_mechanism = NULL;
        PROV_TRANSPORT_IO_INFO* trans_info;

        // Set the proxy options if necessary
        if (amqp_info->proxy_option.host_address != NULL)
        {
            transport_proxy = &amqp_info->proxy_option;
        }
        else
        {
            transport_proxy = NULL;
        }

        if (amqp_info->hsm_type == TRANSPORT_HSM_TYPE_TPM || amqp_info->hsm_type == TRANSPORT_HSM_TYPE_SYMM_KEY)
        {
            sasl_mechanism = &amqp_info->sasl_handler;
        }

        if ((trans_info = amqp_info->transport_io_cb(amqp_info->hostname, sasl_mechanism, transport_proxy)) == NULL)
        {
            LogError("Failure calling transport_io callback");
            result = MU_FAILURE;
        }
        else
        {
            if (trans_info->sasl_handle == NULL)
            {
                amqp_info->underlying_io = trans_info->transport_handle;
            }
            else
            {
                amqp_info->transport_io = trans_info->transport_handle;
                amqp_info->underlying_io = trans_info->sasl_handle;
            }
            free(trans_info);
            result = 0;
        }
    }
    else
    {
        result = 0;
    }
    return result;
}

static int create_amqp_connection(PROV_TRANSPORT_AMQP_INFO* amqp_info)
{
    int result;

    if (create_transport_io_object(amqp_info) != 0)
    {
        LogError("Failure setting transport object");
        result = MU_FAILURE;
    }
    else
    {
        if (amqp_info->hsm_type == TRANSPORT_HSM_TYPE_X509)
        {
            if (amqp_info->x509_cert != NULL && amqp_info->private_key != NULL)
            {
                if (xio_setoption(amqp_info->underlying_io, OPTION_X509_ECC_CERT, amqp_info->x509_cert) != 0)
                {
                    LogError("Failure setting x509 cert on xio");
                    result = MU_FAILURE;
                    xio_destroy(amqp_info->underlying_io);
                    amqp_info->underlying_io = NULL;
                }
                else if (xio_setoption(amqp_info->underlying_io, OPTION_X509_ECC_KEY, amqp_info->private_key) != 0)
                {
                    LogError("Failure setting x509 key on xio");
                    if (amqp_info->error_cb != NULL)
                    {
                        amqp_info->error_cb(PROV_DEVICE_ERROR_KEY_FAIL, amqp_info->error_ctx);
                    }
                    result = MU_FAILURE;
                    xio_destroy(amqp_info->underlying_io);
                    amqp_info->underlying_io = NULL;
                }
                else
                {
                    result = 0;
                }
            }
            else
            {
                LogError("x509 certificate is NULL");
                result = MU_FAILURE;
                xio_destroy(amqp_info->underlying_io);
                amqp_info->underlying_io = NULL;
            }
        }
        else
        {
            result = 0;
        }

        if (result == 0)
        {
            if (amqp_info->certificate != NULL && xio_setoption(amqp_info->underlying_io, OPTION_TRUSTED_CERT, amqp_info->certificate) != 0)
            {
                LogError("Failure setting trusted certs");
                result = MU_FAILURE;
                xio_destroy(amqp_info->underlying_io);
                amqp_info->underlying_io = NULL;
            }
            else if ((amqp_info->connection = connection_create(amqp_info->underlying_io, amqp_info->hostname, "prov_connection", NULL, NULL)) == NULL)
            {
                LogError("failed creating amqp connection");
                xio_destroy(amqp_info->underlying_io);
                amqp_info->underlying_io = NULL;
                result = MU_FAILURE;
            }
            else
            {
                connection_set_trace(amqp_info->connection, amqp_info->log_trace);
                result = 0;
            }
        }
    }
    return result;
}

static char* construct_username(PROV_TRANSPORT_AMQP_INFO* amqp_info)
{
    char* result;
    size_t length;

    length = strlen(AMQP_SASL_USERNAME_FMT) + strlen(amqp_info->scope_id) + strlen(amqp_info->registration_id);
    if ((result = malloc(length + 1)) == NULL)
    {
        LogError("Failure allocating username");
    }
    else if (sprintf(result, AMQP_SASL_USERNAME_FMT, amqp_info->scope_id, amqp_info->registration_id) <= 0)
    {
        LogError("Failure creating mqtt username");
        free(result);
        result = NULL;
    }
    return result;
}


static int create_sasl_handler(PROV_TRANSPORT_AMQP_INFO* amqp_info)
{
    int result;
    const SASL_MECHANISM_INTERFACE_DESCRIPTION* sasl_interface;

    if (amqp_info->hsm_type == TRANSPORT_HSM_TYPE_TPM)
    {
        SASL_TPM_CONFIG_INFO sasl_config;
        sasl_config.registration_id = amqp_info->registration_id;
        sasl_config.scope_id = amqp_info->scope_id;
        sasl_config.storage_root_key = amqp_info->srk;
        sasl_config.endorsement_key = amqp_info->ek;
        sasl_config.hostname = amqp_info->hostname;
        sasl_config.challenge_cb = on_sasl_tpm_challenge_cb;
        sasl_config.user_ctx = amqp_info;

        // Create TPM SASL handler
        if ((sasl_interface = prov_sasltpm_get_interface()) == NULL)
        {
            LogError("prov_sasltpm_get_interface was NULL");
            result = MU_FAILURE;
        }
        else if ((amqp_info->sasl_handler = saslmechanism_create(sasl_interface, &sasl_config)) == NULL)
        {
            LogError("failed creating tpm sasl mechanism");
            result = MU_FAILURE;
        }
        else
        {
            result = 0;
        }
    }
    else if (amqp_info->hsm_type == TRANSPORT_HSM_TYPE_SYMM_KEY)
    {
        SASL_PLAIN_CONFIG sasl_config;
        char* sasl_username;
        char* sas_token;

        if ((sasl_username = construct_username(amqp_info)) == NULL)
        {
            LogError("failed creating symmetical sasl username");
            result = MU_FAILURE;
        }
        else if ((sas_token = amqp_info->challenge_cb(NULL, 0, KEY_NAME_VALUE, amqp_info->challenge_ctx)) == NULL)
        {
            LogError("failed creating symmetical sasl token");
            result = MU_FAILURE;
            free(sasl_username);
        }
        else
        {
            sasl_config.authcid = sasl_username;
            sasl_config.passwd = sas_token;
            sasl_config.authzid = NULL;

            if ((sasl_interface = saslplain_get_interface()) == NULL)
            {
                LogError("prov_saslplain_get_interface was NULL");
                result = MU_FAILURE;
            }
            else if ((amqp_info->sasl_handler = saslmechanism_create(sasl_interface, &sasl_config)) == NULL)
            {
                LogError("failed creating plain sasl mechanism");
                result = MU_FAILURE;
            }
            else
            {
                result = 0;
            }
            free(sasl_username);
            free(sas_token);
        }
    }
    else
    {
        result = 0;
    }
    return result;
}

static int create_connection(PROV_TRANSPORT_AMQP_INFO* amqp_info)
{
    int result;

    if (create_sasl_handler(amqp_info) != 0)
    {
        LogError("failed creating amqp Sasl handler");
        result = MU_FAILURE;
    }
    else if (create_amqp_connection(amqp_info) != 0)
    {
        LogError("failed creating amqp connection");
        saslmechanism_destroy(amqp_info->sasl_handler);
        amqp_info->sasl_handler = NULL;
        result = MU_FAILURE;
    }
    else
    {
        if ((amqp_info->session = session_create(amqp_info->connection, NULL, NULL)) == NULL)
        {
            LogError("failed creating amqp session");
            saslmechanism_destroy(amqp_info->sasl_handler);
            amqp_info->sasl_handler = NULL;
            result = MU_FAILURE;
        }
        else
        {
            (void)session_set_incoming_window(amqp_info->session, 2147483647);
            (void)session_set_outgoing_window(amqp_info->session, 65536);

            if (create_receiver_link(amqp_info) != 0)
            {
                LogError("failure creating amqp receiver link");
                saslmechanism_destroy(amqp_info->sasl_handler);
                amqp_info->sasl_handler = NULL;
                session_destroy(amqp_info->session);
                amqp_info->session = NULL;
                result = MU_FAILURE;
            }
            else if (create_sender_link(amqp_info) != 0)
            {
                LogError("failure creating amqp sender link");
                saslmechanism_destroy(amqp_info->sasl_handler);
                amqp_info->sasl_handler = NULL;
                session_destroy(amqp_info->session);
                amqp_info->session = NULL;
                result = MU_FAILURE;
            }
            else
            {
                result = 0;
            }
        }
    }
    return result;
}

static void free_json_parse_info(PROV_JSON_INFO* parse_info)
{
    switch (parse_info->prov_status)
    {
        case PROV_DEVICE_TRANSPORT_STATUS_UNASSIGNED:
            BUFFER_delete(parse_info->authorization_key);
            free(parse_info->key_name);
            break;
        case PROV_DEVICE_TRANSPORT_STATUS_ASSIGNED:
            BUFFER_delete(parse_info->authorization_key);
            free(parse_info->iothub_uri);
            free(parse_info->device_id);
            break;
        case PROV_DEVICE_TRANSPORT_STATUS_ASSIGNING:
            free(parse_info->operation_id);
            break;
        default:
            break;
    }
    free(parse_info);
}

void cleanup_amqp_data(PROV_TRANSPORT_AMQP_INFO* amqp_info)
{
    free(amqp_info->hostname);
    free(amqp_info->registration_id);
    free(amqp_info->operation_id);
    free(amqp_info->api_version);
    free(amqp_info->scope_id);
    free(amqp_info->certificate);
    free((char*)amqp_info->proxy_option.host_address);
    free((char*)amqp_info->proxy_option.username);
    free((char*)amqp_info->proxy_option.password);
    free(amqp_info->x509_cert);
    free(amqp_info->private_key);
    free(amqp_info->sas_token);
    free(amqp_info->payload_data);
    if (amqp_info->transport_io != NULL)
    {
        xio_destroy(amqp_info->transport_io);
    }
    if (amqp_info->underlying_io != NULL)
    {
        xio_destroy(amqp_info->underlying_io);
    }
    free(amqp_info);
}

PROV_DEVICE_TRANSPORT_HANDLE prov_transport_common_amqp_create(const char* uri, TRANSPORT_HSM_TYPE type, const char* scope_id, const char* api_version, PROV_AMQP_TRANSPORT_IO transport_io, PROV_TRANSPORT_ERROR_CALLBACK error_cb, void* error_ctx)
{
    PROV_TRANSPORT_AMQP_INFO* result;
    if (uri == NULL || scope_id == NULL || api_version == NULL || transport_io == NULL)
    {
        LogError("Invalid parameter specified uri: %p, scope_id: %p, api_version: %p, transport_io: %p", uri, scope_id, api_version, transport_io);
        result = NULL;
    }
    else
    {
        result = malloc(sizeof(PROV_TRANSPORT_AMQP_INFO));
        if (result == NULL)
        {
            LogError("Unable to allocate PROV_TRANSPORT_AMQP_INFO");
        }
        else
        {
            memset(result, 0, sizeof(PROV_TRANSPORT_AMQP_INFO));
            if (mallocAndStrcpy_s(&result->hostname, uri) != 0)
            {
                LogError("Failure allocating hostname");
                free(result);
                result = NULL;
            }
            else if (mallocAndStrcpy_s(&result->api_version, api_version) != 0)
            {
                LogError("Failure allocating api_version");
                cleanup_amqp_data(result);
                result = NULL;
            }
            else if (mallocAndStrcpy_s(&result->scope_id, scope_id) != 0)
            {
                LogError("Failure allocating scope id");
                cleanup_amqp_data(result);
                result = NULL;
            }
            else
            {
                result->transport_io_cb = transport_io;
                result->hsm_type = type;
                result->error_cb = error_cb;
                result->error_ctx = error_ctx;
                result->retry_after_value = PROV_GET_THROTTLE_TIME;
            }
        }
    }
    return result;
}

void prov_transport_common_amqp_destroy(PROV_DEVICE_TRANSPORT_HANDLE handle)
{
    if (handle != NULL)
    {
        PROV_TRANSPORT_AMQP_INFO* amqp_info = (PROV_TRANSPORT_AMQP_INFO*)handle;
        cleanup_amqp_data(amqp_info);
    }
}

int prov_transport_common_amqp_open(PROV_DEVICE_TRANSPORT_HANDLE handle, const char* registration_id, BUFFER_HANDLE ek, BUFFER_HANDLE srk, PROV_DEVICE_TRANSPORT_REGISTER_CALLBACK data_callback, void* user_ctx, PROV_DEVICE_TRANSPORT_STATUS_CALLBACK status_cb, void* status_ctx, PROV_TRANSPORT_CHALLENGE_CALLBACK reg_challenge_cb, void* challenge_ctx)
{
    int result;
    PROV_TRANSPORT_AMQP_INFO* amqp_info = (PROV_TRANSPORT_AMQP_INFO*)handle;
    if (amqp_info == NULL || data_callback == NULL || status_cb == NULL || registration_id == NULL)
    {
        LogError("Invalid parameter specified handle: %p, data_callback: %p, status_cb: %p, registration_id: %p", handle, data_callback, status_cb, registration_id);
        result = MU_FAILURE;
    }
    else if ((amqp_info->hsm_type == TRANSPORT_HSM_TYPE_TPM || amqp_info->hsm_type == TRANSPORT_HSM_TYPE_SYMM_KEY) && reg_challenge_cb == NULL)
    {
        LogError("registration challenge callback must be set");
        result = MU_FAILURE;
    }
    else if ((amqp_info->hsm_type == TRANSPORT_HSM_TYPE_TPM) && (ek == NULL || srk == NULL) )
    {
        LogError("Invalid parameter specified ek: %p, srk: %p", ek, srk);
        result = MU_FAILURE;
    }
    else if (ek != NULL && (amqp_info->ek = BUFFER_clone(ek)) == NULL)
    {
        LogError("Unable to allocate endorsement key");
        result = MU_FAILURE;
    }
    else if (srk != NULL && (amqp_info->srk = BUFFER_clone(srk)) == NULL)
    {
        LogError("Unable to allocate storage root key");
        BUFFER_delete(amqp_info->ek);
        amqp_info->ek = NULL;
        result = MU_FAILURE;
    }
    else if (mallocAndStrcpy_s(&amqp_info->registration_id, registration_id) != 0)
    {
        LogError("failure constructing registration Id");
        BUFFER_delete(amqp_info->ek);
        amqp_info->ek = NULL;
        BUFFER_delete(amqp_info->srk);
        amqp_info->srk = NULL;
        result = MU_FAILURE;
    }
    else
    {
        amqp_info->register_data_cb = data_callback;
        amqp_info->user_ctx = user_ctx;
        amqp_info->status_cb = status_cb;
        amqp_info->status_ctx = status_ctx;
        amqp_info->challenge_cb = reg_challenge_cb;
        amqp_info->challenge_ctx = challenge_ctx;
        amqp_info->amqp_state = AMQP_STATE_DISCONNECTED;
        result = 0;
    }
    return result;
}

int prov_transport_common_amqp_close(PROV_DEVICE_TRANSPORT_HANDLE handle)
{
    int result;
    if (handle == NULL)
    {
        LogError("Invalid parameter specified handle: %p", handle);
        result = MU_FAILURE;
    }
    else
    {
        PROV_TRANSPORT_AMQP_INFO* amqp_info = (PROV_TRANSPORT_AMQP_INFO*)handle;
        BUFFER_delete(amqp_info->ek);
        amqp_info->ek = NULL;
        BUFFER_delete(amqp_info->srk);
        amqp_info->srk = NULL;

        free(amqp_info->registration_id);
        amqp_info->registration_id = NULL;

        if (amqp_info->msg_sender != NULL)
        {
            messagesender_close(amqp_info->msg_sender);
        }
        if (amqp_info->msg_receiver != NULL)
        {
            messagereceiver_close(amqp_info->msg_receiver);
        }
        if (amqp_info->msg_sender != NULL)
        {
            messagesender_destroy(amqp_info->msg_sender);
            amqp_info->msg_sender = NULL;
        }
        if (amqp_info->msg_receiver != NULL)
        {
            messagereceiver_destroy(amqp_info->msg_receiver);
            amqp_info->msg_receiver = NULL;
        }

        // Link
        if (amqp_info->receiver_link != NULL)
        {
            link_destroy(amqp_info->receiver_link);
            amqp_info->receiver_link = NULL;
        }
        if (amqp_info->sender_link != NULL)
        {
            link_destroy(amqp_info->sender_link);
            amqp_info->sender_link = NULL;
        }
        if (amqp_info->sasl_handler != NULL)
        {
            saslmechanism_destroy(amqp_info->sasl_handler);
            amqp_info->sasl_handler = NULL;
        }
        if (amqp_info->session != NULL)
        {
            session_destroy(amqp_info->session);
            amqp_info->session = NULL;
        }
        if (amqp_info->connection != NULL)
        {
            connection_destroy(amqp_info->connection);
            amqp_info->connection = NULL;
        }
        xio_destroy(amqp_info->transport_io);
        amqp_info->transport_io = NULL;
        xio_destroy(amqp_info->underlying_io);
        amqp_info->underlying_io = NULL;

        amqp_info->amqp_state = AMQP_STATE_IDLE;
        result = 0;
    }
    return result;
}

int prov_transport_common_amqp_register_device(PROV_DEVICE_TRANSPORT_HANDLE handle, PROV_TRANSPORT_JSON_PARSE json_parse_cb, PROV_TRANSPORT_CREATE_JSON_PAYLOAD json_create_cb, void* json_ctx)
{
    int result;
    PROV_TRANSPORT_AMQP_INFO* amqp_info = (PROV_TRANSPORT_AMQP_INFO*)handle;
    if (amqp_info == NULL || json_parse_cb == NULL || json_create_cb == NULL)
    {
        LogError("Invalid parameter specified handle: %p, json_parse_cb: %p", handle, json_parse_cb);
        result = MU_FAILURE;
    }
    else if (amqp_info->transport_state == TRANSPORT_CLIENT_STATE_REG_SEND || amqp_info->operation_id != NULL)
    {
        LogError("Failure: device is currently in the registration process");
        result = MU_FAILURE;
    }
    else if (amqp_info->transport_state == TRANSPORT_CLIENT_STATE_ERROR)
    {
        LogError("Provisioning is in an error state, close the connection and try again.");
        result = MU_FAILURE;
    }
    else
    {
        amqp_info->transport_state = TRANSPORT_CLIENT_STATE_REG_SEND;
        amqp_info->json_parse_cb = json_parse_cb;
        amqp_info->json_create_cb = json_create_cb;
        amqp_info->json_ctx = json_ctx;

        result = 0;
    }
    return result;
}

int prov_transport_common_amqp_get_operation_status(PROV_DEVICE_TRANSPORT_HANDLE handle)
{
    int result;
    if (handle == NULL)
    {
        LogError("Invalid parameter specified handle: %p", handle);
        result = MU_FAILURE;
    }
    else
    {
        PROV_TRANSPORT_AMQP_INFO* amqp_info = (PROV_TRANSPORT_AMQP_INFO*)handle;
        if (amqp_info->operation_id == NULL)
        {
            LogError("operation_id was not previously set in the challenge method");
            result = MU_FAILURE;
        }
        else if (amqp_info->transport_state == TRANSPORT_CLIENT_STATE_ERROR)
        {
            LogError("provisioning is in an error state, close the connection and try again.");
            result = MU_FAILURE;
        }
        else
        {
            amqp_info->transport_state = TRANSPORT_CLIENT_STATE_STATUS_SEND;
            result = 0;
        }
    }
    return result;
}

void prov_transport_common_amqp_dowork(PROV_DEVICE_TRANSPORT_HANDLE handle)
{
    if (handle != NULL)
    {
        PROV_TRANSPORT_AMQP_INFO* amqp_info = (PROV_TRANSPORT_AMQP_INFO*)handle;
        if (amqp_info->amqp_state == AMQP_STATE_DISCONNECTED)
        {
            if (create_connection(amqp_info) != 0)
            {
                LogError("unable to create amqp connection");
                amqp_info->amqp_state = AMQP_STATE_ERROR;
                amqp_info->transport_state = TRANSPORT_CLIENT_STATE_ERROR;
            }
            else
            {
                amqp_info->amqp_state = AMQP_STATE_CONNECTING;
            }
        }
        else if (amqp_info->amqp_state != AMQP_STATE_IDLE)
        {
            connection_dowork(amqp_info->connection);
            if (amqp_info->amqp_state == AMQP_STATE_CONNECTED || amqp_info->amqp_state == AMQP_STATE_ERROR)
            {
                switch (amqp_info->transport_state)
                {
                case TRANSPORT_CLIENT_STATE_REG_SEND:
                    if (send_amqp_message(amqp_info, AMQP_REGISTER_ME) != 0)
                    {
                        amqp_info->transport_state = TRANSPORT_CLIENT_STATE_ERROR;
                        amqp_info->amqp_state = AMQP_STATE_ERROR;
                    }
                    else
                    {
                        amqp_info->transport_state = TRANSPORT_CLIENT_STATE_REG_SENT;
                    }
                    break;

                case TRANSPORT_CLIENT_STATE_REG_RECV:
                case TRANSPORT_CLIENT_STATE_STATUS_RECV:
                {
                    PROV_JSON_INFO* parse_info = amqp_info->json_parse_cb(amqp_info->payload_data, amqp_info->json_ctx);
                    if (parse_info == NULL)
                    {
                        LogError("Unable to process registration reply.");
                        amqp_info->transport_state = TRANSPORT_CLIENT_STATE_ERROR;
                        amqp_info->amqp_state = AMQP_STATE_ERROR;
                    }
                    else
                    {
                        amqp_info->transport_state = TRANSPORT_CLIENT_STATE_IDLE;
                        switch (parse_info->prov_status)
                        {
                            case PROV_DEVICE_TRANSPORT_STATUS_UNASSIGNED:
                            case PROV_DEVICE_TRANSPORT_STATUS_ASSIGNING:
                                if (parse_info->operation_id == NULL)
                                {
                                    LogError("Failure operation Id invalid");
                                    amqp_info->transport_state = TRANSPORT_CLIENT_STATE_ERROR;
                                    amqp_info->amqp_state = AMQP_STATE_ERROR;
                                }
                                else if (amqp_info->operation_id == NULL && mallocAndStrcpy_s(&amqp_info->operation_id, parse_info->operation_id) != 0)
                                {
                                    LogError("Failure copying operation Id");
                                    amqp_info->transport_state = TRANSPORT_CLIENT_STATE_ERROR;
                                    amqp_info->amqp_state = AMQP_STATE_ERROR;
                                }
                                else
                                {
                                    if (amqp_info->status_cb != NULL)
                                    {
                                        amqp_info->status_cb(parse_info->prov_status, amqp_info->retry_after_value, amqp_info->status_ctx);
                                    }
                                }
                                break;

                            case PROV_DEVICE_TRANSPORT_STATUS_ASSIGNED:
                                amqp_info->register_data_cb(PROV_DEVICE_TRANSPORT_RESULT_OK, parse_info->authorization_key, parse_info->iothub_uri, parse_info->device_id, amqp_info->user_ctx);
                                amqp_info->transport_state = TRANSPORT_CLIENT_STATE_IDLE;
                                break;
                            case PROV_DEVICE_TRANSPORT_STATUS_ERROR:
                            default:
                                LogError("Unable to process message reply.");
                                amqp_info->transport_state = TRANSPORT_CLIENT_STATE_ERROR;
                                amqp_info->amqp_state = AMQP_STATE_ERROR;
                                break;

                        }
                        free_json_parse_info(parse_info);
                    }
                    break;
                }

                case TRANSPORT_CLIENT_STATE_STATUS_SEND:
                    if (send_amqp_message(amqp_info, AMQP_OPERATION_STATUS) != 0)
                    {
                        amqp_info->transport_state = TRANSPORT_CLIENT_STATE_ERROR;
                        amqp_info->amqp_state = AMQP_STATE_ERROR;
                    }
                    else
                    {
                        amqp_info->transport_state = TRANSPORT_CLIENT_STATE_STATUS_SENT;
                    }
                    break;

                case TRANSPORT_CLIENT_STATE_ERROR:
                    amqp_info->register_data_cb(PROV_DEVICE_TRANSPORT_RESULT_ERROR, NULL, NULL, NULL, amqp_info->user_ctx);
                    amqp_info->transport_state = TRANSPORT_CLIENT_STATE_IDLE;
                    amqp_info->amqp_state = AMQP_STATE_IDLE;
                    break;
                case TRANSPORT_CLIENT_STATE_REG_SENT:
                case TRANSPORT_CLIENT_STATE_STATUS_SENT:
                    // Check timout
                    break;
                case TRANSPORT_CLIENT_STATE_IDLE:
                default:
                    break;
                }
            }
        }
    }
}

int prov_transport_common_amqp_set_trace(PROV_DEVICE_TRANSPORT_HANDLE handle, bool trace_on)
{
    int result;
    if (handle == NULL)
    {
        LogError("Invalid parameter specified handle: %p", handle);
        result = MU_FAILURE;
    }
    else
    {
        PROV_TRANSPORT_AMQP_INFO* amqp_info = (PROV_TRANSPORT_AMQP_INFO*)handle;
        amqp_info->log_trace = trace_on;

        if (amqp_info->connection != NULL)
        {
            connection_set_trace(amqp_info->connection, amqp_info->log_trace);
        }

        result = 0;
    }
    return result;
}

int prov_transport_common_amqp_x509_cert(PROV_DEVICE_TRANSPORT_HANDLE handle, const char* certificate, const char* private_key)
{
    int result;
    if (handle == NULL || certificate == NULL)
    {
        LogError("Invalid parameter specified handle: %p, certificate: %p", handle, certificate);
        result = MU_FAILURE;
    }
    else
    {
        PROV_TRANSPORT_AMQP_INFO* amqp_info = (PROV_TRANSPORT_AMQP_INFO*)handle;

        if (amqp_info->x509_cert != NULL)
        {
            free(amqp_info->x509_cert);
            amqp_info->x509_cert = NULL;
        }
        if (amqp_info->private_key != NULL)
        {
            free(amqp_info->private_key);
            amqp_info->private_key = NULL;
        }

        if (mallocAndStrcpy_s(&amqp_info->x509_cert, certificate) != 0)
        {
            result = MU_FAILURE;
            LogError("failure allocating certificate");
        }
        else if (mallocAndStrcpy_s(&amqp_info->private_key, private_key) != 0)
        {
            LogError("failure allocating certificate");
            free(amqp_info->x509_cert);
            amqp_info->x509_cert = NULL;
            result = MU_FAILURE;
        }
        else
        {
            result = 0;
        }
    }
    return result;
}

int prov_transport_common_amqp_set_trusted_cert(PROV_DEVICE_TRANSPORT_HANDLE handle, const char* certificate)
{
    int result;
    if (handle == NULL || certificate == NULL)
    {
        LogError("Invalid parameter specified handle: %p, certificate: %p", handle, certificate);
        result = MU_FAILURE;
    }
    else
    {
        PROV_TRANSPORT_AMQP_INFO* amqp_info = (PROV_TRANSPORT_AMQP_INFO*)handle;

        if (amqp_info->certificate != NULL)
        {
            free(amqp_info->certificate);
            amqp_info->certificate = NULL;
        }

        if (mallocAndStrcpy_s(&amqp_info->certificate, certificate) != 0)
        {
            result = MU_FAILURE;
            LogError("failure allocating certificate");
        }
        else
        {
            result = 0;
        }
    }
    return result;
}

int prov_transport_common_amqp_set_proxy(PROV_DEVICE_TRANSPORT_HANDLE handle, const HTTP_PROXY_OPTIONS* proxy_options)
{
    int result;
    if (handle == NULL || proxy_options == NULL)
    {
        LogError("Invalid parameter specified handle: %p, proxy_options: %p", handle, proxy_options);
        result = MU_FAILURE;
    }
    else
    {
        PROV_TRANSPORT_AMQP_INFO* amqp_info = (PROV_TRANSPORT_AMQP_INFO*)handle;
        if (proxy_options->host_address == NULL)
        {
            LogError("NULL host_address in proxy options");
            result = MU_FAILURE;
        }
        else if (((proxy_options->username == NULL) || (proxy_options->password == NULL)) &&
            (proxy_options->username != proxy_options->password))
        {
            LogError("Only one of username and password for proxy settings was NULL");
            result = MU_FAILURE;
        }
        else
        {
            if (amqp_info->proxy_option.host_address != NULL)
            {
                free((char*)amqp_info->proxy_option.host_address);
                amqp_info->proxy_option.host_address = NULL;
            }
            if (amqp_info->proxy_option.username != NULL)
            {
                free((char*)amqp_info->proxy_option.username);
                amqp_info->proxy_option.username = NULL;
            }
            if (amqp_info->proxy_option.password != NULL)
            {
                free((char*)amqp_info->proxy_option.password);
                amqp_info->proxy_option.password = NULL;
            }

            amqp_info->proxy_option.port = proxy_options->port;
            if (mallocAndStrcpy_s((char**)&amqp_info->proxy_option.host_address, proxy_options->host_address) != 0)
            {
                LogError("Failure setting proxy_host name");
                result = MU_FAILURE;
            }
            else if (proxy_options->username != NULL && mallocAndStrcpy_s((char**)&amqp_info->proxy_option.username, proxy_options->username) != 0)
            {
                LogError("Failure setting proxy username");
                free((char*)amqp_info->proxy_option.host_address);
                amqp_info->proxy_option.host_address = NULL;
                result = MU_FAILURE;
            }
            else if (proxy_options->password != NULL && mallocAndStrcpy_s((char**)&amqp_info->proxy_option.password, proxy_options->password) != 0)
            {
                LogError("Failure setting proxy password");
                free((char*)amqp_info->proxy_option.host_address);
                amqp_info->proxy_option.host_address = NULL;
                free((char*)amqp_info->proxy_option.username);
                amqp_info->proxy_option.username = NULL;
                result = MU_FAILURE;
            }
            else
            {
                result = 0;
            }
        }
    }
    return result;
}

int prov_transport_common_amqp_set_option(PROV_DEVICE_TRANSPORT_HANDLE handle, const char* option, const void* value)
{
    int result;
    if (handle == NULL || option == NULL)
    {
        LogError("Invalid parameter specified handle: %p, option: %p", handle, option);
        result = MU_FAILURE;
    }
    else
    {
        PROV_TRANSPORT_AMQP_INFO* amqp_info = (PROV_TRANSPORT_AMQP_INFO*)handle;
        if (amqp_info->underlying_io == NULL && create_transport_io_object(amqp_info) != 0)
        {
            LogError("Failure creating transport io object");
            result = MU_FAILURE;
        }
        else
        {
            result = xio_setoption(amqp_info->underlying_io, option, value);
        }
    }
    return result;
}
