/* azureClientSample.c - Azure IoT Hub client sample */

// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT

/* Copyright (c) 2021 Wind River Systems, Inc. */

/*
DESCRIPTION
This sample utilizes MQTT implementation provided by Eclipse Mosquitto and
shows how to use "Azure SDK for Embedded C" to connect to Azure IoT Hub
as a client.

This sample implements:
1. sending telemetry messages to Azure IoT Hub
2. receiving Cloud-to-Device messages from Azure IoT Hub
3. responding to device Method-Calls from Azure IoT Hub
4. Both Shared Access Signature and X509 certificate authentication
   are supproted
5. Device Twin: one desired attribute "device_count" is designed for the sample
   to show how device twin works.
6. Communicate with provisioned devices
*/

/* includes */

#include <azure/az_core.h>
#include <azure/az_iot.h>
#include <mosquitto.h>
#include <stdio.h>

#include <openssl/bio.h>
#include <openssl/buffer.h>
#include <openssl/evp.h>
#include <openssl/hmac.h>

/* defines */

/*
 * This definition has to be updated according to the name of
 * your Azure IoT Hub
 */
#define IOT_HUB_NAME "VxWorksIoT"

/*
 * Define X509_CERTIFICATE to use user-generated Root Certificate.
 * If you only want to use SAS authentication, undefine or comment out this macro.
 */
/* #define X509_CERTIFICATE */

/*
 * Connect to provisioned devices.
 * If you want to connect to devices which is not provisioned,
 * undefine or comment out this macro.
 */
/* #define USE_PROVISIONING */

#ifdef X509_CERTIFICATE
#ifdef USE_PROVISIONING
#define DEVICE_ID "VxNodeProvisioningX509"
#else /* USE_PROVISIONING */
#define DEVICE_ID "VxNodeX509"
#endif /* USE_PROVISIONING */
#else /* X509_CERTIFICATE */
#ifdef USE_PROVISIONING
#define DEVICE_ID "VxNodeProvisioning"
#else /* USE_PROVISIONING */
#define DEVICE_ID "VxDev1"
#endif /* USE_PROVISIONING */
#endif /* X509_CERTIFICATE */

/*
 * Password is only needed by SAS enrollment. It is generated by Primary Key.
 * For X509 enrollment, it is NULL.
 * It is also can be generated by a Azure CLI:
 * az iot hub generate-sas-token -d [Device ID] -n [IoT Hub Name]
 */
#ifdef X509_CERTIFICATE
static char* pwd = NULL;
#else
static char pwd[256] = { 0 };
#endif

#define PORT 8883
/*
 * Host name. It can be found via:
 * Azure IoT Hub Service -> Overview -> Hostname
 */
#define HOST IOT_HUB_NAME ".azure-devices.net"

/*
 * Server certificate. It can be found in:
 * https://github.com/Azure/azure-iot-sdk-c/blob/master/certs/certs.c
 *
 * Copy all the sections beginning with "-----BEGIN CERTIFICATE-----" and
 * ending with "-----END CERTIFICATE-----" into a file with an extension "pem"
 * like "certs.pem". Remove all the beginning and ending double quotes and
 * "\r\n".
 * -----BEGIN CERTIFICATE-----
 * MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ
 * RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD
 * ..............................
 * ..............................
 * Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsaY71k5h+3zvDyny67G7fyUIhz
 * ksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9HRCwBXbsdtTLS
 * R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp
 * -----END CERTIFICATE-----
 */
#define CERT_FILE "/romfs/certs/cert.pem"

/*
 * X509 user-generated Root Certificate guide:
 * https://github.com/Azure/azure-iot-sdk-c/blob/master/tools
 * /CACertificates/CACertificateOverview.md
 */
#ifdef X509_CERTIFICATE
#ifdef USE_PROVISIONING
#define X509_CERT_FILE "/romfs/certs/provisioningx509.pem"
#define X509_KEY_FILE "/romfs/certs/provisioningx509key.pem"
#else /* USE_PROVISIONING */
#define X509_CERT_FILE "/romfs/certs/x509.pem"
#define X509_KEY_FILE "/romfs/certs/x509key.pem"
#endif /* USE_PROVISIONING */
#else /* X509_CERTIFICATE */
#define X509_CERT_FILE NULL
#define X509_KEY_FILE NULL
/*
 * Primary Key is only needed by SAS device authentication.
 * It can be found via:
 * Azure IoT Hub Service -> IoT devices -> one specific device
 *  -> Primary or Secondary Key
 *
 * For a DPS provisioned device, its primary key is the same as its enrollment's.
 */
#ifdef USE_PROVISIONING
static char primaryKey[] = "ch/EsBKjGNEHLc......";
#else /* USE_PROVISIONING */
static char primaryKey[] = "wJ05Y6MESMLPo7......";
#endif /* USE_PROVISIONING */
#endif /* X509_CERTIFICATE */

/* Shared access key duration time in seconds (one hour) */
#define SAS_KEY_DURATION_SECONDS 3600

#define PAYLOAD "Hello world!"

/*
 * After the device received C2D message number == SAMPLE_STOP_THRESHOLD,
 * this sample exits
 */
#define SAMPLE_STOP_THRESHOLD 3

/* Telemetry message number to be sent */
#define TELEMETRY_COUNT 3

/*
 * After 240 seconds, the broker should send a PING message to the client
 * if no other messages have been exchanged in that time.
 */
#define MQTT_KEEPALIVE_SECS 240

/* locals */

