Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
UPnPClient.cs
1using System;
2using System.Collections.Generic;
3using System.IO;
4using System.Net;
5using System.Net.Http;
6using System.Net.NetworkInformation;
7using System.Net.Sockets;
8using System.Text;
9using System.Threading.Tasks;
10using System.Xml;
11using Waher.Events;
13
15{
20 public class UPnPClient : CommunicationLayer, IDisposable
21 {
22 private const int ssdpPort = 1900;
23 private const int defaultMaximumSearchTimeSeconds = 10;
24
25 private readonly List<KeyValuePair<UdpClient, IPEndPoint>> ssdpOutgoing = new List<KeyValuePair<UdpClient, IPEndPoint>>();
26 private readonly List<UdpClient> ssdpIncoming = new List<UdpClient>();
27 private bool disposed = false;
28
34 public UPnPClient(params ISniffer[] Sniffers)
35 : base(false, Sniffers)
36 {
37 Dictionary<AddressFamily, bool> GenIncoming = new Dictionary<AddressFamily, bool>();
38 UdpClient Outgoing;
39 UdpClient Incoming;
40
41 foreach (NetworkInterface Interface in NetworkInterface.GetAllNetworkInterfaces())
42 {
43 if (Interface.OperationalStatus != OperationalStatus.Up)
44 continue;
45
46 IPInterfaceProperties Properties = Interface.GetIPProperties();
47 IPAddress MulticastAddress;
48
49 foreach (UnicastIPAddressInformation UnicastAddress in Properties.UnicastAddresses)
50 {
51 if (UnicastAddress.Address.AddressFamily == AddressFamily.InterNetwork && Socket.OSSupportsIPv4)
52 {
53 try
54 {
55 Outgoing = new UdpClient(AddressFamily.InterNetwork);
56 MulticastAddress = IPAddress.Parse("239.255.255.250");
57 //Outgoing.DontFragment = true;
58 Outgoing.MulticastLoopback = false;
59 }
60 catch (Exception)
61 {
62 continue;
63 }
64 }
65 else if (UnicastAddress.Address.AddressFamily == AddressFamily.InterNetworkV6 && Socket.OSSupportsIPv6)
66 {
67 try
68 {
69 Outgoing = new UdpClient(AddressFamily.InterNetworkV6)
70 {
71 MulticastLoopback = false
72 };
73
74 MulticastAddress = IPAddress.Parse("[FF02::C]");
75 }
76 catch (Exception)
77 {
78 continue;
79 }
80 }
81 else
82 continue;
83
84 Outgoing.EnableBroadcast = true;
85 Outgoing.MulticastLoopback = false;
86 Outgoing.Ttl = 30;
87 Outgoing.Client.Bind(new IPEndPoint(UnicastAddress.Address, 0));
88 Outgoing.JoinMulticastGroup(MulticastAddress);
89
90 IPEndPoint EP = new IPEndPoint(MulticastAddress, ssdpPort);
91 lock (this.ssdpOutgoing)
92 {
93 this.ssdpOutgoing.Add(new KeyValuePair<UdpClient, IPEndPoint>(Outgoing, EP));
94 }
95
96 this.BeginReceiveOutgoing(Outgoing);
97
98 try
99 {
100 Incoming = new UdpClient(Outgoing.Client.AddressFamily)
101 {
102 ExclusiveAddressUse = false
103 };
104
105 Incoming.Client.Bind(new IPEndPoint(UnicastAddress.Address, ssdpPort));
106 this.BeginReceiveIncoming(Incoming);
107
108 lock (this.ssdpIncoming)
109 {
110 this.ssdpIncoming.Add(Incoming);
111 }
112 }
113 catch (Exception)
114 {
115 Incoming = null;
116 }
117
118 if (!GenIncoming.ContainsKey(Outgoing.Client.AddressFamily))
119 {
120 GenIncoming[Outgoing.Client.AddressFamily] = true;
121
122 try
123 {
124 Incoming = new UdpClient(ssdpPort, Outgoing.Client.AddressFamily)
125 {
126 MulticastLoopback = false
127 };
128
129 Incoming.JoinMulticastGroup(MulticastAddress);
130 this.BeginReceiveIncoming(Incoming);
131
132 lock (this.ssdpIncoming)
133 {
134 this.ssdpIncoming.Add(Incoming);
135 }
136 }
137 catch (Exception)
138 {
139 Incoming = null;
140 }
141 }
142 }
143 }
144 }
145
146 private async void BeginReceiveOutgoing(UdpClient Client) // Starts parallel task
147 {
148 try
149 {
150 while (!this.disposed)
151 {
152 UdpReceiveResult Data = await Client.ReceiveAsync();
153 if (this.disposed)
154 return;
155
156 byte[] Packet = Data.Buffer;
157 await this.ReceiveBinary(Packet);
158
159 try
160 {
161 string Header = Encoding.ASCII.GetString(Packet);
162 UPnPHeaders Headers = new UPnPHeaders(Header);
163
164 await this.ReceiveText(Header);
165
166 if (Headers.Direction == HttpDirection.Response &&
167 Headers.HttpVersion >= 1.0 &&
168 Headers.ResponseCode == 200)
169 {
170 if (!string.IsNullOrEmpty(Headers.Location))
171 {
172 DeviceLocation DeviceLocation = new DeviceLocation(this, Headers.SearchTarget, Headers.Server, Headers.Location,
173 Headers.UniqueServiceName, Headers);
174 DeviceLocationEventArgs e = new DeviceLocationEventArgs(DeviceLocation, (IPEndPoint)Client.Client.LocalEndPoint, Data.RemoteEndPoint);
175
176 await this.OnDeviceFound.Raise(this, e);
177 }
178 }
179 else if (Headers.Direction == HttpDirection.Request && Headers.HttpVersion >= 1.0)
180 await this.HandleIncoming(Client, Data.RemoteEndPoint, Headers);
181 }
182 catch (Exception ex)
183 {
184 await this.RaiseOnError(ex);
185 }
186 }
187 }
188 catch (ObjectDisposedException)
189 {
190 // Closed.
191 }
192 catch (Exception ex)
193 {
194 await this.Exception(ex);
195 }
196 }
197
201 public event EventHandlerAsync<DeviceLocationEventArgs> OnDeviceFound = null;
202
203 private async Task HandleIncoming(UdpClient UdpClient, IPEndPoint RemoteIP, UPnPHeaders Headers)
204 {
205 switch (Headers.Verb)
206 {
207 case "M-SEARCH":
208 await this.OnSearch.Raise(this, new NotificationEventArgs(this, Headers, (IPEndPoint)UdpClient.Client.LocalEndPoint, RemoteIP));
209 break;
210
211 case "NOTIFY":
212 await this.OnNotification.Raise(this, new NotificationEventArgs(this, Headers, (IPEndPoint)UdpClient.Client.LocalEndPoint, RemoteIP));
213 break;
214 }
215 }
216
220 public event EventHandlerAsync<NotificationEventArgs> OnNotification = null;
221
225 public event EventHandlerAsync<NotificationEventArgs> OnSearch = null;
226
227 private async void BeginReceiveIncoming(UdpClient Client) // Starts parallel task
228 {
229 try
230 {
231 while (!this.disposed)
232 {
233 UdpReceiveResult Data = await Client.ReceiveAsync();
234 if (this.disposed)
235 return;
236
237 byte[] Packet = Data.Buffer;
238 await this.ReceiveBinary(Packet);
239
240 if (this.disposed)
241 return;
242
243 try
244 {
245 string Header = Encoding.ASCII.GetString(Packet);
246 UPnPHeaders Headers = new UPnPHeaders(Header);
247
248 await this.ReceiveText(Header);
249
250 if (!(Data.RemoteEndPoint is null) &&
251 Headers.Direction == HttpDirection.Request &&
252 Headers.HttpVersion >= 1.0)
253 {
254 await this.HandleIncoming(Client, Data.RemoteEndPoint, Headers);
255 }
256 }
257 catch (Exception ex)
258 {
259 await this.RaiseOnError(ex);
260 }
261 }
262 }
263 catch (ObjectDisposedException)
264 {
265 // Closed.
266 }
267 catch (Exception ex)
268 {
269 await this.Exception(ex);
270 }
271 }
272
276 public Task StartSearch()
277 {
278 return this.StartSearch("upnp:rootdevice", defaultMaximumSearchTimeSeconds);
279 //this.StartSearch("ssdp:all", defaultMaximumSearchTimeSeconds);
280 }
281
286 public Task StartSearch(int MaximumWaitTimeSeconds)
287 {
288 return this.StartSearch("upnp:rootdevice", MaximumWaitTimeSeconds);
289 //this.StartSearch("ssdp:all", MaximumWaitTimeSeconds);
290 }
291
296 public Task StartSearch(string SearchTarget)
297 {
298 return this.StartSearch(SearchTarget, defaultMaximumSearchTimeSeconds);
299 }
300
306 public async Task StartSearch(string SearchTarget, int MaximumWaitTimeSeconds)
307 {
308 foreach (KeyValuePair<UdpClient, IPEndPoint> P in this.GetOutgoing())
309 {
310 string MSearch = "M-SEARCH * HTTP/1.1\r\n" +
311 "HOST: " + P.Value.ToString() + "\r\n" +
312 "MAN:\"ssdp:discover\"\r\n" +
313 "ST: " + SearchTarget + "\r\n" +
314 "MX:" + MaximumWaitTimeSeconds.ToString() + "\r\n\r\n";
315 byte[] Packet = Encoding.ASCII.GetBytes(MSearch);
316
317 await this.SendPacket(P.Key, P.Value, Packet, MSearch);
318 }
319 }
320
321 private KeyValuePair<UdpClient, IPEndPoint>[] GetOutgoing()
322 {
323 return this.GetOutgoing(false);
324 }
325
326 private KeyValuePair<UdpClient, IPEndPoint>[] GetOutgoing(bool Clear)
327 {
328 lock (this.ssdpOutgoing)
329 {
330 KeyValuePair<UdpClient, IPEndPoint>[] Result = this.ssdpOutgoing.ToArray();
331
332 if (Clear)
333 this.ssdpOutgoing.Clear();
334
335 return Result;
336 }
337 }
338
339 private UdpClient[] GetIncoming()
340 {
341 return this.GetIncoming(false);
342 }
343
344 private UdpClient[] GetIncoming(bool Clear)
345 {
346 lock (this.ssdpIncoming)
347 {
348 UdpClient[] Result = this.ssdpIncoming.ToArray();
349
350 if (Clear)
351 this.ssdpIncoming.Clear();
352
353 return Result;
354 }
355 }
356
357 private async Task SendPacket(UdpClient Client, IPEndPoint Destination, byte[] Packet, string Text)
358 {
359 if (this.disposed)
360 return;
361
362 try
363 {
364 await this.TransmitText(Text);
365 await Client.SendAsync(Packet, Packet.Length, Destination);
366 }
367 catch (Exception ex)
368 {
369 await this.RaiseOnError(ex);
370 }
371 }
372
373 private Task RaiseOnError(Exception ex)
374 {
375 return this.OnError.Raise(this, ex);
376 }
377
381 public event EventHandlerAsync<Exception> OnError = null;
382
386 public void Dispose()
387 {
388 this.disposed = true;
389
390 foreach (KeyValuePair<UdpClient, IPEndPoint> P in this.GetOutgoing(true))
391 {
392 try
393 {
394 P.Key.Dispose();
395 }
396 catch (Exception)
397 {
398 // Ignore
399 }
400 }
401
402 foreach (UdpClient Client in this.GetIncoming(true))
403 {
404 try
405 {
406 Client.Dispose();
407 }
408 catch (Exception)
409 {
410 // Ignore
411 }
412 }
413
414 foreach (ISniffer Sniffer in this.Sniffers)
415 {
416 if (Sniffer is IDisposable Disposable)
417 {
418 try
419 {
420 Disposable.Dispose();
421 }
422 catch (Exception ex)
423 {
424 Log.Exception(ex);
425 }
426 }
427 }
428 }
429
438 public DeviceDescriptionDocument GetDevice(string Location)
439 {
440 return this.GetDevice(Location, 10000);
441 }
442
452 public DeviceDescriptionDocument GetDevice(string Location, int Timeout)
453 {
454 return this.GetDeviceAsync(Location, Timeout).Result;
455 }
456
462 public Task<DeviceDescriptionDocument> GetDeviceAsync(string Location)
463 {
464 return this.GetDeviceAsync(Location, 10000);
465 }
466
473 public async Task<DeviceDescriptionDocument> GetDeviceAsync(string Location, int Timeout)
474 {
475 Uri LocationUri = new Uri(Location);
476 using (HttpClient Client = new HttpClient())
477 {
478 try
479 {
480 Client.Timeout = TimeSpan.FromMilliseconds(Timeout);
481 Stream Stream = await Client.GetStreamAsync(LocationUri);
482
483 XmlDocument Xml = new XmlDocument()
484 {
485 PreserveWhitespace = true
486 };
487 Xml.Load(Stream);
488
489 return new DeviceDescriptionDocument(Xml, this, Location);
490 }
491 catch (Exception ex)
492 {
493 await this.RaiseOnError(ex);
494 return null;
495 }
496 }
497 }
498
508 {
509 return this.GetService(Service, 10000);
510 }
511
522 {
523 return this.GetServiceAsync(Service, Timeout).Result;
524 }
525
531 public Task<ServiceDescriptionDocument> GetServiceAsync(UPnPService Service)
532 {
533 return this.GetServiceAsync(Service, 10000);
534 }
535
542 public async Task<ServiceDescriptionDocument> GetServiceAsync(UPnPService Service, int Timeout)
543 {
544 using (HttpClient Client = new HttpClient())
545 {
546 try
547 {
548 Client.Timeout = TimeSpan.FromMilliseconds(Timeout);
549 Stream Stream = await Client.GetStreamAsync(Service.SCPDURI);
550
551 XmlDocument Xml = new XmlDocument()
552 {
553 PreserveWhitespace = true
554 };
555 Xml.Load(Stream);
556
557 return new ServiceDescriptionDocument(Xml, this, Service);
558 }
559 catch (Exception ex)
560 {
561 await this.RaiseOnError(ex);
562 return null;
563 }
564 }
565 }
566
567 }
568}
Static class managing the application event log. Applications and services log events on this static ...
Definition: Log.cs:13
static void Exception(Exception Exception, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, params KeyValuePair< string, object >[] Tags)
Logs an exception. Event type will be determined by the severity of the exception.
Definition: Log.cs:1647
Simple base class for classes implementing communication protocols.
Task Exception(Exception Exception)
Called to inform the viewer of an exception state.
ISniffer[] Sniffers
Registered sniffers.
Task TransmitText(string Text)
Called when text has been transmitted.
Task ReceiveText(string Text)
Called when text has been received.
Task ReceiveBinary(byte[] Data)
Called when binary data has been received.
Contains the information provided in a Device Description Document, downloaded from a device in the n...
Event arguments for completion events when downloading device description documents.
Contains information about the location of a device on the network.
Contains information about the location of a device on the network.
Contains the information provided in a Service Description Document, downloaded from a service in the...
Implements support for the UPnP protocol, as described in: http://upnp.org/specs/arch/UPnP-arch-Devic...
Definition: UPnPClient.cs:21
ServiceDescriptionDocument GetService(UPnPService Service, int Timeout)
Gets the service description document from a service in the network. This method is the synchronous v...
Definition: UPnPClient.cs:521
Task StartSearch(string SearchTarget)
Starts a search for devices on the network.
Definition: UPnPClient.cs:296
ServiceDescriptionDocument GetService(UPnPService Service)
Gets the service description document from a service in the network. This method is the synchronous v...
Definition: UPnPClient.cs:507
Task StartSearch(int MaximumWaitTimeSeconds)
Starts a search for devices on the network.
Definition: UPnPClient.cs:286
Task StartSearch()
Starts a search for devices on the network.
Definition: UPnPClient.cs:276
async Task StartSearch(string SearchTarget, int MaximumWaitTimeSeconds)
Starts a search for devices on the network.
Definition: UPnPClient.cs:306
EventHandlerAsync< DeviceLocationEventArgs > OnDeviceFound
Event raised when a device has been found as a result of a search made by the client.
Definition: UPnPClient.cs:201
EventHandlerAsync< NotificationEventArgs > OnNotification
Event raised when the client is notified of a device or service in the network.
Definition: UPnPClient.cs:220
async Task< DeviceDescriptionDocument > GetDeviceAsync(string Location, int Timeout)
Gets a Device Description Document from a device.
Definition: UPnPClient.cs:473
Task< DeviceDescriptionDocument > GetDeviceAsync(string Location)
Gets a Device Description Document from a device.
Definition: UPnPClient.cs:462
async Task< ServiceDescriptionDocument > GetServiceAsync(UPnPService Service, int Timeout)
Gets a Service Description Document from a device.
Definition: UPnPClient.cs:542
Task< ServiceDescriptionDocument > GetServiceAsync(UPnPService Service)
Gets a Service Description Document from a device.
Definition: UPnPClient.cs:531
EventHandlerAsync< Exception > OnError
Event raised when an error occurs.
Definition: UPnPClient.cs:381
UPnPClient(params ISniffer[] Sniffers)
Implements support for the UPnP protocol, as described in: http://upnp.org/specs/arch/UPnP-arch-Devic...
Definition: UPnPClient.cs:34
EventHandlerAsync< NotificationEventArgs > OnSearch
Event raised when the client receives a request searching for devices or services in the network.
Definition: UPnPClient.cs:225
DeviceDescriptionDocument GetDevice(string Location)
Gets the device description document from a device in the network. This method is the synchronous ver...
Definition: UPnPClient.cs:438
void Dispose()
IDisposable.Dispose
Definition: UPnPClient.cs:386
DeviceDescriptionDocument GetDevice(string Location, int Timeout)
Gets the device description document from a device in the network. This method is the synchronous ver...
Definition: UPnPClient.cs:452
Class managing any HTTP headers in a UPnP UDP request/response.
Definition: UPnPHeaders.cs:32
double HttpVersion
HTTP Version
Definition: UPnPHeaders.cs:177
string SearchTarget
Search Target header
Definition: UPnPHeaders.cs:182
string UniqueServiceName
Unique Service Name (USN) header
Definition: UPnPHeaders.cs:197
string Location
Location header
Definition: UPnPHeaders.cs:192
HttpDirection Direction
Message direction.
Definition: UPnPHeaders.cs:162
Contains information about a service.
Definition: UPnPService.cs:12
Uri SCPDURI
URI to service description
Definition: UPnPService.cs:119
Interface for sniffers. Sniffers can be added to ICommunicationLayer classes to eavesdrop on communic...
Definition: ISniffer.cs:11
HttpDirection
Direction of message
Definition: UPnPHeaders.cs:11