Sending telemetry messages to Azure Services

Microsoft Azure platform supports many messaging services for various purposes: SericeBus, EventHub, EventGrid, IotHub and StorageQueue.

When working in IoT space, IoTHub might probably be the best choice regarding messaging between devices and some other services. Similarly, when working in an integration space, the better option would be to use the ServiceBus. The EventGrid is a higher-level (easier) variation of the ServiceBus (a trivial explanation).

But there is also the EventHub, mainly used to ingress streaming events. Interestingly IoTHub egress (reading) endpoint is nearly the same endpoint used by EventHub. And this is when things sometimes might get complicated.

To send events to EventHub, AMQP protocol and EventHub REST are typically used. When sending events to IoTHub few more protocols can be used: AMQP, MQTT, AMQP through Websocket and MQTT through Websocket.

That means that the device which sends telemetry messages (events) via AMQP or REST does not have to know if it is sending to EventHub or IotHub.

For example, to send the message to IotHub via REST following POST request can be used:

https://iothubname.azure-devices.net/devices/deviceid/messages/events?api-version=2020-03-13

To send the same message to EventHub following POST request can be used:

https://eventhubnamespace.servicebus.windows.net/eventhubname/messages?timeout=60&api-version=2014-01

Similarly, when receiving events, the same code is used. This can be, for example, EventHubConsumerClient or AzureFunction with EventHubTrigger.

In both cases, the natively received event is of type PartitionEvent. However, Azure Function provides different deserialization options, which means that the received event can also be of type string, string[] or something else.

This all is nice, but why should you care about this?

If you work in the IoT space, you typically use IoTHub and do not care about EventHub. However, sometimes you might work with devices that send telemetry messages unidirectionally to another service responsible for collecting messages (events). This service might run in another cloud (not Azure) or any other possible environment. If devices in such scenarios are unidirectional (no messages can be sent to them), then there is no reason to post messages to IoTHub. In such cases, messages can be posted to EventHub. Depending on the number of messages and required performance, EventHub might be more cost-effective than IotHub.

You should carefully take a look at the pricing of both services.

Generating SAS Tokens

One more thing. The code responsible for sending telemetry messages from the device to *Hub should be aware of creating a SAS token if no SDK can be used to cover this task. Please note that SAS tokens for IotHub and EventHub are semantically identical but use a different signature encoding mechanism internally. The following two code snippets show how to create the SAS Token for EventHub and IotHub.

private static string GenerateSasTokenForEventHub(string resourceUri, 
string keyName, string key)
{
      TimeSpan sinceEpoch = DateTime.UtcNow - new DateTime(1970, 1, 1);         
          
      var expiry = Convert.ToString((int)sinceEpoch.TotalSeconds +  
      TimeSpan.FromDays(365 * 10).TotalSeconds);
          
      string stringToSign = WebUtility.UrlEncode(resourceUri) + "\n" + expiry;
           
      HMACSHA256 hmac = new HMACSHA256(Encoding.UTF8.GetBytes(key));
      var signature =   Convert.ToBase64String(
      hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign)));

      string sasToken;
      
      if (keyName != null)
         sasToken = String.Format(CultureInfo.InvariantCulture,         
         "SharedAccessSignature sr={0}&sig={1}&se={2}&skn={3}",
         WebUtility.UrlEncode(resourceUri), 
         WebUtility.UrlEncode(signature), expiry, keyName);
      else
          sasToken = String.Format(CultureInfo.InvariantCulture,   
          "SharedAccessSignature sr={0}&sig={1}&se={2}", 
          WebUtility.UrlEncode(resourceUri), 
          WebUtility.UrlEncode(signature), expiry);

            return sasToken;
        }
        public static string GenerateSasTokenForIotHub(string resourceUri,  
        string policyName, string key, int expiryInSeconds = 3600)
        {
            TimeSpan fromEpochStart = DateTime.UtcNow - new DateTime(1970, 1, 1);
           
            string expiry = 
            Convert.ToString((int)fromEpochStart.TotalSeconds + expiryInSeconds);

            string stringToSign = WebUtility.UrlEncode(resourceUri) + "\n" + expiry;

            HMACSHA256 hmac = new HMACSHA256(Convert.FromBase64String(key));

            string signature = Convert.ToBase64String(
            hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign)));

            string token = String.Format(CultureInfo.InvariantCulture,
            "SharedAccessSignature sr={0}&sig={1}&se={2}",
            WebUtility.UrlEncode(resourceUri), 
            WebUtility.UrlEncode(signature), expiry);

            if (!String.IsNullOrEmpty(policyName))
            {
                token += "&skn=" + policyName;
            }

            return token;
        }

Please notice a difference. SAS Token for IotHub uses Base64 encoding. In contrast, SAS Token for EventHub uses UTF8 encoding. That means the same token generator function cannot be used for both hubs.

The following examples show how to form the resourceUri in both cases.

// Create the SAS Token for IoTHub
var iotHubToken = GenerateSasTokenForIotHub(__aSyNcId_<_jLcgrQWZ__quot;iothubname-devices.net/devices/deviceid", null, "**device key ***");

The resourceUri, in the case of IotHub is dedicated to device defined by deviceId. Therefore, if the device key is used, the argument policyName does not have to be specified because the device has no policy. However, if the general policy “device” or ”iothubowner” is used to generate the token, then the argument policyName must be specified.

// Create the SAS Token for EventHub
var eventHubToken = GenerateSasTokenForEventHub($https://eventhubnamespace.servicebus.windows.net/eventhubname/messages, "send", "**eventhub send key**");

In the case of EventHub, the resourceUri is dedicated to the event hub, which plays a role of a general device, which will receive all events.