Today I presented a session as part of Microsoft Australia’s Virtual AppFest, where I covered the basics of the networking stack in WinRT for both Windows Phone and Windows 8. As part of the session I took a look at Bluetooth, NFC, and WIFI direct – but before discussing any of these technologies I always feel it’s worth reviewing the basics of network socket support. In this session I touched on both StreamSockets (TCP) and DatagramSockets (UDP) and one of the demo’s I most enjoy showing off it using the socket APIs to connect back to my Samsung SmartTV.

The Samsung SmartTV, like many devices in most modern day homes has access to the local network and exposes itself as a DLNA device, and subsequantly a UPNP device. The great thing about the Samsung TV’s is they have a whole range of UPNP methods exposed, and not just the complicated media functionality offered in the DLNA spec, allowing me to do things such as change the volume, channel, and even access the in-built browser. The idea being developers can build companion apps for the device to compliment the remote control.

So back to socket support – the primary reason I choose the TV as my demo of choice is because UPNP and the discovery protocol SSDP is a combination of UDP and TCP requests using principals most developers understand (HTTP, and SOAP-style service requests). 

The first thing in particular I wanted to demonstrate in the WinRT API’s was how to reach out across your network and find any UDP/DLNA compatible devices using the Service discovery protocol (SSDP).  SSDP is actually a fairly basic mechanism, and simply requires the device to join a multicast UDP session on port 1900 and send out a simple payload which looks something like :

M-SEARCH * HTTP/1.1
HOST: 239.255.255.250:1900
ST:upnp:rootdevice
MAN:ssdp:discover
MX:3

Other UPNP devices on the same broadcast network will receive this packet and will respond accordingly with a response similar to:

HTTP/1.1 200 OK
Cache-Control: max-age=300
Date: Tue, 13 Nov 2012 18:33:35 GMT
Ext:
Location:
http://10.1.1.1:1780/WFADevice.xml
Server: POSIX UPnP/1.0 UPnP Stack/estimation 1.00
ST: upnp:rootdevice
USN: uuid:3de2a802-636b-d9fc-5f5f-9d9c9dbfb61b::upnp:rootdevice

 

You’ll see that both the request and response use basic HTTP header information, being passed over UDP.

From here we can then make a request to the service metadata using a simple HTTP call. This file is the equivalent of a SOAP WSDL file, and can be parsed as XML. Inside we have a whole host of different data including the service description URIs which can be called in a similar fashion. All of this is achieve in this amount of code:

 public class SSDPClient
    {
        public event EventHandler<DeviceFoundEventArgs> DeviceFound;

        public async void SearchForDevices()
        {
            var socket = new DatagramSocket();
            socket.MessageReceived += async (sender, args) =>
                                                {
                                                    DataReader reader = args.GetDataReader();

                                                    uint count = reader.UnconsumedBufferLength;
                                                    string data = reader.ReadString(count);
                                                    var response = new Dictionary<string, string>();
                                                    foreach (
                                                        string x in
                                                            data.Split(new[] {"rn", "n"}, StringSplitOptions.None))
                                                    {
                                                        if (x.Contains(":"))
                                                        {
                                                            string[] strings = x.Split(':');
                                                            response.Add(strings[0].ToLower(), x.Remove(0, strings[0].Length + 1));
                                                        }
                                                    }

                                                    Device device = await GetXml(response);
                                                    Debug.WriteLine("Device found");
                                                    if (DeviceFound != null)
                                                        DeviceFound(this, new DeviceFoundEventArgs(device));
                                                };
            IOutputStream stream = await socket.GetOutputStreamAsync(new HostName("239.255.255.250"), "1900");


            const string message = "M-SEARCH * HTTP/1.1rn" +
                                   "HOST: 239.255.255.250:1900rn" +
                                   "ST:upnp:rootdevicern" +
                                   "MAN:"ssdp:discover"rn" +
                                   "MX:3rnrn";

            var writer = new DataWriter(stream) {UnicodeEncoding = UnicodeEncoding.Utf8};
            writer.WriteString(message);
            await writer.StoreAsync();
        }


        private async Task<Device> GetXml(Dictionary<string, string> headers)
        {
            if (headers.ContainsKey("location"))
            {
                WebRequest request = WebRequest.Create(new Uri(headers["location"]));

                var r = (HttpWebResponse) await request.GetResponseAsync();
                if (r.StatusCode == HttpStatusCode.OK)
                {
                    try
                    {
                        var ser = new XmlSerializer(typeof (Device));
                        return (Device) ser.Deserialize(r.GetResponseStream());
                    }
                    catch (InvalidOperationException ex)
                    {
                        Debug.WriteLine(ex.Message);
                        return null;
                    }
                }

                throw new Exception("Cannot connect to service");
            }

            throw new Exception("No service Uri defined");
        }
    }

For me this project is still a work in progress, and I’ll continue to add to the API to allow methods to be called, and also expose some more DLNA functionality. I’ve uploaded the full source code to: https://github.com/lewisbenge/WinRT-UPnp-Demo

Tagged on:                         

One thought on “Device discovery (SSDP) in Windows 8 and WinRT

  • February 19, 2013 at 12:01 am
    Permalink

    Hello i tryed to implement your code to Windows 8 app which is based on javascript but i get exception in this line

    IOutputStream stream = await socket.GetOutputStreamAsync(new HostName(“239.255.255.250″), “1900”);

    Please could you give me some advice how to deal with it ? thx Miko

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>