Microsoft Azure IoTHub client libraries

Dependents:   sht15_remote_monitoring RobotArmDemo iothub_client_sample_amqp f767zi_mqtt ... more

This library implements the Microsoft Azure IoTHub client library. The code is replicated from https://github.com/Azure/azure-iot-sdks

Revision:
66:a419827cb051
Parent:
63:1bf1c2d60aab
Child:
70:875388a7d419
--- a/iothub_client_ll.c	Fri Apr 21 14:50:19 2017 -0700
+++ b/iothub_client_ll.c	Mon May 08 10:50:31 2017 -0700
@@ -10,11 +10,13 @@
 #include "azure_c_shared_utility/xlogging.h"
 #include "azure_c_shared_utility/tickcounter.h"
 #include "azure_c_shared_utility/constbuffer.h"
+#include "azure_c_shared_utility/platform.h"
 
 #include "iothub_client_authorization.h"
 #include "iothub_client_ll.h"
 #include "iothub_transport_ll.h"
 #include "iothub_client_private.h"
+#include "iothub_client_options.h"
 #include "iothub_client_version.h"
 #include <stdint.h>
 
@@ -79,6 +81,7 @@
     uint32_t data_msg_id;
     bool complete_twin_update_encountered;
     IOTHUB_AUTHORIZATION_HANDLE authorization_module;
+    STRING_HANDLE product_info;
 }IOTHUB_CLIENT_LL_HANDLE_DATA;
 
 static const char HOSTNAME_TOKEN[] = "HostName";
@@ -148,203 +151,255 @@
 #endif
 }
 
-static IOTHUB_CLIENT_LL_HANDLE_DATA* initialize_iothub_client(const IOTHUB_CLIENT_CONFIG* client_config, const IOTHUB_CLIENT_DEVICE_CONFIG* device_config)
+/*Codes_SRS_IOTHUBCLIENT_LL_10_032: ["product_info" - takes a char string as an argument to specify the product information(e.g. `"ProductName/ProductVersion"`). ]*/
+/*Codes_SRS_IOTHUBCLIENT_LL_10_034: ["product_info" - shall store the given string concatenated with the sdk information and the platform information in the form(ProductInfo DeviceSDKName / DeviceSDKVersion(OSName OSVersion; Architecture). ]*/
+static STRING_HANDLE make_product_info(const char* product)
 {
-    IOTHUB_CLIENT_LL_HANDLE_DATA* result = (IOTHUB_CLIENT_LL_HANDLE_DATA*)malloc(sizeof(IOTHUB_CLIENT_LL_HANDLE_DATA));
-    if (result == NULL)
+    STRING_HANDLE result;
+    STRING_HANDLE pfi = platform_get_platform_info();
+    if (pfi == NULL)
     {
-        LogError("failure allocating IOTHUB_CLIENT_LL_HANDLE_DATA");
+        result = NULL;
     }
     else
     {
-        IOTHUB_CLIENT_CONFIG actual_config;
-        const IOTHUB_CLIENT_CONFIG* config = NULL;
-        char* IoTHubName = NULL;
-
-        memset(result, 0, sizeof(IOTHUB_CLIENT_LL_HANDLE_DATA) );
+        if (product == NULL)
+        {
+            result = STRING_construct_sprintf("%s %s", CLIENT_DEVICE_TYPE_PREFIX CLIENT_DEVICE_BACKSLASH IOTHUB_SDK_VERSION, STRING_c_str(pfi));
+        }
+        else
+        {
+            result = STRING_construct_sprintf("%s %s %s", product, CLIENT_DEVICE_TYPE_PREFIX CLIENT_DEVICE_BACKSLASH IOTHUB_SDK_VERSION, STRING_c_str(pfi));
+        }
+        STRING_delete(pfi);
+    }
+    return result;
+}
 