static const int mqttVer = MQTT_PROTOCOL_V311;
static az_span const testMethodName = AZ_SPAN_LITERAL_FROM_STR("testMethod");
static az_span const testMethodResp = AZ_SPAN_LITERAL_FROM_STR("{\"response\": \"OK\"}");

/* desired attribute for device twin */
static az_span const desiredDeviceCountPropName = AZ_SPAN_LITERAL_FROM_STR("device_count");
static az_span const twinPatchRequestId = AZ_SPAN_LITERAL_FROM_STR("reported_prop");
static az_span const twinDocRequestId = AZ_SPAN_LITERAL_FROM_STR("get_twin");
static int32_t deviceCount = 0;

/*******************************************************************************
 *
 * mqttPublish - publish an MQTT message to Azure IoT Hub
 *
 * This routine publishes a messsage to Azure IoT Hub. It can be used to publish
 * telemetry messages or response messages to Method-Calls.
 *
 * ERRNO: N/A
 *
 * RETURNS: true if the publication is successful, otherwise false
 *
 * \NOMANUAL
 */

static bool mqttPublish(
    struct mosquitto* mqttClient, /* MQTT client object */
    int* msgId, /* message ID */
    char* topic, /* topic to publish */
    int payloadLen, /* payload length */
    void* payload /* payload data */
)
{
  int res;
  res = mosquitto_publish(mqttClient, msgId, topic, payloadLen, payload, 1, true);
  if (res != MOSQ_ERR_SUCCESS)
  {
    printf_s("mosquitto_publish ERROR: %s\n", mosquitto_strerror(res));
    return false;
  }
  printf_s("mosquitto_publish: %d OK.\n", *msgId);
  return true;
}

/*******************************************************************************
 *
 * onConnect - connect callback of a MQTT client
 *
 * This routine is called when a MQTT client is connected to the Azure IoT Hub.
 *
 * ERRNO: N/A
 *
 * RETURNS: void
 *
 * \NOMANUAL
 */

static void onConnect(
    struct mosquitto* mqttClient, /* MQTT client object */
    void* obj, /* user data provided in mosquitto_new */
    int result /* result of the connection */
)
{
  if (result == MOSQ_ERR_SUCCESS)
  {
    printf_s("connect OK.\n");
  }
  else
  {
    printf_s("connect ERROR: %s\n", mosquitto_strerror(result));
  }
}

/*******************************************************************************
 *
 * onPublish - publish callback of a MQTT client
 *
 * This routine is called when a MQTT client finishes sending a message to the
 * Azure IoT Hub.
 *
 * ERRNO: N/A
 *
 * RETURNS: void
 *
 * \NOMANUAL
 */

static void onPublish(
    struct mosquitto* mqttClient, /* MQTT client object */
    void* obj, /* user data provided in mosquitto_new */
    int msgId /* message ID */
)
{
  printf_s("Message: %d is sent!\n", msgId);
}

/*******************************************************************************
 *
 * testMethod - the implementation of method "testMethod"
 *
 * This routine is called when the Azure IoT Hub calls a device method named
 * "testMethod".
 *
 * ERRNO: N/A
 *
 * RETURNS: void
 *
 * \NOMANUAL
 */

static void testMethod(void) { printf_s("testMethod is invoked\n"); }

/*******************************************************************************
 *
 * methodSuccessRespSend - send a success response for a method call
 *
 * This routine is to publish a message with the method response topic to
 * the Azure IoT Hub to confirm that the method is successfully called.
 *
 * ERRNO: N/A
 *
 * RETURNS: void
 *
 * \NOMANUAL
 */

static void methodSuccessRespSend(
    struct mosquitto* mqttClient, /* MQTT client object */
    az_iot_hub_client* azureClient, /* Azure client object */
    az_iot_hub_client_method_request* req /* method request object */
)
{
  char respTopic[128];
  uint16_t status = (uint16_t)AZ_IOT_STATUS_OK;
  int msgId;

  if (az_result_failed(az_iot_hub_client_methods_response_get_publish_topic(
          azureClient, req->request_id, status, respTopic, sizeof(respTopic), NULL)))
  {
    printf_s("az_iot_hub_client_methods_response_get_publish_topic "
             "ERROR!\n");
    return;
  }
  (void)mqttPublish(
      mqttClient, &msgId, respTopic, az_span_size(testMethodResp), az_span_ptr(testMethodResp));
}

/*******************************************************************************
 *
 * methodReqHandle - handle a method request
 *
 * This routine calls a device method according the method name provided by the Azure
 * IoT Hub.
 *
 * ERRNO: N/A
 *
 * RETURNS: void
 *
 * \NOMANUAL
 */

static void methodReqHandle(
    struct mosquitto* mqttClient, /* MQTT client object */
    az_iot_hub_client* azureClient, /* Azure client object */
    az_iot_hub_client_method_request* methodReq /* method request object */
)
{
  if (az_span_is_content_equal(methodReq->name, testMethodName))
  {
    testMethod();
    methodSuccessRespSend(mqttClient, azureClient, methodReq);
  }
}

/*******************************************************************************
 *
 * reportedPropertyGenerate - generate a reported property payload
 *
 * This routine generates a device twin reported property payload in JSON format.
 *
 * ERRNO: N/A
 *
 * RETURNS: true if the generation is successful, otherwise false
 *
 * \NOMANUAL
 */

