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;
3using System.IO;
4using System.Net;
5using System.Net.Http;
6using System.Net.NetworkInformation;
8using System.Text;
9using System.Threading.Tasks;
10using System.Xml;
11using Waher.Events;
13
15{
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
89 if (IsMulticastAddress(MulticastAddress))
90 Outgoing.JoinMulticastGroup(MulticastAddress);
91 else
92 this.Warning("Address provided is not a multi-cast address.");
93
94 IPEndPoint EP = new IPEndPoint(MulticastAddress, ssdpPort);
95 lock (this.ssdpOutgoing)
96 {
97 this.ssdpOutgoing.Add(new KeyValuePair<UdpClient, IPEndPoint>(Outgoing, EP));
98 }
99
100 this.BeginReceiveOutgoing(Outgoing);
101
102 try
103 {
104 Incoming = new UdpClient(Outgoing.Client.AddressFamily)
105 {
106 ExclusiveAddressUse = false
107 };
108
109 Incoming.Client.Bind(new IPEndPoint(UnicastAddress.Address, ssdpPort));
110 this.BeginReceiveIncoming(Incoming);
111
112 lock (this.ssdpIncoming)
113 {
114 this.ssdpIncoming.Add(Incoming);
115 }
116 }
117 catch (Exception)
118 {
119 Incoming = null;
120 }
121
122 if (!GenIncoming.ContainsKey(Outgoing.Client.AddressFamily))
123 {
124 GenIncoming[Outgoing.Client.AddressFamily] = true;
125
126 try
127 {
128 Incoming = new UdpClient(ssdpPort, Outgoing.Client.AddressFamily)
129 {
130 MulticastLoopback = false
131 };
132
133 if (IsMulticastAddress(MulticastAddress))
134 Incoming.JoinMulticastGroup(MulticastAddress);
135 else
136 this.Warning("Address provided is not a multi-cast address.");
137
138 this.BeginReceiveIncoming(Incoming);
139
140 lock (this.ssdpIncoming)
141 {
142 this.ssdpIncoming.Add(Incoming);
143 }
144 }
145 catch (Exception)
146 {
147 Incoming = null;
148 }
149 }
150 }
151 }
152 }
153
154 private async void BeginReceiveOutgoing(UdpClient Client) // Starts parallel task
155 {
156 try
157 {
158 while (!this.disposed)
159 {
160 UdpReceiveResult Data = await Client.ReceiveAsync();
161 if (this.disposed)
162 return;
163
164 byte[] Packet = Data.Buffer;
165 this.ReceiveBinary(true, Packet);
166
167 try
168 {
169 string Header = Encoding.ASCII.GetString(Packet);
170 UPnPHeaders Headers = new UPnPHeaders(Header);
171
172 this.ReceiveText(Header);
173
174 if (Headers.Direction == HttpDirection.Response &&
175 Headers.HttpVersion >= 1.0 &&
176 Headers.ResponseCode == 200)
177 {
178 if (!string.IsNullOrEmpty(Headers.Location))
179 {
180 DeviceLocation DeviceLocation = new DeviceLocation(this, Headers.SearchTarget, Headers.Server, Headers.Location,
181 Headers.UniqueServiceName, Headers);
182 DeviceLocationEventArgs e = new DeviceLocationEventArgs(DeviceLocation, (IPEndPoint)Client.Client.LocalEndPoint, Data.RemoteEndPoint);
183
184 await this.OnDeviceFound.Raise(this, e);
185 }
186 }
187 else if (Headers.Direction == HttpDirection.Request && Headers.HttpVersion >= 1.0)
188 await this.HandleIncoming(Client, Data.RemoteEndPoint, Headers);
189 }
190 catch (Exception ex)
191 {
192 await this.RaiseOnError(ex);
193 }
194 }
195 }
196 catch (ObjectDisposedException)
197 {
198 // Closed.
199 }
200 catch (Exception ex)
201 {
202 this.Exception(ex);
203 }
204 }
205
209 public event EventHandlerAsync<DeviceLocationEventArgs> OnDeviceFound = null;
210
211 private async Task HandleIncoming(UdpClient UdpClient, IPEndPoint RemoteIP, UPnPHeaders Headers)
212 {
213 switch (Headers.Verb)
214 {
215 case "M-SEARCH":
216 await this.OnSearch.Raise(this, new NotificationEventArgs(this, Headers, (IPEndPoint)UdpClient.Client.LocalEndPoint, RemoteIP));
217 break;
218
219 case "NOTIFY":
220 await this.OnNotification.Raise(this, new NotificationEventArgs(this, Headers, (IPEndPoint)UdpClient.Client.LocalEndPoint, RemoteIP));
221 break;
222 }
223 }
224
228 public event EventHandlerAsync<NotificationEventArgs> OnNotification = null;
229
233 public event EventHandlerAsync<NotificationEventArgs> OnSearch = null;
234
235 private async void BeginReceiveIncoming(UdpClient Client) // Starts parallel task
236 {
237 try
238 {
239 while (!this.disposed)
240 {
241 UdpReceiveResult Data = await Client.ReceiveAsync();
242 if (this.disposed)
243 return;
244
245 byte[] Packet = Data.Buffer;
246 this.ReceiveBinary(true, Packet);
247
248 if (this.disposed)
249 return;
250
251 try
252 {
253 string Header = Encoding.ASCII.GetString(Packet);
254 UPnPHeaders Headers = new UPnPHeaders(Header);
255
256 this.ReceiveText(Header);
257
258 if (!(Data.RemoteEndPoint is null) &&
259 Headers.Direction == HttpDirection.Request &&
260 Headers.HttpVersion >= 1.0)
261 {
262 await this.HandleIncoming(Client, Data.RemoteEndPoint, Headers);
263 }
264 }
265 catch (Exception ex)
266 {
267 await this.RaiseOnError(ex);
268 }
269 }
270 }
271 catch (ObjectDisposedException)
272 {
273 // Closed.
274 }
275 catch (Exception ex)
276 {
277 this.Exception(ex);
278 }
279 }
280
284 public Task StartSearch()
285 {
286 return this.StartSearch("upnp:rootdevice", defaultMaximumSearchTimeSeconds);
287 //this.StartSearch("ssdp:all", defaultMaximumSearchTimeSeconds);
288 }
289
294 public Task StartSearch(int MaximumWaitTimeSeconds)
295 {
296 return this.StartSearch("upnp:rootdevice", MaximumWaitTimeSeconds);
297 //this.StartSearch("ssdp:all", MaximumWaitTimeSeconds);
298 }
299
304 public Task StartSearch(string SearchTarget)
305 {
306 return this.StartSearch(SearchTarget, defaultMaximumSearchTimeSeconds);
307 }
308
314 public async Task StartSearch(string SearchTarget, int MaximumWaitTimeSeconds)
315 {
316 foreach (KeyValuePair<UdpClient, IPEndPoint> P in this.GetOutgoing())
317 {
318 StringBuilder sb = new StringBuilder();
319
320 sb.Append("M-SEARCH * HTTP/1.1\r\n");
321 sb.Append("HOST: ");
322 sb.Append(P.Value.ToString());
323 sb.Append("\r\nMAN:\"ssdp:discover\"");
324 sb.Append("\r\nST: ");
325 sb.Append(SearchTarget);
326 sb.Append("\r\nMX:");
327 sb.Append(MaximumWaitTimeSeconds.ToString());
328 sb.Append("\r\n\r\n");
329
330 string MSearch = sb.ToString();
331 byte[] Packet = Encoding.ASCII.GetBytes(MSearch);
332
333 await this.SendPacket(P.Key, P.Value, Packet, MSearch);
334 }
335 }
336
337 private KeyValuePair<UdpClient, IPEndPoint>[] GetOutgoing()
338 {
339 return this.GetOutgoing(false);
340 }
341
342 private KeyValuePair<UdpClient, IPEndPoint>[] GetOutgoing(bool Clear)
343 {
344 lock (this.ssdpOutgoing)
345 {
346 KeyValuePair<UdpClient, IPEndPoint>[] Result = this.ssdpOutgoing.ToArray();
347
348 if (Clear)
349 this.ssdpOutgoing.Clear();
350
351 return Result;
352 }
353 }
354
355 private UdpClient[] GetIncoming()
356 {
357 return this.GetIncoming(false);
358 }
359
360 private UdpClient[] GetIncoming(bool Clear)
361 {
362 lock (this.ssdpIncoming)
363 {
364 UdpClient[] Result = this.ssdpIncoming.ToArray();
365
366 if (Clear)
367 this.ssdpIncoming.Clear();
368
369 return Result;
370 }
371 }
372
373 private async Task SendPacket(UdpClient Client, IPEndPoint Destination, byte[] Packet, string Text)
374 {
375 if (this.disposed)
376 return;
377
378 try
379 {
380 this.TransmitText(Text);
381 await Client.SendAsync(Packet, Packet.Length, Destination);
382 }
383 catch (Exception ex)
384 {
385 await this.RaiseOnError(ex);
386 }
387 }
388
389 private Task RaiseOnError(Exception ex)
390 {
391 return this.OnError.Raise(this, ex);
392 }
393
397 public event EventHandlerAsync<Exception> OnError = null;
398
402 [Obsolete("Use DisposeAsync() instead.")]
403 public void Dispose()
404 {
405 this.DisposeAsync().Wait();
406 }
407
411 public async Task DisposeAsync()
412 {
413 this.disposed = true;
414
415 foreach (KeyValuePair<UdpClient, IPEndPoint> P in this.GetOutgoing(true))
416 {
417 try
418 {
419 P.Key.Dispose();
420 }
421 catch (Exception)
422 {
423 // Ignore
424 }
425 }
426
427 foreach (UdpClient Client in this.GetIncoming(true))
428 {
429 try
430 {
431 Client.Dispose();
432 }
433 catch (Exception)
434 {
435 // Ignore
436 }
437 }
438
439 foreach (ISniffer Sniffer in this.Sniffers)
440 {
441 try
442 {
443 if (Sniffer is IDisposableAsync DisposableAsync)
444 await DisposableAsync.DisposeAsync();
445 else if (Sniffer is IDisposable Disposable)
446 Disposable.Dispose();
447 }
448 catch (Exception ex)
449 {
450 Log.Exception(ex);
451 }
452 }
453 }
454
463 public DeviceDescriptionDocument GetDevice(string Location)
464 {
465 return this.GetDevice(Location, 10000);
466 }
467
477 public DeviceDescriptionDocument GetDevice(string Location, int Timeout)
478 {
479 return this.GetDeviceAsync(Location, Timeout).Result;
480 }
481
487 public Task<DeviceDescriptionDocument> GetDeviceAsync(string Location)
488 {
489 return this.GetDeviceAsync(Location, 10000);
490 }
491
498 public async Task<DeviceDescriptionDocument> GetDeviceAsync(string Location, int Timeout)
499 {
500 Uri LocationUri = new Uri(Location);
501 using (HttpClient Client = new HttpClient())
502 {
503 try
504 {
505 Client.Timeout = TimeSpan.FromMilliseconds(Timeout);
506 Stream Stream = await Client.GetStreamAsync(LocationUri);
507
508 XmlDocument Xml = new XmlDocument()
509 {
510 PreserveWhitespace = true
511 };
512 Xml.Load(Stream);
513
514 return new DeviceDescriptionDocument(Xml, this, Location);
515 }
516 catch (Exception ex)
517 {
518 await this.RaiseOnError(ex);
519 return null;
520 }
521 }
522 }
523
533 {
534 return this.GetService(Service, 10000);
535 }
536
547 {
548 return this.GetServiceAsync(Service, Timeout).Result;
549 }
550
556 public Task<ServiceDescriptionDocument> GetServiceAsync(UPnPService Service)
557 {
558 return this.GetServiceAsync(Service, 10000);
559 }
560
567 public async Task<ServiceDescriptionDocument> GetServiceAsync(UPnPService Service, int Timeout)
568 {
569 using (HttpClient Client = new HttpClient())
570 {
571 try
572 {
573 Client.Timeout = TimeSpan.FromMilliseconds(Timeout);
574 Stream Stream = await Client.GetStreamAsync(Service.SCPDURI);
575
576 XmlDocument Xml = new XmlDocument()
577 {
578 PreserveWhitespace = true
579 };
580 Xml.Load(Stream);
581
582 return new ServiceDescriptionDocument(Xml, this, Service);
583 }
584 catch (Exception ex)
585 {
586 await this.RaiseOnError(ex);
587 return null;
588 }
589 }
590 }
591
592 }
593}
Static class managing the application event log. Applications and services log events on this static ...
Definition: Log.cs:14
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:1657
Simple base class for classes implementing communication protocols.
void TransmitText(string Text)
Called when text has been transmitted.
static bool IsMulticastAddress(IPAddress Address)
Checks if an IP Address is a multi-cast address or not.
void Exception(Exception Exception)
Called to inform the viewer of an exception state.
void ReceiveText(string Text)
Called when text has been received.
ISniffer[] Sniffers
Registered sniffers.
void Warning(string Warning)
Called to inform the viewer of a warning state.
void ReceiveBinary(int Count)
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:546
Task StartSearch(string SearchTarget)
Starts a search for devices on the network.
Definition: UPnPClient.cs:304
ServiceDescriptionDocument GetService(UPnPService Service)
Gets the service description document from a service in the network. This method is the synchronous v...
Definition: UPnPClient.cs:532
Task StartSearch(int MaximumWaitTimeSeconds)
Starts a search for devices on the network.
Definition: UPnPClient.cs:294
Task StartSearch()
Starts a search for devices on the network.
Definition: UPnPClient.cs:284
async Task StartSearch(string SearchTarget, int MaximumWaitTimeSeconds)
Starts a search for devices on the network.
Definition: UPnPClient.cs:314
EventHandlerAsync< DeviceLocationEventArgs > OnDeviceFound
Event raised when a device has been found as a result of a search made by the client.
Definition: UPnPClient.cs:209
async Task DisposeAsync()
IDisposableAsync.DisposeAsync
Definition: UPnPClient.cs:411
EventHandlerAsync< NotificationEventArgs > OnNotification
Event raised when the client is notified of a device or service in the network.
Definition: UPnPClient.cs:228
async Task< DeviceDescriptionDocument > GetDeviceAsync(string Location, int Timeout)
Gets a Device Description Document from a device.
Definition: UPnPClient.cs:498
Task< DeviceDescriptionDocument > GetDeviceAsync(string Location)
Gets a Device Description Document from a device.
Definition: UPnPClient.cs:487
async Task< ServiceDescriptionDocument > GetServiceAsync(UPnPService Service, int Timeout)
Gets a Service Description Document from a device.
Definition: UPnPClient.cs:567
Task< ServiceDescriptionDocument > GetServiceAsync(UPnPService Service)
Gets a Service Description Document from a device.
Definition: UPnPClient.cs:556
EventHandlerAsync< Exception > OnError
Event raised when an error occurs.
Definition: UPnPClient.cs:397
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:233
DeviceDescriptionDocument GetDevice(string Location)
Gets the device description document from a device in the network. This method is the synchronous ver...
Definition: UPnPClient.cs:463
void Dispose()
IDisposable.Dispose
Definition: UPnPClient.cs:403
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:477
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:11
Uri SCPDURI
URI to service description
Definition: UPnPService.cs:118
Interface for asynchronously disposable objects.
Interface for sniffers. Sniffers can be added to ICommunicationLayer classes to eavesdrop on communic...
Definition: ISniffer.cs:10
HttpDirection
Direction of message
Definition: UPnPHeaders.cs:11