-        const char* device_key;
-        const char* device_id;
-        const char* sas_token;
-
-        if (device_config == NULL)
+static IOTHUB_CLIENT_LL_HANDLE_DATA* initialize_iothub_client(const IOTHUB_CLIENT_CONFIG* client_config, const IOTHUB_CLIENT_DEVICE_CONFIG* device_config)
+{
+    IOTHUB_CLIENT_LL_HANDLE_DATA* result;
+    STRING_HANDLE product_info = make_product_info(NULL);
+    if (product_info == NULL)
+    {
+        LogError("failed to initialize product info");
+        result = NULL;
+    }
+    else
+    {
+        result = (IOTHUB_CLIENT_LL_HANDLE_DATA*)malloc(sizeof(IOTHUB_CLIENT_LL_HANDLE_DATA));
+        if (result == NULL)
         {
-            device_key = client_config->deviceKey;
-            device_id = client_config->deviceId;
-            sas_token = client_config->deviceSasToken;
+            LogError("failure allocating IOTHUB_CLIENT_LL_HANDLE_DATA");
+            STRING_delete(product_info);
         }
         else
         {
-            device_key = device_config->deviceKey;
-            device_id = device_config->deviceId;
-            sas_token = device_config->deviceSasToken;
-        }
+            IOTHUB_CLIENT_CONFIG actual_config;
+            const IOTHUB_CLIENT_CONFIG* config = NULL;
+            char* IoTHubName = NULL;
+            STRING_HANDLE transport_hostname = NULL;
+
+            memset(result, 0, sizeof(IOTHUB_CLIENT_LL_HANDLE_DATA));
 
-        /* Codes_SRS_IOTHUBCLIENT_LL_07_029: [ IoTHubClient_LL_Create shall create the Auth module with the device_key, device_id, and/or deviceSasToken values ] */
-        if ((result->authorization_module = IoTHubClient_Auth_Create(device_key, device_id, sas_token) ) == NULL)
-        {
-            LogError("Failed create authorization module");
-            free(result);
-            result = NULL;
-        }
-        else if (client_config != NULL)
-        {
-            IOTHUBTRANSPORT_CONFIG lowerLayerConfig;
-            /*Codes_SRS_IOTHUBCLIENT_LL_02_006: [IoTHubClient_LL_Create shall populate a structure of type IOTHUBTRANSPORT_CONFIG with the information from config parameter and the previous DLIST and shall pass that to the underlying layer _Create function.]*/
-            lowerLayerConfig.upperConfig = client_config;
-            lowerLayerConfig.waitingToSend = &(result->waitingToSend);
-            lowerLayerConfig.auth_module_handle = result->authorization_module;
+            const char* device_key;
+            const char* device_id;
+            const char* sas_token;
 
-            setTransportProtocol(result, (TRANSPORT_PROVIDER*)client_config->protocol());
-            if ((result->transportHandle = result->IoTHubTransport_Create(&lowerLayerConfig)) == NULL)
+            if (device_config == NULL)
             {
-                /*Codes_SRS_IOTHUBCLIENT_LL_02_007: [If the underlaying layer _Create function fails them IoTHubClient_LL_Create shall fail and return NULL.] */
-                LogError("underlying transport failed");
-                destroy_blob_upload_module(result);
-                tickcounter_destroy(result->tickCounter);
-                IoTHubClient_Auth_Destroy(result->authorization_module);
-                free(result);
-                result = NULL;
+                device_key = client_config->deviceKey;
+                device_id = client_config->deviceId;
+                sas_token = client_config->deviceSasToken;
             }
             else
             {
-                /*Codes_SRS_IOTHUBCLIENT_LL_02_008: [Otherwise, IoTHubClient_LL_Create shall succeed and return a non-NULL handle.] */
-                result->isSharedTransport = false;
-                config = client_config;
+                device_key = device_config->deviceKey;
+                device_id = device_config->deviceId;
+                sas_token = device_config->deviceSasToken;
+            }
+
+            /* Codes_SRS_IOTHUBCLIENT_LL_07_029: [ IoTHubClient_LL_Create shall create the Auth module with the device_key, device_id, and/or deviceSasToken values ] */
+            if ((result->authorization_module = IoTHubClient_Auth_Create(device_key, device_id, sas_token)) == NULL)
+            {
+                LogError("Failed create authorization module");
+                free(result);
+                STRING_delete(product_info);
+                result = NULL;
             }
-        }
-        else
-        {
-            result->transportHandle = device_config->transportHandle;
-            setTransportProtocol(result, (TRANSPORT_PROVIDER*)device_config->protocol());
+            else if (client_config != NULL)
+            {
+                IOTHUBTRANSPORT_CONFIG lowerLayerConfig;
+                memset(&lowerLayerConfig, 0, sizeof(IOTHUBTRANSPORT_CONFIG));
+                /*Codes_SRS_IOTHUBCLIENT_LL_02_006: [IoTHubClient_LL_Create shall populate a structure of type IOTHUBTRANSPORT_CONFIG with the information from config parameter and the previous DLIST and shall pass that to the underlying layer _Create function.]*/
+                lowerLayerConfig.upperConfig = client_config;
+                lowerLayerConfig.waitingToSend = &(result->waitingToSend);
+                lowerLayerConfig.auth_module_handle = result->authorization_module;
 
-            STRING_HANDLE transport_hostname = result->IoTHubTransport_GetHostname(result->transportHandle);
-            if (transport_hostname == NULL)
-            {
-                /*Codes_SRS_IOTHUBCLIENT_LL_02_097: [ If creating the data structures fails or instantiating the IOTHUB_CLIENT_LL_UPLOADTOBLOB_HANDLE fails then IoTHubClient_LL_CreateWithTransport shall fail and return NULL. ]*/
-                LogError("unable to determine the transport IoTHub name");
-                IoTHubClient_Auth_Destroy(result->authorization_module);
-                free(result);
-                result = NULL;
+                setTransportProtocol(result, (TRANSPORT_PROVIDER*)client_config->protocol());
+                if ((result->transportHandle = result->IoTHubTransport_Create(&lowerLayerConfig)) == NULL)
+                {
+                    /*Codes_SRS_IOTHUBCLIENT_LL_02_007: [If the underlaying layer _Create function fails them IoTHubClient_LL_Create shall fail and return NULL.] */
+                    LogError("underlying transport failed");
+                    destroy_blob_upload_module(result);
+                    tickcounter_destroy(result->tickCounter);
+                    IoTHubClient_Auth_Destroy(result->authorization_module);
+                    STRING_delete(product_info);
+                    free(result);
+                    result = NULL;
+                }
+                else
+                {
+                    /*Codes_SRS_IOTHUBCLIENT_LL_02_008: [Otherwise, IoTHubClient_LL_Create shall succeed and return a non-NULL handle.] */
+                    result->isSharedTransport = false;
+                    config = client_config;
+                }
             }
             else
             {
-                const char* hostname = STRING_c_str(transport_hostname);
-                /*Codes_SRS_IOTHUBCLIENT_LL_02_096: [ IoTHubClient_LL_CreateWithTransport shall create the data structures needed to instantiate a IOTHUB_CLIENT_LL_UPLOADTOBLOB_HANDLE. ]*/
-                /*the first '.' says where the iothubname finishes*/
-                const char* whereIsDot = strchr(hostname, '.');
-                if (whereIsDot == NULL)
+                result->transportHandle = device_config->transportHandle;
+                setTransportProtocol(result, (TRANSPORT_PROVIDER*)device_config->protocol());
+
+                transport_hostname = result->IoTHubTransport_GetHostname(result->transportHandle);
+                if (transport_hostname == NULL)
                 {
                     /*Codes_SRS_IOTHUBCLIENT_LL_02_097: [ If creating the data structures fails or instantiating the IOTHUB_CLIENT_LL_UPLOADTOBLOB_HANDLE fails then IoTHubClient_LL_CreateWithTransport shall fail and return NULL. ]*/
-                    LogError("unable to determine the IoTHub name");
+                    LogError("unable to determine the transport IoTHub name");
                     IoTHubClient_Auth_Destroy(result->authorization_module);
+                    STRING_delete(product_info);
                     free(result);
                     result = NULL;
                 }
                 else
                 {
+                    const char* hostname = STRING_c_str(transport_hostname);
                     /*Codes_SRS_IOTHUBCLIENT_LL_02_096: [ IoTHubClient_LL_CreateWithTransport shall create the data structures needed to instantiate a IOTHUB_CLIENT_LL_UPLOADTOBLOB_HANDLE. ]*/
-                    IoTHubName = (char*) malloc(whereIsDot - hostname + 1);
-                    if (IoTHubName == NULL)
+                    /*the first '.' says where the iothubname finishes*/
+                    const char* whereIsDot = strchr(hostname, '.');
+                    if (whereIsDot == NULL)
                     {
                         /*Codes_SRS_IOTHUBCLIENT_LL_02_097: [ If creating the data structures fails or instantiating the IOTHUB_CLIENT_LL_UPLOADTOBLOB_HANDLE fails then IoTHubClient_LL_CreateWithTransport shall fail and return NULL. ]*/
-                        LogError("unable to malloc");
+                        LogError("unable to determine the IoTHub name");
                         IoTHubClient_Auth_Destroy(result->authorization_module);
+                        STRING_delete(product_info);
                         free(result);
                         result = NULL;
                     }
                     else
                     {
-                        const char* IotHubSuffix = whereIsDot + 1;
-                        (void)memcpy(IoTHubName, hostname, whereIsDot - hostname);
-                        IoTHubName[whereIsDot - hostname ] = '\0';
+                        /*Codes_SRS_IOTHUBCLIENT_LL_02_096: [ IoTHubClient_LL_CreateWithTransport shall create the data structures needed to instantiate a IOTHUB_CLIENT_LL_UPLOADTOBLOB_HANDLE. ]*/
+                        IoTHubName = (char*)malloc(whereIsDot - hostname + 1);
+                        if (IoTHubName == NULL)
+                        {
+                            /*Codes_SRS_IOTHUBCLIENT_LL_02_097: [ If creating the data structures fails or instantiating the IOTHUB_CLIENT_LL_UPLOADTOBLOB_HANDLE fails then IoTHubClient_LL_CreateWithTransport shall fail and return NULL. ]*/
+                            LogError("unable to malloc");
+                            IoTHubClient_Auth_Destroy(result->authorization_module);
+                            STRING_delete(product_info);
+                            free(result);
+                            result = NULL;
+                        }
+                        else
+                        {
+                            const char* IotHubSuffix = whereIsDot + 1;
+                            (void)memcpy(IoTHubName, hostname, whereIsDot - hostname);
+                            IoTHubName[whereIsDot - hostname] = '\0';
 
-                        actual_config.deviceId = device_config->deviceId;
-                        actual_config.deviceKey = device_config->deviceKey;
-                        actual_config.deviceSasToken = device_config->deviceSasToken;
-                        actual_config.iotHubName = IoTHubName;
-                        actual_config.iotHubSuffix = IotHubSuffix;
-                        actual_config.protocol = NULL; /*irrelevant to IoTHubClient_LL_UploadToBlob*/
-                        actual_config.protocolGatewayHostName = NULL; /*irrelevant to IoTHubClient_LL_UploadToBlob*/
+                            actual_config.deviceId = device_config->deviceId;
+                            actual_config.deviceKey = device_config->deviceKey;
+                            actual_config.deviceSasToken = device_config->deviceSasToken;
+                            actual_config.iotHubName = IoTHubName;
+                            actual_config.iotHubSuffix = IotHubSuffix;
+                            actual_config.protocol = NULL; /*irrelevant to IoTHubClient_LL_UploadToBlob*/
+                            actual_config.protocolGatewayHostName = NULL; /*irrelevant to IoTHubClient_LL_UploadToBlob*/
 
-                        config = &actual_config;
+                            config = &actual_config;
 
-                        /*Codes_SRS_IOTHUBCLIENT_LL_02_008: [Otherwise, IoTHubClient_LL_Create shall succeed and return a non-NULL handle.] */
-                        result->isSharedTransport = true;
+                            /*Codes_SRS_IOTHUBCLIENT_LL_02_008: [Otherwise, IoTHubClient_LL_Create shall succeed and return a non-NULL handle.] */
+                            result->isSharedTransport = true;
+                        }
                     }
                 }
             }
-        }
-
-        if (result != NULL)
-        {
-            if (create_blob_upload_module(result, config) != 0)
+            if (result != NULL)
             {
-                LogError("unable to create blob upload");
-                IoTHubClient_Auth_Destroy(result->authorization_module);
-                free(result);
-                result = NULL;
-            }
-            else
-            {
-                if ( (result->tickCounter = tickcounter_create()) == NULL)
+                if (create_blob_upload_module(result, config) != 0)
                 {
-                    LogError("unable to get a tickcounter");
-                    destroy_blob_upload_module(result);
+                    LogError("unable to create blob upload");
                     IoTHubClient_Auth_Destroy(result->authorization_module);
+                    STRING_delete(product_info);
                     free(result);
                     result = NULL;
                 }
                 else
                 {
-                    /*Codes_SRS_IOTHUBCLIENT_LL_02_004: [Otherwise IoTHubClient_LL_Create shall initialize a new DLIST (further called "waitingToSend") containing records with fields of the following types: IOTHUB_MESSAGE_HANDLE, IOTHUB_CLIENT_EVENT_CONFIRMATION_CALLBACK, void*.]*/
-                    DList_InitializeListHead(&(result->waitingToSend));
-                    DList_InitializeListHead(&(result->iot_msg_queue));
-                    DList_InitializeListHead(&(result->iot_ack_queue));
-                    result->messageCallback.type = CALLBACK_TYPE_NONE;
-                    result->lastMessageReceiveTime = INDEFINITE_TIME;
-                    result->data_msg_id = 1;
-
-                    IOTHUB_DEVICE_CONFIG deviceConfig;
-                    deviceConfig.deviceId = config->deviceId;
-                    deviceConfig.deviceKey = config->deviceKey;
-                    deviceConfig.deviceSasToken = config->deviceSasToken;
-
-                    /*Codes_SRS_IOTHUBCLIENT_LL_17_008: [IoTHubClient_LL_Create shall call the transport _Register function with a populated structure of type IOTHUB_DEVICE_CONFIG and waitingToSend list.] */
-                    if ((result->deviceHandle = result->IoTHubTransport_Register(result->transportHandle, &deviceConfig, result, &(result->waitingToSend))) == NULL)
+                    if ((result->tickCounter = tickcounter_create()) == NULL)
                     {
-                        LogError("Registering device in transport failed");
+                        LogError("unable to get a tickcounter");
+                        destroy_blob_upload_module(result);
                         IoTHubClient_Auth_Destroy(result->authorization_module);
-                        result->IoTHubTransport_Destroy(result->transportHandle);
-                        destroy_blob_upload_module(result);
-                        tickcounter_destroy(result->tickCounter);
+                        STRING_delete(product_info);
                         free(result);
                         result = NULL;
                     }
                     else
                     {
-                        /*Codes_SRS_IOTHUBCLIENT_LL_02_042: [ By default, messages shall not timeout. ]*/
-                        result->currentMessageTimeout = 0;
-                        result->current_device_twin_timeout = 0;
-                        /*Codes_SRS_IOTHUBCLIENT_LL_25_124: [ `IoTHubClient_LL_Create` shall set the default retry policy as Exponential backoff with jitter and if succeed and return a `non-NULL` handle. ]*/
-                        if (IoTHubClient_LL_SetRetryPolicy(result, IOTHUB_CLIENT_RETRY_EXPONENTIAL_BACKOFF_WITH_JITTER, 0) != IOTHUB_CLIENT_OK)
+                        /*Codes_SRS_IOTHUBCLIENT_LL_02_004: [Otherwise IoTHubClient_LL_Create shall initialize a new DLIST (further called "waitingToSend") containing records with fields of the following types: IOTHUB_MESSAGE_HANDLE, IOTHUB_CLIENT_EVENT_CONFIRMATION_CALLBACK, void*.]*/
+                        DList_InitializeListHead(&(result->waitingToSend));
+                        DList_InitializeListHead(&(result->iot_msg_queue));
+                        DList_InitializeListHead(&(result->iot_ack_queue));
+                        result->messageCallback.type = CALLBACK_TYPE_NONE;
+                        result->lastMessageReceiveTime = INDEFINITE_TIME;
+                        result->data_msg_id = 1;
+                        result->product_info = product_info;
+
+                        IOTHUB_DEVICE_CONFIG deviceConfig;
+                        deviceConfig.deviceId = config->deviceId;
+                        deviceConfig.deviceKey = config->deviceKey;
+                        deviceConfig.deviceSasToken = config->deviceSasToken;
+                        deviceConfig.authorization_module = result->authorization_module;
+
+                        /*Codes_SRS_IOTHUBCLIENT_LL_17_008: [IoTHubClient_LL_Create shall call the transport _Register function with a populated structure of type IOTHUB_DEVICE_CONFIG and waitingToSend list.] */
+                        if ((result->deviceHandle = result->IoTHubTransport_Register(result->transportHandle, &deviceConfig, result, &(result->waitingToSend))) == NULL)
                         {
-                            LogError("Setting default retry policy in transport failed");
-                            result->IoTHubTransport_Unregister(result->deviceHandle);
+                            LogError("Registering device in transport failed");
                             IoTHubClient_Auth_Destroy(result->authorization_module);
                             result->IoTHubTransport_Destroy(result->transportHandle);
                             destroy_blob_upload_module(result);
                             tickcounter_destroy(result->tickCounter);
+                            STRING_delete(product_info);
                             free(result);
                             result = NULL;
                         }
+                        else
+                        {
+                            /*Codes_SRS_IOTHUBCLIENT_LL_02_042: [ By default, messages shall not timeout. ]*/
+                            result->currentMessageTimeout = 0;
+                            result->current_device_twin_timeout = 0;
+                            /*Codes_SRS_IOTHUBCLIENT_LL_25_124: [ `IoTHubClient_LL_Create` shall set the default retry policy as Exponential backoff with jitter and if succeed and return a `non-NULL` handle. ]*/
+                            if (IoTHubClient_LL_SetRetryPolicy(result, IOTHUB_CLIENT_RETRY_EXPONENTIAL_BACKOFF_WITH_JITTER, 0) != IOTHUB_CLIENT_OK)
+                            {
+                                LogError("Setting default retry policy in transport failed");
+                                result->IoTHubTransport_Unregister(result->deviceHandle);
+                                IoTHubClient_Auth_Destroy(result->authorization_module);
+                                result->IoTHubTransport_Destroy(result->transportHandle);
+                                destroy_blob_upload_module(result);
+                                tickcounter_destroy(result->tickCounter);
+                                STRING_delete(product_info);
+                                free(result);
+                                result = NULL;
+                            }
+                        }
                     }
                 }
             }
-        }
-        if (IoTHubName)
-        {
-            free(IoTHubName);
+            if (IoTHubName)
+            {
+                free(IoTHubName);
+            }
+            if (transport_hostname)
+            {
+                STRING_delete(transport_hostname);
+            }
         }
     }
     return result;
@@ -763,6 +818,7 @@
 #ifndef DONT_USE_UPLOADTOBLOB
         IoTHubClient_LL_UploadToBlob_Destroy(handleData->uploadToBlobHandle);
 #endif
+        STRING_delete(handleData->product_info);
         free(handleData);
     }
 }