static bool reportedPropertyGenerate(az_span reportPropPayload, az_span* outReportPropPayload)
{
  az_json_writer jsonWriter;

  if (az_result_failed(az_json_writer_init(&jsonWriter, reportPropPayload, NULL)))
  {
    printf_s("az_json_writer_init ERROR!\n");
    return false;
  }

  if (az_result_failed(az_json_writer_append_begin_object(&jsonWriter)))
  {
    printf_s("az_json_writer_append_begin_object ERROR!\n");
    return false;
  }

  if (az_result_failed(
          az_json_writer_append_property_name(&jsonWriter, desiredDeviceCountPropName)))
  {
    printf_s("az_json_writer_append_property_name ERROR!\n");
    return false;
  }

  if (az_result_failed(az_json_writer_append_int32(&jsonWriter, deviceCount)))
  {
    printf_s("az_json_writer_append_int32 ERROR!\n");
    return false;
  }

  if (az_result_failed(az_json_writer_append_end_object(&jsonWriter)))
  {
    printf_s("az_json_writer_append_end_object ERROR!\n");
    return false;
  }

  *outReportPropPayload = az_json_writer_get_bytes_used_in_destination(&jsonWriter);

  return true;
}

/*******************************************************************************
 *
 * deviceTwinReportedPropertySend - send a reported property
 *
 * This routine sends a reported property to the Azure IoT Hub for device twin.
 *
 * ERRNO: N/A
 *
 * RETURNS: void
 *
 * \NOMANUAL
 */

static void deviceTwinReportedPropertySend(
    struct mosquitto* mqttClient, /* MQTT client object */
    az_iot_hub_client* azureClient /* Azure client object */
)
{
  char twinPatchTopic[128];
  char reportedPropPayloadBuff[128];
  az_span reportedPropPayload = AZ_SPAN_FROM_BUFFER(reportedPropPayloadBuff);
  int msgId;

  if (az_result_failed(az_iot_hub_client_twin_patch_get_publish_topic(
          azureClient, twinPatchRequestId, twinPatchTopic, sizeof(twinPatchTopic), NULL)))
  {
    printf_s("az_iot_hub_client_twin_patch_get_publish_topic ERROR!\n");
    return;
  }

  if (!reportedPropertyGenerate(reportedPropPayload, &reportedPropPayload))
  {
    return;
  }

  (void)mqttPublish(
      mqttClient,
      &msgId,
      twinPatchTopic,
      az_span_size(reportedPropPayload),
      az_span_ptr(reportedPropPayload));
}

/*******************************************************************************
 *
 * desiredPropertiesHandle - handle desired property change messages
 *
 * This routine parses a desired twin property change message, extracts
 * the interested desired property value and stores it.
 *
 * ERRNO: N/A
 *
 * RETURNS: true if the parsing is successful, otherwise false
 *
 * \NOMANUAL
 */

static bool desiredPropertiesHandle(const struct mosquitto_message* message /* message object */
)
{
  const az_span msgSpan = az_span_create((uint8_t*)(message->payload), message->payloadlen);
  az_json_reader jsonReader;
  bool isFound = false;

  if (az_result_failed(az_json_reader_init(&jsonReader, msgSpan, NULL)))
  {
    printf_s("az_json_reader_init ERROR!\n");
    return false;
  }

  if (az_result_failed(az_json_reader_next_token(&jsonReader)))
  {
    printf_s("az_json_reader_next_token ERROR!\n");
    return false;
  }

  if (jsonReader.token.kind != AZ_JSON_TOKEN_BEGIN_OBJECT)
  {
    printf_s("The desired properties response does NOT contain "
             "a valid JSON object!\n");
    return false;
  }

  if (az_result_failed(az_json_reader_next_token(&jsonReader)))
  {
    printf_s("az_json_reader_next_token 2 ERROR!\n");
    return false;
  }

  while (jsonReader.token.kind != AZ_JSON_TOKEN_END_OBJECT)
  {
    if (az_json_token_is_text_equal(&jsonReader.token, desiredDeviceCountPropName))
    {
      isFound = true;
      break;
    }
    if (az_result_failed(az_json_reader_skip_children(&jsonReader)))
    {
      /* just for suppressing GCC warning [-Wunused-result] */
    }
    if (az_result_failed(az_json_reader_next_token(&jsonReader)))
    {
      printf_s("az_json_reader_next_token 3 ERROR!\n");
      break;
    }
  }
  if (isFound)
  {
    if (az_result_failed(az_json_reader_next_token(&jsonReader)))
    {
      printf_s("az_json_reader_next_token 4 ERROR!\n");
      return false;
    }
    if (az_result_failed(az_json_token_get_int32(&jsonReader.token, &deviceCount)))
    {
      printf_s("az_json_token_get_int32 ERROR!\n");
      return false;
    }
    return true;
  }
  printf_s("Can NOT find desired property: %s\n", az_span_ptr(desiredDeviceCountPropName));
  return false;
}

/*******************************************************************************
 *
 * mqttMessagePayloadDump - dump the payload of an MQTT message
 *
 * This routine dumps the payload of an MQTT message.
 *
 * ERRNO: N/A
 *
 * RETURNS: void
 *
 * \NOMANUAL
 */

static void mqttMessagePayloadDump(
    const char* info, /* extra information */
    const struct mosquitto_message* message /* message object */
)
{
  char* c = (char*)(message->payload);
  printf_s("%s: ", info);
  for (int i = 0; i < message->payloadlen; i++)
  {
    printf_s("%c", c[i]);
  }
  printf_s("\n");
}

