Tuesday, December 6, 2016

Writing GPS coordinates on an image from OneDrive using Azure Functions

Let's take a look over Azure Functions, from a developer perspective. In this post we will write an Azure Function that adds the GPS coordinates of a picture as watermark.

If you want to jump to the code, without any other explanations, than feel free to check the GitHub repository - https://github.com/vunvulear/Stuff/tree/master/Azure/azure-function-extractgpslocation-onedrive-drawwatermark 

What is Azure Functions?
The best explanation that I can give is Azure Functions are the AWS Lambda in Azure world. They allow us to execute code in a serverless way, without thinking about where you host the code, you just write the code and run it as Azure Functions.
There are many things that we can say about Azure Functions, the most important things for me are:
  • You can write code in different languages (C#, Node.JS, PHP, Python, Bash, Batch, PowerShell)
  • Multiple connectors and triggers, from queues to Blob Storage and Google Drive, all of them are supported
  • Flexible and transparent charging model, you pay only what you use
  • Real time processing
  • Refs to external libraries (e.g. NuGet) are supported

Our mission
The mission is to write an Azure Functions that:
  • Is triggered each time when a new image is added to a OneDrive folder
  • Extract the GPS coordinates from image attributes (if are available)
  • Draw GPS coordinates on the image
  • Save image in another folder in OneDrive 
First step  - Create the Azure Function
The first step is to create an Azure Function. This can be done from Azure Portal, a nice documentation related to it can be found here. We can create a 'GenericWebhookCSharp' function initially and we will delete the parts that we don't need.
Once we done this, we will need to go to the Integrate tab and specify our trigger. Delete all the Triggers and Outputs that are already defined. We will define them again.

Triggers and binding
In our case we will need use External File and in the connection field we will create a new one that will point to OneDrive. It is important to know that even if your access credentials are requests, they are not stored in Azure Functions. They are stored are API Connections and the same connection can be used in multiple functions if needed. This trigger will be called in the moment when a new file will be copied in the given path. As output, we will need to use the same External File. Now, we can reuse the same connection, that we created for the trigger - onedrive_ONEDRIVE. 
Now, let's take a look on the binding. 
{
  "bindings": [
    {
      "type": "apiHubFileTrigger",
      "name": "inputFile",
      "path": "Imagini/Peliculă/{filename}",
      "connection": "onedrive_ONEDRIVE",
      "direction": "in"
    },
    {
      "type": "apiHubFile",
      "name": "outputFile",
      "path": "Imagini/Peliculă/pictureswithgpswatermark/{rand-guid}.jpg",
      "connection": "onedrive_ONEDRIVE",
      "direction": "out"
    }
  ],
  "disabled": false
}

The first binding specifies the trigger. As we can see the direct is in and the connection is done to OneDrive. The 'path' is relative to our OneDrive. In my case, the folder that is monitored is 'Imagini/Peliculă'. {filename} is the file parameter. In Azure Function, we will refer to the input file using the 'name' attribute - inputFile.
Similar to it, we have the output, that is written in ""Imagini/Peliculă/pictureswithgpswatermark" folder. '{rand-guid}' is used to generate a random name for each image. 

Writing an empty function
As we can see below, we have inputFile and outputFile as parameters to the Run method. This method is the entry point each time when trigger runs. If you need to write something to logs, you can use TraceWriter with success, that needs to be specified as parameter.
public static void Run(Stream inputFile, Stream outputFile, TraceWriter log)
{
     log.Info("Image Process Starts"); 

     log.Info("Image Process Ends");
}

We can define our own class, reference existing libraries or nuget packages. To be able to work with files and this type of binding we'll need to add a reference to ApiHub, otherwise an encrypted error will be throw:
Exception while executing function: Functions.SaasFileTriggerCSharp1. Microsoft.Azure.WebJobs.Host: One or more errors occurred. Exception binding parameter 'input'. Microsoft.Azure.ApiHub.Sdk

The reference is added as a normal using, but specifies to Azure Functions to load the assembly from his shared repository.
#r "Microsoft.Azure.WebJobs.Extensions.ApiHub" //

Save and Run
Before hitting the Save button, make sure that Logs window is visible. This is useful, because each time when you hit Save button, the function is compiled. Any error during the build are displayed in the Logs window.

From now one, each time when you copy/upload a new file in your OneDrive folder, your function will be called automatically. In the logs you shall be able to see the output logs.

Reading the GPS location
To be able to read the GPS location from images, we will use ExifLib. This nuget package allow us easily to read GPS information. To be able to push a Nuget package, we will need to open project.json and add a dependence to our nuget package. Below you can find how the JSON should look like. I also added the Nuget package that will be used later on to draw the coordinates on the image
{
  "frameworks": {
    "net46":{
      "dependencies": {
        "ExifLib": "1.7.0.0",
        "System.Drawing.Primitives": "4.3.0"
      }
    }
   }
}

In the moment when you click Save button, you will see that the function is compiled and the Nuget packages and all package dependencies are downloaded.
When we run the code that extracts the GPS location we shall take into account the cases when an image doesn't has this information.
private static string GetCoordinate(Stream image, TraceWriter log)
{
    log.Info("Extract location information");
    ExifReader exifReader = new ExifReader(image);
    double[] latitudeComponents;
    exifReader.GetTagValue(ExifTags.GPSLatitude, out latitudeComponents);
    double[] longitudeComponents;
    exifReader.GetTagValue(ExifTags.GPSLongitude, out longitudeComponents);

    log.Info("Prepare string content");
    string location = string.Empty;
    if (latitudeComponents == null ||
        longitudeComponents == null)
    {
        location = "No GPS location";
    }
    else
    {
        double latitude = 0;
        double longitude = 0;
        latitude = latitudeComponents[0] + latitudeComponents[1] / 60 + latitudeComponents[2] / 3600;
        longitude = longitudeComponents[0] + longitudeComponents[1] / 60 + longitudeComponents[2] / 3600;

        location = $"Latitude: '{latitude}' | Longitude: '{longitude}'";
    }

    return location;
}

Next step is to call our method from the Run method (string locationText = GetCoordinate(inputFile, log). Once we click on save, the GPS location for each image can be found in the log window.

string locationText = GetCoordinate(inputFile, log);
log.Info($"Text to be written: '{locationText}'");
---- log window ----
2016-12-06T00:06:35.680 Image Process Starts
2016-12-06T00:06:35.680 Extract location information
2016-12-06T00:06:35.680 Prepare string content
2016-12-06T00:06:35.680 Text to be written: 'Latitude: '46.7636219722222' | Longitude: '23.5550620833333''


Write the watermark (coordinates)
The last step is to write the text on the image and copy the image stream to the output. The code is the same code that is required in a console application for the same task.

private static void WriteWatermark(string watermarkContent, Stream originalImage, Stream newImage, TraceWriter log)
{
    log.Info("Write text to picture");
    using (Image inputImage = Image.FromStream(originalImage, true))
    {
        using (Graphics graphic = Graphics.FromImage(inputImage))
        {
            graphic.SmoothingMode = SmoothingMode.HighQuality;
            graphic.InterpolationMode = InterpolationMode.HighQualityBicubic;
            graphic.PixelOffsetMode = PixelOffsetMode.HighQuality;
            graphic.DrawString(watermarkContent, new Font("Tahoma", 100, FontStyle.Bold), Brushes.Red, 200, 200);
            graphic.Flush();

            log.Info("Write to the output stream");
            inputImage.Save(newImage, ImageFormat.Jpeg);
        }
    }
}

Don't forget to reset the cursor position of the inputFile stream before calling the WriteWatermark. This is necessary because reading coordinates will move the cursor from position 0.
In the end, the run method should look like this:
public static void Run(Stream inputFile, Stream outputFile, TraceWriter log)
{
    
    log.Info("Image Process Starts"); 

    string locationText = GetCoordinate(inputFile, log);
    log.Info($"Text to be written: '{locationText}'");

    // Reset position. After Exif operations the cursor location is not on position 0 anymore;
    inputFile.Position = 0;

    WriteWatermark(locationText, inputFile, outputFile, log);

    log.Info("Image Process Ends");
}

Final output
The final output, of our function shall be an image that was the coordinates written with RED, see below.

Conclusion
In this post we discovered how we can write an Azure Function that adds as watermark the GPS coordinates of the location where the picture was taken. In the next post we will discover how we can do the same think from Visual Studio directly and how we can integrate CI to Azure Function.

Full code can be found on GitHub - https://github.com/vunvulear/Stuff/tree/master/Azure/azure-function-extractgpslocation-onedrive-drawwatermark 

Thursday, December 1, 2016

ITCamp Winter Community Event (Cluj-Napoca), December 7

Only for Romanian fellows.
În luna Decembrie, ITCamp organizează un nou eveniment pentru profesioniștii IT din Cluj-Napoca. Evenimentul urmează să aibă loc pe data de 07 Decembrie, la sediul Endava (United Business Center, lângă Iulius Mall).
Participarea la eveniment este gratuită. Mulțumim Endava pentru găzduire.
Te poți înregistra folosind oricare din următoarele site-ur:

Program:
17:45 – 18:15 – Welcoming & Registration 
18:15 – 19:00 – Systems Development in a Changing World – Eoin Woods
19:00 – 19:15 – Coffe Break
19:15 – 20:00 – Empower your Raspberry PI using Azure IoT Suite – Radu Vunvulea
20:00 – 21:00 – Augmented Reality in Industrial Predictive Maintenance, including HoloLens – Tibor Molnar
21:00 – 22:00 – Networking

Descrierea sesiunilor:
Systems Development in a Changing World (Eoin Woods)
The way we build systems is changing. From our history of monolithic systems, then distributed systems, to Internet connected systems, we are now entering the era of cloud-hosted, microservice based, pay-for-usage system development. What does the history of software development tell us about the challenges of this new environment? And how does our approach to building systems need to evolve in order to meet them?
Empower your Raspberry PI using Azure IoT Suite (Radu Vunvulea)
What can you do with smart devices nowadays? In the next 45 minutes we will take a look on what we can do With Raspberry PI and how this device can become the brain of an intelligent house or office. The session will contain a lot of demos explaining what we can do to empower this smart device with the IoT Intelligence on top of Azure. We will also use NodeJs and C++ beside C# code.
Augmented Reality in Industrial Predictive Maintenance, including HoloLens (Tibor Molnar)

Industry 4.0 comes with interconnected machines using IoT, cloud computing and machine learning (beside many other things). We at iQuest are working on an end to end IoT solution architecture which includes gateways for devices, IoT hubs, maintenance case predictions, real time business intelligence and augmented reality support for devices maintenance and predictive maintenance. Additionally, we are testing Augmented Reality to provide a better experience and support for field engineers doing devices maintenance work. This session will present high level an example IoT architecture, then will focus on details regarding how to implement holographic and object augmentation experiences in devices maintenance using Microsoft Hololens, Unity, Visual Studio and the PTC Vuforia platform.

Sponsor:
Endava

Wednesday, November 30, 2016

Why Device Twins from Azure IoT Hub are important for us

This post is focusing on Device Twins that are available on Azure IoT Hub.
If it is your first time when you hear about Azure IoT Hub, than you should know that Azure IoT Hub is a powerfull serviced offered by Azure that can connect, manage and monitor your devices. More information about it can be found on Azure site

Why Device Twins?
When we are working with devices, we will find all the time information that is specific to individual device. Information like device serial number, customer information, location, device configuration is part of this type of information.
In an ideal world, we should have a unique location where all this data is stored. No custom code of functionality should be developed by us. Azure IoT Hub is offering this to us through Device Twins.

What are Device Twins?
It’s a collection of properties that specific for each individual device. Device twins contains 3 types of structure (where the last two are similar, but have different roles)

  • Tags: Device specific information that resides on the Azure IoT Hub and never reach the device
  • Desire Properties: Properties set on Azure IoT Hub (by backend) and are delivered to the device
  • Reported Properties: Properties set by Device and are delivered to Azure IoT Hub (to backend)

You might find odd that there are 2 types of properties/structures in device twin – tags and desired/reported properties. From the usability point of view, this is useful and handy.
Tags are used when you want to store information related to a device on the backend without sending it to the device. A good example is customer ID or device location. There is no need to send this data to device if the device doesn’t need this data.
In contrast, desired/reported properties are all the time send/received from device. Properties like device configuration, current status or different counters can be found inside them.

Format
All information related to Device Twins is stored in JSON format. Tags, Desired Properties and Reported Properties are stored separately in the same JSON, as different nodes.
"tags": {
  "$etag": "32423",
  "cliendId": "2323232",
  "location":  {
    "country": "Romania"
    "city": "Cluj-Napoca",
    "zipCode": "400001"
  }
},
"properties": {
  "desired": {
    "logsFrequency": 1000ms,
    "status": 1,
    "$metadata" : {...},
    "$version": 4
  },
  "reported": {
    "logsFrequency": 1000ms,
    "status": 1,
    "$metadata" : {...},
    "$version": 54
  }
}

Not all the time, the desired and reported properties will have the same value, we will see why later on.
The keys for properties are case-sensitive and can contain most of the characters (except ‘ ‘, ‘.’, ‘$’, C0 and C1 segments from Unicode control chars).
The values of properties can be any JSON type like string, int, boolean and even object. When you are playing with object you shall remember that the maximum depth of a property is 5. Arrays are not supported, but can be mapped easily as multiple properties.


Tracking of Device Twins
We can see Device Twin as a clone of our Device in the cloud. We can know all the time what is the current state of the device and what configuration was received by the device and what not.
For tracking, we will find under each type of Device Twin a node called $metadata, where information related to when was the last update of each property can be found. All the properties that are added by Azure IoT Hub are beginning with ‘$’ (‘$lastUpaded’, ‘version’).
The $version property is automatically added to each property structure like desired properties. The value of version is incremented with 1 each time when a change is done (a new value is added, updated or removed). Don’t forget that the version value is independently incremented for each structure. This means that the version of desired properties can be at 1400 and the reported property version can be at 4. There is no connection between them.
"properties": {
  "desired": {
    "logsFrequency": 1000ms,
    "status": 1,
    "$metadata" : {...},
    "$version": 3
  },
  "reported": {
    "logsFrequency": 5000ms,
    "status": 1,
    "securityLevel": 9
    "$metadata" : {...},
    "$version": 94
  }
}

In the above example we can obsever that the logsFrequency property value is not the same. Also, we have additional one new property in the reported properties - securityLevel.
The maximum size of properties value is 512 bytes. Overall, the maximum size of each structure can be maximum 8KB. 8KB for tags, 8KB for desired properties and 8KB for reported properties.

Actions
From the backend, we can retrieve the device twins based on device ID. Once we get this data, we can add or updates tags and desired properties. The reported properties can be only read on the backend.
At device level, we can retrieve the device twin for our current device, access desired properties and add or update the reported properties. At device level, we can device to be notified when a desired property is update or added (so called observe desired properties). Tags are not accessible on device.
To remove an existing property from tags, desired properties or reported properties we need to set the value to null.
"properties": {
  "desired": {
    "logsFrequency": 1000ms,
    "status": 1,
    "securityLevel": null
    "$metadata" : {...},
    "$version": 4
  },
  "reported": {
    "logsFrequency": 5000ms,
    "status": 1,
    "securityLevel": 0
    "$metadata" : {...},
    "$version": 94
  }
}

Communication and reconciliation
If we are on backend, the story is simple. We retrieve from Azure IoT Hub the last know device twins, with all the property’s values that are available. There is no magic required on this side.
On the device, the story is a little different. First of all, the communication for device twins is done over the same TLS connection. When a desired value is changed, the notification is sent to the device.
But we shall remember that notification is not queened in the command queue for C2D. It means that even if we have commands in the queue for that specific device, the desired properties update notification will be received by the device – using a different MQTT topic.
This is great, because we can use device twins when we want to send an action with a very high priority – like stopping the device.
The device twin notification is not persisted by Azure IoT Hub. If our device is offline when the notification is send, the notification will not reach the device. It means, that once a device state changes from offline to online, on the device we need to retrieve the desired properties and check the version of it. If the version is higher than the one that we have on the device, it means that one or more properties value changed and we need to check the values.

Conclusion
Device Twins are a powerful feature of Azure IoT Hub. If they are used smart, can change the way how you are using and communicate with your backend. Before jumping to code, try to understand them and what are the main characteristic of each of them.

Remember:

  1. Tags can be set, read and accessed only by backend 
  2. Reported Properties are set by device can be read by backend
  3. Desired Properties are set by backend and can be read by backend
  4. Use version and lastUpdated properties to detect updates when necessary


Monday, November 28, 2016

Initial RDP connection on a remote machine over Relay Hybrid Connection and Web Sockets - Azure Relay

In one of my latest post I presented how we can establish a PuTTy connection between two different machines through Azure Relay Hybrid Connection using web sockets.
Today, we will take a look over a Node.JS sample that initiate the tunnel that can be used for an RDP connection between two machines over Azure Relay.

Context
Before jumping to the solution let's take a look on RDP Connection Sequence (https://msdn.microsoft.com/en-us/library/cc240452.aspx?f=255&MSPPError=-2147217396).
As we can see in the connection sequence flow, there are multiple connections that are open during a RDP session. The initial session that is open between client and server is used for the initial handshake and credentials validation. Once the credential are validated, the current connection is closed and other socket connection are open automatically.
In the current sample, we will update the original one that was written for Telnet. The first 5 steps from the flow will be supported, until in the moment when the initial socket connection is close and a new one is open.
More information on what we should do after this steps are presented at the end of the post.

Implementation
The implementation is straightforward and similar with the one that was used for PuTTy connection. There are only some small things that we need to take into account.
GitHub Source code: https://github.com/vunvulear/Stuff/tree/master/Azure/relay-hybrid-connections-rdp-initial

Server.js needs to run on the machine that we want to access. In our code, we will need to open the socket and redirect all the content that is send through Azure Relay Hybrid Connection to the local socket.
function (socket) {
    relaysocket = socket;
    console.log('New connection from client');
    relaysocket.onmessage = function (event) {
      // Send data to local socket (local port)
      myLocalHost.write(event.data);
    };
    relaysocket.on('close', function () {
      console.log('Relay connectin was closed');
    });       
  });

The second step needs to be done on our local socket, where is necessary to redirect the content from our local socket to our web socket.
net.createServer(function(localsocket)
{
  myLocalHost = localsocket;
  myLocalHost.on('data', function(d) {
    relaysocket.send(d);
    myLocalHost.on('error', function(err) {console.log("Socket close: " + err.stack)});
  });
}).listen(localport);

On the other machine, where we run client.js we will do a similar thing. Listen to web socket that is communication with Azure Relay Hybrid Connection and redirect content to local port and redirect all content from the local port to our web socket.
var relayclient = webrelay.relayedConnect(
        webrelay.createRelaySendUri(ns, path),
        webrelay.createRelayToken('http://'+ns, keyrule, key),
        function (socket) {
            // Create local socket to the given port                               
            console.log("Connected to relay")
            relayclient.onmessage = function (event) {
                if(typeof localsocket === "undefined")
                {
                    localsocket = net.connect(sourceport,function(socket)  {
                            console.log("New socket");
                        });
                    localsocket.on('data', function(data) {
                        relayclient.send(data);
                    });
                    localsocket.on('error', function(err) {console.log("Socket close: " + err.stack)});
                }
                localsocket.write(event.data);
            };            
        }
    ); 

Next steps
What if we would like to extend the current solution to be able to do a full RDP connection over Azure Relay Hybrid Connection? There are two clear steps that need to be done.

1. Support multiple connection
We shall extend client.js and server.js to be able to send through the web sockets multiple socket connections. This would required that on one side to mark each package that we send over Azure Relay with a flag that would allow us to know on the other side on what socket we need to redirect the content.

2. Buffering
Even if the solution will work, it is pretty clear that we need a buffering mechanism that is able to stream uniformly all content that is send over Azure Relay. If we would have only one connection open, this would not be necessary. Having multiple open connection that goes over the same web socket, then it is required to have a buffering  mechanism.
Without it, the solution will work, but the connection will not be stable enough.

Conclusion
Yes, it is possible to tunnel a RDP connection over Azure Relay. We have all the functionality and tools available already. The support for multiple connection and buffering are two features that are necessary for any kind of remote connection that we will want to establish.
Once we will do this, we will be able to tunnel a VNC or a FTP connection without any problems.

Monday, November 21, 2016

[Post-Event] ITDays, Cluj-Napoca - Nov. 18-19, 2016

Durring 18th and 19th of November, another edition of ITDays took place in Cluj-Napoca. This was the 4th edition of ITDays. In comparison with other years, this edition was not only bigger, but the quality of speakers and the content was much better. I would even say that ITDays evolved in a good conference where you can find good quality content.
I had a great time and I hope that we will meet next year to.
I was invited at ITDays to talk about Docker on Windows. The most funny thing was that I prepared the slides 3 weeks before the event. During this period Docker team together with Microsoft released some new features for Windows and I had to update my slides and demos.

Title:
Docker + .NET Core Demos
Abstract:
Come to join a 30 minutes demo, where we will discover how we can work with Docker and .NET Core, how we can run Docker on Linux and Windows and what are the most important commands that we shall know. At the end of the session we identify 3 cases when Docker can be our best friend.

[Post-Event] Codecamp Conference Cluj-Napoca - Nov 19, 2016

Last day I was invited to another Codecamp Conference, that took place in Cluj-Napoca. Like other Codecamp Conferences, the event was very big, with more than 1.000 participants and 70 sessions. There were 10 tracks in parallel, so it was pretty hard to decide at  what session you want to join.
It was great to join this conference and I hope that you discovered something new during the conference.
At this event I talked about Azure IoT Hub and how we can use it to connect devices from the field. I had a lot of demos using Raspberry PI 3 and Simplelink SensorTag. Most of the samples were written in C++ and Node.JS and people were impressed that even if we are using Microsoft technologies, we are not limited to C# and .NET. World and Microsoft are changing so fast. Just looking and Azure IoT Hub and new features that were launched and I'm pressed (Jobs, Methods, Device Twin).
On backend my demos covered Stream Analytics, Event Hub, Azure Object Storage and DocumentDB.

Title:
What about Azure IoT Hub
Abstract:
In this session we will take a look on how we can integrate Azure IoT Hub in our solutions. At the begining we will start with a simple scenario that will evolve until we will reach the limits of IoT Hub. Yes, Azure IoT Hub is not perfect, this is way we will take a look on what are the limitations and workarounds for it. If you are working with devices or you want to find more about IoT Hub, this sessesion will unreveal the secrests of Azure IoT Hub.

Slides:



Pictures:





Sunday, November 20, 2016

Connect through telnet on two different computers using Relay Hybrid Connection - Azure Relay

A few days ago I wrote a short post about Relay Hybrid Connection (using Azure Relay), that enable us to establish a secure tunnel connection between two different computers, that can be located anywhere in the world.
The connection is done over HTTPS using web sockets. This means that even if you have firewalls, as long as port 443 is open, you can have a secure connection out of the box.
A short description of Relay Hybrid Connection can be found in the previous post Secure tunnel using Hybrid Connections.

Scope
In today post let's see how we can establish a Telnet connection between two different computers using Relay Hybrid Connection and Node.JS.

Why Telnet?
I decided to make this sample using Telnet, because a connection through Telnet requires a direct connection to a specific port. Having such a connection requires as to redirect content that is send to a a port directly to Azure Relay.
If we can do such a thing, than we can redirect the VNC or RDP port without a problem.

What we need ?
From Azure Relay perspective we need to create a Relay Hybrid Connection. This can be done easily from Azure Portal (see Azure documentation for this). On top of it, we need to write code that redirects traffic from a local port to a web-socket. Once we manage to do this, we only need to configure the web-socket client to connect to our Relay Hybrid Connection.
Two Node.js packages are required (that can be downloaded from NPM):
  • net - It is used to create the socket for the local ports
  • hyco-ws - Wrapper over web-sockets, that helps us to connect to Azure Relay. It is not mandatory, but it is more simple for example to create the connection URL automatically and not manually
You will see later on, that there is an implementation for Server and Client. For such a connection (over web-sockets), it is required that one computer (computer A) to play the role of listener (server). Another computer needs to play the role of client (computer B).
This is because of how web-sockets works and the role of client and server can be switched between computer A and B.

Server implementation (computer A)
In this sample the computer A connects on port 1235. We will need to create a socket that listen on this port.
net.createServer(function(localsocket)
{

}.listen(1235);

Once we done this, we need to create a web-socket connection to our Azure Relay and play the role of the web-socket server (listen to relay server). In the same time when we create this connection, we'll need to register to the event that is trigger when new data is send from Azure Relay. This content needs to be directed to local socket, from where it will be pushed to port 1235.
var wss = websocket.createRelayedServer({
        ...
    }, 
    function (socket) {
        relaysocket = socket;
        relaysocket.onmessage = function (event) {
            // Send data to local socket (local port)
            localsocket.write(event.data);
        };      
        
        relaysocket.send(d);
    });

The last thing is to register to the local socket event that is triggered when new data is send from local computer to our port. Data needs to be redirected to our web socket.
localsocket.on('data', function(d) {
        // Receive data on local socket, redirect it to relay server (web socket)         
        relaysocket.send(d);
    });
And we are done with the server.

Client implementation (computer B)
On computer B, we will use port 1234 as local port. I decided to not use the same port, because in this way we can test our solution using only one computer.
From the implementation perspective, the code is similar. The main difference is the order on what we open and connect first. For the server, we first create the connection to the local socket and only after it we connected to Azure Relay.
In the case of the client, we first need to connect to relay and only after it to connect to the socket. Below you can find the implementation of the client.
client.js
const net = require('net');
const webrelay = require('hyco-ws');

// Relay information
const ns = "rvrelay.servicebus.windows.net";
const path = "a";
const keyrule = "full";
const key = "hdm88wZHtAj412uqSV7IRscJiBKnFcWs3Sw5UFtWQ/o=";

// Local port used to send data
var sourceport = 1234;
var localsocket;
// Create relay connection for client
var relayclient = webrelay.relayedConnect(
        webrelay.createRelaySendUri(ns, path),
        webrelay.createRelayToken('http://'+ns, keyrule, key),
        function (socket) {
            console.log("Connected to relay")
            // Send msg to relay that confirms that cnnection was with success. 
            socket.send("Connection with success")

            // Define action for content received from relay
            relayclient.onmessage = function (event) {
                console.log('Data from relay: ' + event.data);
                // Send data to local socket (port)
                localsocket.write(event.data);
            };

            // Create local socket to the given port                     
            net.createServer(function(socket)
            {
                localsocket = socket;
                localsocket.on('data', function(d) {
                    // Send data from socket to 
                    relayclient.send(d);
                });
                }).listen(sourceport);            
        }
    ); 

As you can see the client and server implementation are very similar. The magic is happening behind the scene on Azure Relay that now supports web sockets.
The server implementation source code can be found at the end of this post.

Running the sample
All source code can be found on Github - https://github.com/vunvulear/Stuff/tree/master/Azure/relay-hybrid-connections-telnet2telnet
Steps:
  1. Update the Azure Relay connection information
  2. If needed, change the port of the client and server
  3. Check that you have Ttelnet installed (if not click here)
  4. Check that you have Node.Js installed (if not click here)
  5. [on server] Open power-shell and run server ('node server.js')
  6. [on server] Open power-shell and run telnet where you need to enter 'localhost 1235'
  7. [on client] Open power-shell and run client ('node client.js')
  8. [on client] Open power-shell and run telnet where you need to enter 'localhost 1234'
  9. And you are done, you have a secure tunnel connection between them. Feel free to write anything in the telnet consoles. The content will be visible in the other telnet window.
Security  
From a security point of view we shall take into consideration two things. 
At transport level, we are connected over a secure connection to Azure Relay (HTTPS/Web Socket). It means the connection is secure and people will not be able to access and see the content that we send through the wire.
At connection level, Azure Relay is using SAS (Shared Access Signatures), that allows us to specify what kind of operation can be done on a specific Azure Relay and for what period of time. It means that without a valid SAS you cannot connect to a relay.

Conclusion
I'm exited to see that we can establish a secure connection, very similar to a VNC connection) between two computers that are in different networks using an out of the box Azure Service. More exciting is that this connection is done over web-sockets, meaning that I can connect from any kind of system and I can use any kind of technology stack (C++, C#, Node.JS, Java, Ruby, Go and so on). 

In the next posts we will see how we can establish a VNC client connectin using Azure Relay and Relay Hybrid Connection. 
Github source code: https://github.com/vunvulear/Stuff/tree/master/Azure/relay-hybrid-connections-telnet2telnet

server.js
const net = require('net');
const websocket = require('hyco-ws');


// Relay information
const ns = "rvrelay.servicebus.windows.net";
const path = "a";
const keyrule = "full";
const key = "hdm88wZHtAj412uqSV7IRscJiBKnFcWs3Sw5UFtWQ/o=";

// Local port where content is redirected (to/from)
var localport = 1235; 
var relaysocket;        
// Create local  socket connection
net.createServer(function(localsocket)
{
    // write to socket a dummy connection
    localsocket.write("Connection with success");
    localsocket.on('data', function(d) {
        // Receive data on local socket, redirect it to relay server (web socket)         
        relaysocket.send(d);
    });

    // Create relay server, only after local socket was open
    var wss = websocket.createRelayedServer(
        {
            // Init listener to relay
            server : websocket.createRelayListenUri(ns, path),
            token: websocket.createRelayToken('http://' + ns, keyrule, key)
        }, 
        function (socket) {
            relaysocket = socket;
            console.log('New connection from client');
            relaysocket.onmessage = function (event) {
                // Send data to local socket (local port)
                localsocket.write(event.data);
                console.log("Send data to local port: " + event.data);
            };
            relaysocket.on('close', function () {
                console.log('Relay connectin was closed');
            });       
        });

        console.log('Ready for new connection');

        wss.on('error', function(err) {
        console.log('error' + err);
        });

        websocket.createRelayListenUri() 

}).listen(localport);