@@ -1478,6 +1534,27 @@
             handleData->currentMessageTimeout = *(const tickcounter_ms_t*)value;
             result = IOTHUB_CLIENT_OK;
         }
+        else if (strcmp(optionName, OPTION_PRODUCT_INFO) == 0)
+        {
+            /*Codes_SRS_IOTHUBCLIENT_LL_10_033: [repeat calls with "product_info" will erase the previously set product information if applicatble. ]*/
+            if (handleData->product_info != NULL)
+            {
+                STRING_delete(handleData->product_info);
+                handleData->product_info = NULL;
+            }
+
+            /*Codes_SRS_IOTHUBCLIENT_LL_10_035: [If string concatenation fails, `IoTHubClient_LL_SetOption` shall return `IOTHUB_CLIENT_ERRROR`. Otherwise, `IOTHUB_CLIENT_OK` shall be returned. ]*/
+            handleData->product_info = make_product_info((const char*)value);
+            if (handleData->product_info == NULL)
+            {
+                LogError("STRING_construct_sprintf failed");
+                result = IOTHUB_CLIENT_ERROR;
+            }
+            else
+            {
+                result = IOTHUB_CLIENT_OK;
+            }
+        }
         else
         {
 
@@ -1516,6 +1593,28 @@
     return result;
 }
 