/*******************************************************************************
 *
 * deviceTwinRspHandle - handle Device Twin response messages
 *
 * This routine handles Device Twin response messages by type.
 *
 * ERRNO: N/A
 *
 * RETURNS: void
 *
 * \NOMANUAL
 */

static void deviceTwinRspHandle(
    struct mosquitto* mqttClient, /* MQTT client object */
    az_iot_hub_client* azureClient, /* Azure client object */
    const struct mosquitto_message* message, /* message object */
    az_iot_hub_client_twin_response* twinRsp /* twin response object */
)
{
  switch (twinRsp->response_type)
  {
    case AZ_IOT_HUB_CLIENT_TWIN_RESPONSE_TYPE_GET:
      printf_s("Twin response: GET\n");
      mqttMessagePayloadDump("payload", message);
      /*
       * getting document is successful. Send the reported property to
       * the Device Twin.
       */
      deviceTwinReportedPropertySend(mqttClient, azureClient);
      break;
    case AZ_IOT_HUB_CLIENT_TWIN_RESPONSE_TYPE_REPORTED_PROPERTIES:
      printf_s("Twin response: REPORTED PROPERTIES\n");
      break;
    case AZ_IOT_HUB_CLIENT_TWIN_RESPONSE_TYPE_DESIRED_PROPERTIES:
      printf_s("Twin response: DESIRED PROPERTIES\n");
      mqttMessagePayloadDump("payload", message);
      if (desiredPropertiesHandle(message))
      {
        /*
         * desired properties are successfully retrieved. update the
         * corresponding reported property.
         */
        deviceTwinReportedPropertySend(mqttClient, azureClient);
      }
      break;
    default:
      printf_s("Invalidate Twin Response Type!\n");
      break;
  }
}

/*******************************************************************************
 *
 * onMessage - message receiving callback of a MQTT client
 *
 * This routine is called when a MQTT client receives a message.
 *
 * ERRNO: N/A
 *
 * RETURNS: void
 *
 * \NOMANUAL
 */

static void onMessage(
    struct mosquitto* mqttClient, /* MQTT client object */
    void* obj, /* user data provided */
    /* in mosquitto_new */
    const struct mosquitto_message* message /* message object */
)
{
  az_span topic;
  az_iot_hub_client_c2d_request c2dReq;
  az_iot_hub_client_method_request methodReq;
  az_iot_hub_client_twin_response twinResp;
  az_iot_hub_client* azureClient;
  static int received = 0;

  if (message == NULL)
  {
    printf_s("onMessage: message is NULL!\n");
    return;
  }
  topic = az_span_create_from_str(message->topic);

  if (obj != NULL)
  {
    azureClient = (az_iot_hub_client*)obj;
  }
  else
  {
    printf_s("onMessage: obj is NULL!\n");
    return;
  }

  /* handle different topics in different ways */
  if (az_result_succeeded(az_iot_hub_client_c2d_parse_received_topic(azureClient, topic, &c2dReq)))
  {
    /* C2D */
    printf_s("C2D Topic: %s\n", message->topic);
    mqttMessagePayloadDump("C2D Payload", message);
    received++;
  }
  else if (az_result_succeeded(
               az_iot_hub_client_methods_parse_received_topic(azureClient, topic, &methodReq)))
  {
    /* Methods */
    methodReqHandle(mqttClient, azureClient, &methodReq);
  }
  else if (az_result_succeeded(
               az_iot_hub_client_twin_parse_received_topic(azureClient, topic, &twinResp)))
  {
    /* twin response */
    deviceTwinRspHandle(mqttClient, azureClient, message, &twinResp);
  }
  else
  {
    printf_s("Unexpected message received!\n");
  }

  /* stop the sample as soon as enough C2D messages are received */
  if (received == SAMPLE_STOP_THRESHOLD)
  {
    (void)mosquitto_disconnect(mqttClient);
  }
}

#ifndef X509_CERTIFICATE

/*******************************************************************************
 *
 * base64Decode - decode a base64 string to a binary buffer
 *
 * This routine decodes a base64 string to a binary buffer.
 *
 * ERRNO: N/A
 *
 * RETURNS: true if the decoding is successful, false otherwise
 *
 * \NOMANUAL
 */

static bool base64Decode(az_span base64Encoded, az_span* decoded)
{
  BIO* base64Decoder = NULL;
  BIO* sourceMemBio = NULL;
  int readCnt = 0;

  (void)memset_s(
      az_span_ptr(*decoded), (size_t)az_span_size(*decoded), 0, (size_t)az_span_size(*decoded));

  /* Create a base64 BIO filter */
  base64Decoder = BIO_new(BIO_f_base64());
  if (base64Decoder == NULL)
  {
    printf_s("BIO_new ERROR!\n");
    goto base64DecodeFailed;
  }

  /* Create a source BIO memory */
  sourceMemBio = BIO_new_mem_buf(az_span_ptr(base64Encoded), (int)az_span_size(base64Encoded));
  if (sourceMemBio == NULL)
  {
    printf_s("BIO_new_mem_buf ERROR!\n");
    goto base64DecodeFailed;
  }

  /* push the base64 encoded buffer through the base64 filter */
  sourceMemBio = BIO_push(base64Decoder, sourceMemBio);
  if (sourceMemBio == NULL)
  {
    printf_s("BIO_push ERROR!\n");
    goto base64DecodeFailed;
  }

  /* set no newline flag and close the BIO */
  BIO_set_flags(sourceMemBio, BIO_FLAGS_BASE64_NO_NL);
  BIO_set_close(sourceMemBio, BIO_CLOSE);

  /* read the decoded buffer out */
  readCnt = BIO_read(sourceMemBio, az_span_ptr(*decoded), (int)az_span_size(*decoded));
  if (readCnt <= 0)
  {
    printf_s("BIO_read ERROR!\n");
    goto base64DecodeFailed;
  }

  /* update the span size with the actual data size */
  *decoded = az_span_create(az_span_ptr(*decoded), (int32_t)readCnt);
  BIO_free_all(sourceMemBio);
  return true;

base64DecodeFailed:
  if (base64Decoder != NULL)
  {
    BIO_free(base64Decoder);
  }

  if (sourceMemBio != NULL)
  {
    BIO_free(sourceMemBio);
  }

  return false;
}

/*******************************************************************************
 *
 * base64Encode - encode a binary buffer to a base64 string
 *
 * This routine encodes a binary buffer to a base64 string.
 *
 * ERRNO: N/A
 *
 * RETURNS: true if the encoding is successful, false otherwise
 *
 * \NOMANUAL
 */

static bool base64Encode(az_span hmac256SignedSignature, az_span* base64SignedSignature)
{
  BIO* base64Encoder = NULL;
  BIO* sinkMemBio = NULL;
  BUF_MEM* encodedMemPtr = NULL;
  int writeCnt = 0;

  /* create a base64 filter */
  base64Encoder = BIO_new(BIO_f_base64());
  if (base64Encoder == NULL)
  {
    printf_s("BIO_new base64 encoder ERROR!\n");
    goto base64EncodeFailed;
  }

  /* create a sink memory BIO */
  sinkMemBio = BIO_new(BIO_s_mem());
  if (sinkMemBio == NULL)
  {
    printf_s("BIO_new sink memory ERROR!\n");
    goto base64EncodeFailed;
  }

  /* push the sink to the encoder */
  base64Encoder = BIO_push(base64Encoder, sinkMemBio);
  if (base64Encoder == NULL)
  {
    printf_s("BIO_push base64 encoder ERROR!\n");
    goto base64EncodeFailed;
  }
  /* set no newline flag */
  BIO_set_flags(base64Encoder, BIO_FLAGS_BASE64_NO_NL);

  /* write the bytes to the encoder */
  writeCnt = BIO_write(
      base64Encoder,
      az_span_ptr(hmac256SignedSignature),
      (int)az_span_size(hmac256SignedSignature));
  if (writeCnt < 1)
  {
    printf_s("BIO_write ERROR!\n");
    goto base64EncodeFailed;
  }

  /* flush BIO */
  BIO_flush(base64Encoder);

  /* get the pointer to the encoded bytes */
  BIO_get_mem_ptr(base64Encoder, &encodedMemPtr);

  if (encodedMemPtr == NULL)
  {
    printf_s("BIO_get_mem_ptr ERROR!\n");
    goto base64EncodeFailed;
  }
  if (encodedMemPtr->length > az_span_size(*base64SignedSignature))
  {
    printf_s("Signed Signature buffer doesn't have enough space!\n");
    goto base64EncodeFailed;
  }

  /* copy the encoded bytes to the output span and update the span size */
  (void)memcpy_s(
      az_span_ptr(*base64SignedSignature),
      (size_t)az_span_size(*base64SignedSignature),
      encodedMemPtr->data,
      (size_t)encodedMemPtr->length);
  *base64SignedSignature
      = az_span_create(az_span_ptr(*base64SignedSignature), (int32_t)encodedMemPtr->length);
  BIO_free_all(base64Encoder);
  return true;
base64EncodeFailed:
  if (base64Encoder != NULL)
  {
    BIO_free(base64Encoder);
  }
  if (sinkMemBio != NULL)
  {
    BIO_free(sinkMemBio);
  }
  return false;
}

/*******************************************************************************
 *
 * hmacSha256SignatureSign - generate a HMAC-SHA256 signature
 *
 * This routine generates a HMAC-SHA256 signature.
 *
 * ERRNO: N/A
 *
 * RETURNS: true if successful, false otherwise
 *
 * \NOMANUAL
 */

static bool hmacSha256SignatureSign(az_span decodedKey, az_span signature, az_span* signedSignature)
{
  unsigned int mdLen;

  unsigned char* hmac = HMAC(
      EVP_sha256(),
      az_span_ptr(decodedKey),
      az_span_size(decodedKey),
      az_span_ptr(signature),
      (size_t)az_span_size(signature),
      az_span_ptr(*signedSignature),
      &mdLen);
  if (hmac == NULL)
  {
    printf_s("HMAC ERROR!\n");
    return false;
  }
  *signedSignature = az_span_create(az_span_ptr(*signedSignature), mdLen);
  return true;
}

/*******************************************************************************
 *
 * mqttPasswordGen - generate a MQTT password when using Shared Access Key
 *
 * This routine generates a MQTT password when using Shared Access Key.
 *
 * ERRNO: N/A
 *
 * RETURNS: true if successful, false otherwise
 *
 * \NOMANUAL
 */