+IOTHUB_CLIENT_RESULT IoTHubClient_LL_GetOption(IOTHUB_CLIENT_LL_HANDLE iotHubClientHandle, const char* optionName, void** value)
+{
+    IOTHUB_CLIENT_RESULT result;
+
+    if ((iotHubClientHandle == NULL) || (optionName == NULL) || (value == NULL))
+    {
+        result = IOTHUB_CLIENT_INVALID_ARG;
+        LogError("invalid argument iotHubClientHandle(%p); optionName(%p); value(%p)", iotHubClientHandle, optionName, value);
+    }
+    else if (strcmp(optionName, OPTION_PRODUCT_INFO) == 0)
+    {
+        result = IOTHUB_CLIENT_OK;
+        *value = iotHubClientHandle->product_info;
+    }
+    else
+    {
+        result = IOTHUB_CLIENT_INVALID_ARG;
+        LogError("invalid argument (%s)", optionName);
+    }
+    return result;
+}
+
 IOTHUB_CLIENT_RESULT IoTHubClient_LL_SetDeviceTwinCallback(IOTHUB_CLIENT_LL_HANDLE iotHubClientHandle, IOTHUB_CLIENT_DEVICE_TWIN_CALLBACK deviceTwinCallback, void* userContextCallback)
 {
     IOTHUB_CLIENT_RESULT result;