static bool mqttPasswordGen(
    az_iot_hub_client* azureClient, /* Azure IoT Hub client object */
    char* pwdBuffer, /* password buffer */
    size_t pwdBufferLen /* password buffer length */
)
{
  static char sasSignature[128] = { 0 };
  static char sasBase64SignedSignature[128] = { 0 };
  /* calculate duration time */
  uint64_t sasKeyExpirationTime = (uint64_t)time(NULL) + SAS_KEY_DURATION_SECONDS;
  az_span sasBase64SignedSignatureSpan = AZ_SPAN_FROM_BUFFER(sasBase64SignedSignature);
  az_span primaryKeySpan = AZ_SPAN_FROM_BUFFER(primaryKey);
  static char sasDecodedKey[64] = { 0 };
  az_span sasDecodedKeySpan = AZ_SPAN_FROM_BUFFER(sasDecodedKey);
  static char sasHMAC256SignedSignature[128] = { 0 };
  az_span sasHMAC256SignedSignatureSpan = AZ_SPAN_FROM_BUFFER(sasHMAC256SignedSignature);
  size_t pwdLen;

  /* get the signature which will be signed with the decoded key */
  az_span sasSignatureSpan = AZ_SPAN_FROM_BUFFER(sasSignature);
  if (az_result_failed(az_iot_hub_client_sas_get_signature(
          azureClient, sasKeyExpirationTime, sasSignatureSpan, &sasSignatureSpan)))
  {
    printf_s("az_iot_hub_client_sas_get_signature ERROR!\n");
    return false;
  }

  if (!base64Decode(primaryKeySpan, &sasDecodedKeySpan))
  {
    return false;
  }

  if (!hmacSha256SignatureSign(sasDecodedKeySpan, sasSignatureSpan, &sasHMAC256SignedSignatureSpan))
  {
    return false;
  }

  if (!base64Encode(sasHMAC256SignedSignatureSpan, &sasBase64SignedSignatureSpan))
  {
    return false;
  }

  /* get the MQTT password */
  if (az_result_failed(az_iot_hub_client_sas_get_password(
          azureClient,
          sasKeyExpirationTime,
          sasBase64SignedSignatureSpan,
          AZ_SPAN_EMPTY,
          pwdBuffer,
          pwdBufferLen,
          &pwdLen)))
  {
    printf_s("az_iot_hub_client_sas_get_password ERROR!\n");
    return false;
  }

  return true;
}
#endif /* X509_CERTIFICATE */

/*******************************************************************************
 *
 * azureClientCreate - create and initialize an Azure IoT Hub client object
 *
 * This routine creates and initializes an Azure IoT Hub client object with the
 * default options. The user name and client ID are retrieved for
 * the further use. The password is generated for SAS.
 *
 * ERRNO: N/A
 *
 * RETURNS: true if successful, otherwise false
 *
 * \NOMANUAL
 */

static bool azureClientCreate(
    az_iot_hub_client* azureClient, /* Azure IoT Hub client object */
    char* azureUserName, /* Azure user name buffer */
    size_t azureUserNameLen, /* length of Azure user name */
    char* azureClientId, /* Azure IoT Hub client ID buffer */
    size_t azureClientIdLen /* length of Azure IoT Hub */
    /* client ID */
)
{
  az_result azureRes;
  az_iot_hub_client_options azureOpt = az_iot_hub_client_options_default();
  az_span iotHubHostName = AZ_SPAN_LITERAL_FROM_STR(HOST);
  az_span iotHubDeviceId = AZ_SPAN_LITERAL_FROM_STR(DEVICE_ID);

  /* init Azure Client */
  azureRes = az_iot_hub_client_init(azureClient, iotHubHostName, iotHubDeviceId, &azureOpt);
  if (az_result_succeeded(azureRes))
  {
    printf_s("az_iot_hub_client_init OK.");
  }
  else
  {
    printf_s("az_iot_hub_client_init ERROR!\n");
    return false;
  }

  azureRes = az_iot_hub_client_get_client_id(azureClient, azureClientId, azureClientIdLen, NULL);
  if (az_result_succeeded(azureRes))
  {
    printf_s("az_iot_hub_client_get_client_id OK: %s\n", azureClientId);
  }
  else
  {
    printf_s("az_iot_hub_client_get_client_id ERROR!\n");
    return false;
  }
#ifndef X509_CERTIFICATE
  /* generate a password for MQTT */
  if (!mqttPasswordGen(azureClient, pwd, sizeof(pwd)))
  {
    return false;
  }
#endif
  azureRes = az_iot_hub_client_get_user_name(azureClient, azureUserName, azureUserNameLen, NULL);
  if (az_result_succeeded(azureRes))
  {
    printf_s("az_iot_hub_client_get_user_name OK: %s\n", azureUserName);
  }
  else
  {
    printf_s("az_iot_hub_client_get_user_name ERROR!\n");
    return false;
  }

  return true;
}

/*******************************************************************************
 *
 * mqttClientDestroy - destroy a MQTT client object
 *
 * This routine first disconnects the MQTT client from the Azure IoT Hub,
 * destroys the mosquitto object and does the library cleanup for mosquitto.
 *
 * ERRNO: N/A
 *
 * RETURNS: void
 *
 * \NOMANUAL
 */

static void mqttClientDestroy(struct mosquitto* mqttClient /* MQTT client object */
)
{
  if (mqttClient != NULL)
  {
    (void)mosquitto_disconnect(mqttClient);
    mosquitto_destroy(mqttClient);
    mqttClient = NULL;
    (void)mosquitto_lib_cleanup();
  }
}

/*******************************************************************************
 *
 * mqttClientCreate - create a MQTT client object
 *
 * This routine creates a MQTT client object and connects it to the
 * Azure IoT Hub.
 *
 * ERRNO: N/A
 *
 * RETURNS: a pointer to the MQTT client object if successful, otherwise NULL
 *
 * \NOMANUAL
 */

static struct mosquitto* mqttClientCreate(
    az_iot_hub_client* azureClient, /* Azure IoT Hub client object */
    char* azureUserName, /* Azure user name */
    char* azureClientId /* Azure IoT Hub client ID */
)
{
  int mqttRes;
  struct mosquitto* mqttClient = NULL;

  /* init the mosquitto lib */
  mqttRes = mosquitto_lib_init();
  if (mqttRes == MOSQ_ERR_SUCCESS)
  {
    printf_s("mosquitto_lib_init() OK\n");
  }
  else
  {
    printf_s("mosquitto_lib_init ERROR: %s\n", mosquitto_strerror(mqttRes));
    return NULL;
  }

  /* create an MQTT client */
  mqttClient = mosquitto_new(azureClientId, true, azureClient);
  if (mqttClient != NULL)
  {
    printf_s("mosquitto_new OK.\n");
  }
  else
  {
    printf_s("mosquitto_new ERROR: %s\n", mosquitto_strerror(mqttRes));
    (void)mosquitto_lib_cleanup();
    return NULL;
  }

  /* add callbacks */
  mosquitto_connect_callback_set(mqttClient, onConnect);
  mosquitto_publish_callback_set(mqttClient, onPublish);
  mosquitto_message_callback_set(mqttClient, onMessage);

  mqttRes = mosquitto_username_pw_set(mqttClient, azureUserName, pwd);
  if (mqttRes == MOSQ_ERR_SUCCESS)
  {
    printf_s("mosquitto_username_pw_set OK.\n");
  }
  else
  {
    printf_s("mosquitto_username_pw_set ERROR: %s\n", mosquitto_strerror(mqttRes));
    goto mqttCreationFailed;
  }

  /* configure CERT */
  mqttRes = mosquitto_tls_opts_set(mqttClient, 1, "tlsv1.2", NULL);
  if (mqttRes == MOSQ_ERR_SUCCESS)
  {
    printf_s("mosquitto_tls_opts_set OK.\n");
  }
  else
  {
    printf_s("mosquitto_tls_opts_set ERROR: %s\n", mosquitto_strerror(mqttRes));
    goto mqttCreationFailed;
  }

  mqttRes = mosquitto_tls_set(mqttClient, CERT_FILE, NULL, X509_CERT_FILE, X509_KEY_FILE, NULL);
  if (mqttRes == MOSQ_ERR_SUCCESS)
  {
    printf_s("mosquitto_tls_set OK.\n");
  }
  else
  {
    printf_s("mosquitto_tls_set ERROR: %s\n", mosquitto_strerror(mqttRes));
    goto mqttCreationFailed;
  }
  mqttRes = mosquitto_tls_insecure_set(mqttClient, false);
  if (mqttRes == MOSQ_ERR_SUCCESS)
  {
    printf_s("mosquitto_tls_insecure_set OK.\n");
  }
  else
  {
    printf_s("mosquitto_tls_insecure_set ERROR: %s\n", mosquitto_strerror(mqttRes));
    goto mqttCreationFailed;
  }

  /* configure MQTT version */
  mqttRes = mosquitto_opts_set(mqttClient, MOSQ_OPT_PROTOCOL_VERSION, &mqttVer);
  if (mqttRes == MOSQ_ERR_SUCCESS)
  {
    printf_s("mosquitto_opts_set(MOSQ_OPT_PROTOCOL_VERSION) OK\n");
  }
  else
  {
    printf_s("mosquitto_opts_set(MOSQ_OPT_PROTOCOL_VERSION) ERROR\n");
    goto mqttCreationFailed;
  }

  mqttRes = mosquitto_connect(mqttClient, HOST, PORT, MQTT_KEEPALIVE_SECS);
  if (mqttRes == MOSQ_ERR_SUCCESS)
  {
    printf_s("mosquitto_connect OK.\n");
  }
  else
  {
    printf_s("mosquitto_connect ERROR: %s\n", mosquitto_strerror(mqttRes));
    goto mqttCreationFailed;
  }

  return mqttClient;

mqttCreationFailed:
  mqttClientDestroy(mqttClient);
  return NULL;
}

/*******************************************************************************
 *
 * mqttSubscribe - subscribe to MQTT topics
 *
 * This routine subscribes to Azure IoT Hub MQTT topics:
 * 1. C2D message
 * 2. methods-call
 * 3. device twin reported property changes
 * 4. device twin operation responses
 *
 * ERRNO: N/A
 *
 * RETURNS: true if successful, otherwise false
 *
 * \NOMANUAL
 */

static bool mqttSubscribe(struct mosquitto* mqttClient)
{
  int mqttRes;

  /* subscribe to C2D messages */
  mqttRes = mosquitto_subscribe(mqttClient, NULL, AZ_IOT_HUB_CLIENT_C2D_SUBSCRIBE_TOPIC, 0);
  if (mqttRes == MOSQ_ERR_SUCCESS)
  {
    printf_s("mosquitto_subscribe C2D OK.\n");
  }
  else
  {
    printf_s("mosquitto_subscribe C2D ERROR: %s\n", mosquitto_strerror(mqttRes));
    return false;
  }

  /* subscribe to device method messages */
  mqttRes = mosquitto_subscribe(mqttClient, NULL, AZ_IOT_HUB_CLIENT_METHODS_SUBSCRIBE_TOPIC, 0);
  if (mqttRes == MOSQ_ERR_SUCCESS)
  {
    printf_s("mosquitto_subscribe Method OK.\n");
  }
  else
  {
    printf_s("mosquitto_subscribe Method ERROR: %s\n", mosquitto_strerror(mqttRes));
    return false;
  }

  /* subscribe to twin reported property changes */
  mqttRes = mosquitto_subscribe(mqttClient, NULL, AZ_IOT_HUB_CLIENT_TWIN_PATCH_SUBSCRIBE_TOPIC, 0);
  if (mqttRes == MOSQ_ERR_SUCCESS)
  {
    printf_s("mosquitto_subscribe Twin Patch OK.\n");
  }
  else
  {
    printf_s("mosquitto_subscribe Twin Patch ERROR: %s\n", mosquitto_strerror(mqttRes));
    return false;
  }

  /* subscribe to twin operation responses */
  mqttRes
      = mosquitto_subscribe(mqttClient, NULL, AZ_IOT_HUB_CLIENT_TWIN_RESPONSE_SUBSCRIBE_TOPIC, 0);
  if (mqttRes == MOSQ_ERR_SUCCESS)
  {
    printf_s("mosquitto_subscribe Twin Response OK.\n");
  }
  else
  {
    printf_s("mosquitto_subscribe Twin Response ERROR: %s\n", mosquitto_strerror(mqttRes));
    return false;
  }

  return true;
}

/*******************************************************************************
 *
 * deviceTwinDocumentGet - publish a device twin document get message
 *
 * This routine publishes a device twin document get message.
 *
 * ERRNO: N/A
 *
 * RETURNS: true if successful, false otherwise
 *
 * \NOMANUAL
 */

static bool deviceTwinDocumentGet(struct mosquitto* mqttClient, az_iot_hub_client* azureClient)
{
  char twinDocGetTopic[128];
  int msgId;

  if (az_result_failed(az_iot_hub_client_twin_document_get_publish_topic(
          azureClient, twinDocRequestId, twinDocGetTopic, sizeof(twinDocGetTopic), NULL)))
  {
    printf_s("az_iot_hub_client_twin_document_get_publish_topic ERROR!\n");
    return false;
  }

  return mqttPublish(mqttClient, &msgId, twinDocGetTopic, 0, NULL);
}

/*******************************************************************************
 *
 * telemetrySend - send a telemetry message to Azure IoT Hub
 *
 * This routine sends 3 telemetry messages to Azure IoT Hub.
 *
 * ERRNO: N/A
 *
 * RETURNS: true if messages are sent, otherwise false
 *
 * \NOMANUAL
 */

static bool telemetrySend(struct mosquitto* mqttClient, az_iot_hub_client* azureClient)
{
  char telemetryTopic[128];
  int msgId = 1;
  char msg[128];
  int pubMsgCnt = TELEMETRY_COUNT;

  /* get the publish topic used for sending telemetry messages */
  if (az_result_failed(az_iot_hub_client_telemetry_get_publish_topic(
          azureClient, NULL, telemetryTopic, sizeof(telemetryTopic), NULL)))
  {
    printf_s("az_iot_hub_client_telemetry_get_publish_topic ERROR!\n");
    return false;
  }

  /* send telemetry messages */
  while (pubMsgCnt > 0)
  {
    sprintf_s(msg, sizeof(msg), PAYLOAD ": %d", msgId);
    if (!mqttPublish(
            mqttClient, &msgId, telemetryTopic, (int)(strnlen_s(msg, sizeof(msg) - 1)), msg))
    {
      return false;
    }
    pubMsgCnt--;
  }
  return true;
}

/*******************************************************************************
 *
 * main - main function of a Real Time Process
 *
 * This sample runs as a RTP.
 * 1. It sends 3 telemetry messages to Azure IoT Hub and
 *    waits for Method-Calls and C2D messages.
 * 2. It also supports device twin communication. An attrubute "device_count" is
 *    used to show how it works.
 * 3. It exits when receiving 3 C2D messages.
 * messages.
 *
 * ERRNO: N/A
 *
 * RETURNS: 0 if successful, otherwise 1
 *
 * \NOMANUAL
 */

int main(void)
{
  struct mosquitto* mqttClient = NULL;
  az_iot_hub_client azureClient;
  char azureUserName[128];
  char azureClientId[128];
  int mqttRes;
  int ret = 0;

  /* initialize and configure Azure Client object */
  if (!azureClientCreate(
          &azureClient, azureUserName, sizeof(azureUserName), azureClientId, sizeof(azureClientId)))
  {
    ret = 1;
    goto failed;
  }

  /* initialize MQTT object and connect to Azure IoT Hub */
  mqttClient = mqttClientCreate(&azureClient, azureUserName, azureClientId);
  if (mqttClient == NULL)
  {
    ret = 1;
    goto failed;
  }

  if (!mqttSubscribe(mqttClient))
  {
    ret = 1;
    goto failed;
  }

  if (!deviceTwinDocumentGet(mqttClient, &azureClient))
  {
    ret = 1;
    goto failed;
  }

  if (!telemetrySend(mqttClient, &azureClient))
  {
    ret = 1;
    goto failed;
  }

  mqttRes = mosquitto_loop_forever(mqttClient, -1, 1);
  if (mqttRes == MOSQ_ERR_SUCCESS)
  {
    printf_s("mosquitto_loop_forever OK.\n");
  }
  else
  {
    printf_s("mosquitto_loop_forever ERROR: %s\n", mosquitto_strerror(mqttRes));
  }
failed:
  mqttClientDestroy(mqttClient);
  printf_s("Azure Client Sample done!\n");
  return ret;
}
