SsdpDevice.cs 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Collections.ObjectModel;
  4. using System.Text;
  5. using System.Threading.Tasks;
  6. using System.Xml;
  7. using Rssdp.Infrastructure;
  8. namespace Rssdp
  9. {
  10. /// <summary>
  11. /// Base class representing the common details of a (root or embedded) device, either to be published or that has been located.
  12. /// </summary>
  13. /// <remarks>
  14. /// <para>Do not derive new types directly from this class. New device classes should derive from either <see cref="SsdpRootDevice"/> or <see cref="SsdpEmbeddedDevice"/>.</para>
  15. /// </remarks>
  16. /// <seealso cref="SsdpRootDevice"/>
  17. /// <seealso cref="SsdpEmbeddedDevice"/>
  18. public abstract class SsdpDevice
  19. {
  20. #region Fields
  21. private string _Udn;
  22. private string _DeviceType;
  23. private string _DeviceTypeNamespace;
  24. private int _DeviceVersion;
  25. private SsdpDevicePropertiesCollection _CustomProperties;
  26. private CustomHttpHeadersCollection _CustomResponseHeaders;
  27. private IList<SsdpDevice> _Devices;
  28. #endregion
  29. #region Events
  30. /// <summary>
  31. /// Raised when a new child device is added.
  32. /// </summary>
  33. /// <seealso cref="AddDevice"/>
  34. /// <seealso cref="DeviceAdded"/>
  35. public event EventHandler<DeviceEventArgs> DeviceAdded;
  36. /// <summary>
  37. /// Raised when a child device is removed.
  38. /// </summary>
  39. /// <seealso cref="RemoveDevice"/>
  40. /// <seealso cref="DeviceRemoved"/>
  41. public event EventHandler<DeviceEventArgs> DeviceRemoved;
  42. #endregion
  43. #region Constructors
  44. /// <summary>
  45. /// Derived type constructor, allows constructing a device with no parent. Should only be used from derived types that are or inherit from <see cref="SsdpRootDevice"/>.
  46. /// </summary>
  47. protected SsdpDevice()
  48. {
  49. _DeviceTypeNamespace = SsdpConstants.UpnpDeviceTypeNamespace;
  50. _DeviceType = SsdpConstants.UpnpDeviceTypeBasicDevice;
  51. _DeviceVersion = 1;
  52. this.Icons = new List<SsdpDeviceIcon>();
  53. _Devices = new List<SsdpDevice>();
  54. this.Devices = new ReadOnlyCollection<SsdpDevice>(_Devices);
  55. _CustomResponseHeaders = new CustomHttpHeadersCollection();
  56. _CustomProperties = new SsdpDevicePropertiesCollection();
  57. }
  58. /// <summary>
  59. /// Deserialisation constructor.
  60. /// </summary>
  61. /// <remarks><para>Uses the provided XML string and parent device properties to set the properties of the object. The XML provided must be a valid UPnP device description document.</para></remarks>
  62. /// <param name="deviceDescriptionXml">A UPnP device description XML document.</param>
  63. /// <exception cref="System.ArgumentNullException">Thrown if the <paramref name="deviceDescriptionXml"/> argument is null.</exception>
  64. /// <exception cref="System.ArgumentException">Thrown if the <paramref name="deviceDescriptionXml"/> argument is empty.</exception>
  65. protected SsdpDevice(string deviceDescriptionXml)
  66. : this()
  67. {
  68. if (deviceDescriptionXml == null) throw new ArgumentNullException("deviceDescriptionXml");
  69. if (deviceDescriptionXml.Length == 0) throw new ArgumentException("deviceDescriptionXml cannot be an empty string.", "deviceDescriptionXml");
  70. using (var ms = new System.IO.MemoryStream(System.Text.UTF8Encoding.UTF8.GetBytes(deviceDescriptionXml)))
  71. {
  72. var reader = XmlReader.Create(ms);
  73. LoadDeviceProperties(reader, this);
  74. }
  75. }
  76. #endregion
  77. #region Public Properties
  78. #region UPnP Device Description Properties
  79. /// <summary>
  80. /// Sets or returns the core device type (not including namespace, version etc.). Required.
  81. /// </summary>
  82. /// <remarks><para>Defaults to the UPnP basic device type.</para></remarks>
  83. /// <seealso cref="DeviceTypeNamespace"/>
  84. /// <seealso cref="DeviceVersion"/>
  85. /// <seealso cref="FullDeviceType"/>
  86. public string DeviceType
  87. {
  88. get
  89. {
  90. return _DeviceType;
  91. }
  92. set
  93. {
  94. _DeviceType = value;
  95. }
  96. }
  97. public string DeviceClass { get; set; }
  98. /// <summary>
  99. /// Sets or returns the namespace for the <see cref="DeviceType"/> of this device. Optional, but defaults to UPnP schema so should be changed if <see cref="DeviceType"/> is not a UPnP device type.
  100. /// </summary>
  101. /// <remarks><para>Defaults to the UPnP standard namespace.</para></remarks>
  102. /// <seealso cref="DeviceType"/>
  103. /// <seealso cref="DeviceVersion"/>
  104. /// <seealso cref="FullDeviceType"/>
  105. public string DeviceTypeNamespace
  106. {
  107. get
  108. {
  109. return _DeviceTypeNamespace;
  110. }
  111. set
  112. {
  113. _DeviceTypeNamespace = value;
  114. }
  115. }
  116. /// <summary>
  117. /// Sets or returns the version of the device type. Optional, defaults to 1.
  118. /// </summary>
  119. /// <remarks><para>Defaults to a value of 1.</para></remarks>
  120. /// <seealso cref="DeviceType"/>
  121. /// <seealso cref="DeviceTypeNamespace"/>
  122. /// <seealso cref="FullDeviceType"/>
  123. public int DeviceVersion
  124. {
  125. get
  126. {
  127. return _DeviceVersion;
  128. }
  129. set
  130. {
  131. _DeviceVersion = value;
  132. }
  133. }
  134. /// <summary>
  135. /// Returns the full device type string.
  136. /// </summary>
  137. /// <remarks>
  138. /// <para>The format used is urn:<see cref="DeviceTypeNamespace"/>:device:<see cref="DeviceType"/>:<see cref="DeviceVersion"/></para>
  139. /// </remarks>
  140. public string FullDeviceType
  141. {
  142. get
  143. {
  144. return String.Format("urn:{0}:{3}:{1}:{2}",
  145. this.DeviceTypeNamespace ?? String.Empty,
  146. this.DeviceType ?? String.Empty,
  147. this.DeviceVersion,
  148. this.DeviceClass ?? "device");
  149. }
  150. }
  151. /// <summary>
  152. /// Sets or returns the universally unique identifier for this device (without the uuid: prefix). Required.
  153. /// </summary>
  154. /// <remarks>
  155. /// <para>Must be the same over time for a specific device instance (i.e. must survive reboots).</para>
  156. /// <para>For UPnP 1.0 this can be any unique string. For UPnP 1.1 this should be a 128 bit number formatted in a specific way, preferably generated using the time and MAC based algorithm. See section 1.1.4 of http://upnp.org/specs/arch/UPnP-arch-DeviceArchitecture-v1.1.pdf for details.</para>
  157. /// <para>Technically this library implements UPnP 1.0, so any value is allowed, but we advise using UPnP 1.1 compatible values for good behaviour and forward compatibility with future versions.</para>
  158. /// </remarks>
  159. public string Uuid { get; set; }
  160. /// <summary>
  161. /// Returns (or sets*) a unique device name for this device. Optional, not recommended to be explicitly set.
  162. /// </summary>
  163. /// <remarks>
  164. /// <para>* In general you should not explicitly set this property. If it is not set (or set to null/empty string) the property will return a UDN value that is correct as per the UPnP specification, based on the other device properties.</para>
  165. /// <para>The setter is provided to allow for devices that do not correctly follow the specification (when we discover them), rather than to intentionally deviate from the specification.</para>
  166. /// <para>If a value is explicitly set, it is used verbatim, and so any prefix (such as uuid:) must be provided in the value.</para>
  167. /// </remarks>
  168. public string Udn
  169. {
  170. get
  171. {
  172. if (String.IsNullOrEmpty(_Udn) && !String.IsNullOrEmpty(this.Uuid))
  173. return "uuid:" + this.Uuid;
  174. else
  175. return _Udn;
  176. }
  177. set
  178. {
  179. _Udn = value;
  180. }
  181. }
  182. /// <summary>
  183. /// Sets or returns a friendly/display name for this device on the network. Something the user can identify the device/instance by, i.e Lounge Main Light. Required.
  184. /// </summary>
  185. /// <remarks><para>A short description for the end user. </para></remarks>
  186. public string FriendlyName { get; set; }
  187. /// <summary>
  188. /// Sets or returns the name of the manufacturer of this device. Required.
  189. /// </summary>
  190. public string Manufacturer { get; set; }
  191. /// <summary>
  192. /// Sets or returns a URL to the manufacturers web site. Optional.
  193. /// </summary>
  194. public Uri ManufacturerUrl { get; set; }
  195. /// <summary>
  196. /// Sets or returns a description of this device model. Recommended.
  197. /// </summary>
  198. /// <remarks><para>A long description for the end user.</para></remarks>
  199. public string ModelDescription { get; set; }
  200. /// <summary>
  201. /// Sets or returns the name of this model. Required.
  202. /// </summary>
  203. public string ModelName { get; set; }
  204. /// <summary>
  205. /// Sets or returns the number of this model. Recommended.
  206. /// </summary>
  207. public string ModelNumber { get; set; }
  208. /// <summary>
  209. /// Sets or returns a URL to a web page with details of this device model. Optional.
  210. /// </summary>
  211. /// <remarks>
  212. /// <para>Optional. May be relative to base URL.</para>
  213. /// </remarks>
  214. public Uri ModelUrl { get; set; }
  215. /// <summary>
  216. /// Sets or returns the serial number for this device. Recommended.
  217. /// </summary>
  218. public string SerialNumber { get; set; }
  219. /// <summary>
  220. /// Sets or returns the universal product code of the device, if any. Optional.
  221. /// </summary>
  222. /// <remarks>
  223. /// <para>If not blank, must be exactly 12 numeric digits.</para>
  224. /// </remarks>
  225. public string Upc { get; set; }
  226. /// <summary>
  227. /// Sets or returns the URL to a web page that can be used to configure/manager/use the device. Recommended.
  228. /// </summary>
  229. /// <remarks>
  230. /// <para>May be relative to base URL. </para>
  231. /// </remarks>
  232. public Uri PresentationUrl { get; set; }
  233. #endregion
  234. /// <summary>
  235. /// Returns a list of icons (images) that can be used to display this device. Optional, but recommended you provide at least one at 48x48 pixels.
  236. /// </summary>
  237. public IList<SsdpDeviceIcon> Icons
  238. {
  239. get;
  240. private set;
  241. }
  242. /// <summary>
  243. /// Returns a read-only enumerable set of <see cref="SsdpDevice"/> objects representing children of this device. Child devices are optional.
  244. /// </summary>
  245. /// <seealso cref="AddDevice"/>
  246. /// <seealso cref="RemoveDevice"/>
  247. public IList<SsdpDevice> Devices
  248. {
  249. get;
  250. private set;
  251. }
  252. /// <summary>
  253. /// Returns a dictionary of <see cref="SsdpDeviceProperty"/> objects keyed by <see cref="SsdpDeviceProperty.FullName"/>. Each value represents a custom property in the device description document.
  254. /// </summary>
  255. public SsdpDevicePropertiesCollection CustomProperties
  256. {
  257. get
  258. {
  259. return _CustomProperties;
  260. }
  261. }
  262. /// <summary>
  263. /// Provides a list of additional information to provide about this device in search response and notification messages.
  264. /// </summary>
  265. /// <remarks>
  266. /// <para>The headers included here are included in the (HTTP headers) for search response and alive notifications sent in relation to this device.</para>
  267. /// <para>Only values specified directly on this <see cref="SsdpDevice"/> instance will be included, headers from ancestors are not automatically included.</para>
  268. /// </remarks>
  269. public CustomHttpHeadersCollection CustomResponseHeaders
  270. {
  271. get
  272. {
  273. return _CustomResponseHeaders;
  274. }
  275. }
  276. #endregion
  277. #region Public Methods
  278. /// <summary>
  279. /// Adds a child device to the <see cref="Devices"/> collection.
  280. /// </summary>
  281. /// <param name="device">The <see cref="SsdpEmbeddedDevice"/> instance to add.</param>
  282. /// <remarks>
  283. /// <para>If the device is already a member of the <see cref="Devices"/> collection, this method does nothing.</para>
  284. /// <para>Also sets the <see cref="SsdpEmbeddedDevice.RootDevice"/> property of the added device and all descendant devices to the relevant <see cref="SsdpRootDevice"/> instance.</para>
  285. /// </remarks>
  286. /// <exception cref="System.ArgumentNullException">Thrown if the <paramref name="device"/> argument is null.</exception>
  287. /// <exception cref="System.InvalidOperationException">Thrown if the <paramref name="device"/> is already associated with a different <see cref="SsdpRootDevice"/> instance than used in this tree. Can occur if you try to add the same device instance to more than one tree. Also thrown if you try to add a device to itself.</exception>
  288. /// <seealso cref="DeviceAdded"/>
  289. public void AddDevice(SsdpEmbeddedDevice device)
  290. {
  291. if (device == null) throw new ArgumentNullException("device");
  292. if (device.RootDevice != null && device.RootDevice != this.ToRootDevice()) throw new InvalidOperationException("This device is already associated with a different root device (has been added as a child in another branch).");
  293. if (device == this) throw new InvalidOperationException("Can't add device to itself.");
  294. bool wasAdded = false;
  295. lock (_Devices)
  296. {
  297. device.RootDevice = this.ToRootDevice();
  298. _Devices.Add(device);
  299. wasAdded = true;
  300. }
  301. if (wasAdded)
  302. OnDeviceAdded(device);
  303. }
  304. /// <summary>
  305. /// Removes a child device from the <see cref="Devices"/> collection.
  306. /// </summary>
  307. /// <param name="device">The <see cref="SsdpEmbeddedDevice"/> instance to remove.</param>
  308. /// <remarks>
  309. /// <para>If the device is not a member of the <see cref="Devices"/> collection, this method does nothing.</para>
  310. /// <para>Also sets the <see cref="SsdpEmbeddedDevice.RootDevice"/> property to null for the removed device and all descendant devices.</para>
  311. /// </remarks>
  312. /// <exception cref="System.ArgumentNullException">Thrown if the <paramref name="device"/> argument is null.</exception>
  313. /// <seealso cref="DeviceRemoved"/>
  314. public void RemoveDevice(SsdpEmbeddedDevice device)
  315. {
  316. if (device == null) throw new ArgumentNullException("device");
  317. bool wasRemoved = false;
  318. lock (_Devices)
  319. {
  320. wasRemoved = _Devices.Remove(device);
  321. if (wasRemoved)
  322. {
  323. device.RootDevice = null;
  324. }
  325. }
  326. if (wasRemoved)
  327. OnDeviceRemoved(device);
  328. }
  329. /// <summary>
  330. /// Raises the <see cref="DeviceAdded"/> event.
  331. /// </summary>
  332. /// <param name="device">The <see cref="SsdpEmbeddedDevice"/> instance added to the <see cref="Devices"/> collection.</param>
  333. /// <seealso cref="AddDevice"/>
  334. /// <seealso cref="DeviceAdded"/>
  335. protected virtual void OnDeviceAdded(SsdpEmbeddedDevice device)
  336. {
  337. var handlers = this.DeviceAdded;
  338. if (handlers != null)
  339. handlers(this, new DeviceEventArgs(device));
  340. }
  341. /// <summary>
  342. /// Raises the <see cref="DeviceRemoved"/> event.
  343. /// </summary>
  344. /// <param name="device">The <see cref="SsdpEmbeddedDevice"/> instance removed from the <see cref="Devices"/> collection.</param>
  345. /// <seealso cref="RemoveDevice"/>
  346. /// <see cref="DeviceRemoved"/>
  347. protected virtual void OnDeviceRemoved(SsdpEmbeddedDevice device)
  348. {
  349. var handlers = this.DeviceRemoved;
  350. if (handlers != null)
  351. handlers(this, new DeviceEventArgs(device));
  352. }
  353. /// <summary>
  354. /// Writes this device to the specified <see cref="System.Xml.XmlWriter"/> as a device node and it's content.
  355. /// </summary>
  356. /// <param name="writer">The <see cref="System.Xml.XmlWriter"/> to output to.</param>
  357. /// <param name="device">The <see cref="SsdpDevice"/> to write out.</param>
  358. /// <exception cref="System.ArgumentNullException">Thrown if the <paramref name="writer"/> or <paramref name="device"/> argument is null.</exception>
  359. protected virtual void WriteDeviceDescriptionXml(XmlWriter writer, SsdpDevice device)
  360. {
  361. if (writer == null) throw new ArgumentNullException("writer");
  362. if (device == null) throw new ArgumentNullException("device");
  363. writer.WriteStartElement("device");
  364. if (!String.IsNullOrEmpty(device.FullDeviceType))
  365. WriteNodeIfNotEmpty(writer, "deviceType", device.FullDeviceType);
  366. WriteNodeIfNotEmpty(writer, "friendlyName", device.FriendlyName);
  367. WriteNodeIfNotEmpty(writer, "manufacturer", device.Manufacturer);
  368. WriteNodeIfNotEmpty(writer, "manufacturerURL", device.ManufacturerUrl);
  369. WriteNodeIfNotEmpty(writer, "modelDescription", device.ModelDescription);
  370. WriteNodeIfNotEmpty(writer, "modelName", device.ModelName);
  371. WriteNodeIfNotEmpty(writer, "modelNumber", device.ModelNumber);
  372. WriteNodeIfNotEmpty(writer, "modelURL", device.ModelUrl);
  373. WriteNodeIfNotEmpty(writer, "presentationURL", device.PresentationUrl);
  374. WriteNodeIfNotEmpty(writer, "serialNumber", device.SerialNumber);
  375. WriteNodeIfNotEmpty(writer, "UDN", device.Udn);
  376. WriteNodeIfNotEmpty(writer, "UPC", device.Upc);
  377. WriteCustomProperties(writer, device);
  378. WriteIcons(writer, device);
  379. WriteChildDevices(writer, device);
  380. writer.WriteEndElement();
  381. }
  382. /// <summary>
  383. /// Converts a string to a <see cref="Uri"/>, or returns null if the string provided is null.
  384. /// </summary>
  385. /// <param name="value">The string value to convert.</param>
  386. /// <returns>A <see cref="Uri"/>.</returns>
  387. protected static Uri StringToUri(string value)
  388. {
  389. if (!String.IsNullOrEmpty(value))
  390. return new Uri(value, UriKind.RelativeOrAbsolute);
  391. return null;
  392. }
  393. #endregion
  394. #region Private Methods
  395. #region Serialisation Methods
  396. private static void WriteCustomProperties(XmlWriter writer, SsdpDevice device)
  397. {
  398. foreach (var prop in device.CustomProperties)
  399. {
  400. writer.WriteElementString(prop.Namespace, prop.Name, SsdpConstants.SsdpDeviceDescriptionXmlNamespace, prop.Value);
  401. }
  402. }
  403. private static void WriteIcons(XmlWriter writer, SsdpDevice device)
  404. {
  405. if (device.Icons.Count > 0)
  406. {
  407. writer.WriteStartElement("iconList");
  408. foreach (var icon in device.Icons)
  409. {
  410. writer.WriteStartElement("icon");
  411. writer.WriteElementString("mimetype", icon.MimeType);
  412. writer.WriteElementString("width", icon.Width.ToString());
  413. writer.WriteElementString("height", icon.Height.ToString());
  414. writer.WriteElementString("depth", icon.ColorDepth.ToString());
  415. writer.WriteElementString("url", icon.Url.ToString());
  416. writer.WriteEndElement();
  417. }
  418. writer.WriteEndElement();
  419. }
  420. }
  421. private void WriteChildDevices(XmlWriter writer, SsdpDevice parentDevice)
  422. {
  423. if (parentDevice.Devices.Count > 0)
  424. {
  425. writer.WriteStartElement("deviceList");
  426. foreach (var device in parentDevice.Devices)
  427. {
  428. WriteDeviceDescriptionXml(writer, device);
  429. }
  430. writer.WriteEndElement();
  431. }
  432. }
  433. private static void WriteNodeIfNotEmpty(XmlWriter writer, string nodeName, string value)
  434. {
  435. if (!String.IsNullOrEmpty(value))
  436. writer.WriteElementString(nodeName, value);
  437. }
  438. private static void WriteNodeIfNotEmpty(XmlWriter writer, string nodeName, Uri value)
  439. {
  440. if (value != null)
  441. writer.WriteElementString(nodeName, value.ToString());
  442. }
  443. #endregion
  444. #region Deserialisation Methods
  445. private void LoadDeviceProperties(XmlReader reader, SsdpDevice device)
  446. {
  447. ReadUntilDeviceNode(reader);
  448. while (!reader.EOF)
  449. {
  450. if (reader.NodeType == XmlNodeType.EndElement && reader.Name == "device")
  451. {
  452. reader.Read();
  453. break;
  454. }
  455. if (!SetPropertyFromReader(reader, device))
  456. reader.Read();
  457. }
  458. }
  459. private static void ReadUntilDeviceNode(XmlReader reader)
  460. {
  461. while (!reader.EOF && (reader.LocalName != "device" || reader.NodeType != XmlNodeType.Element))
  462. {
  463. reader.Read();
  464. }
  465. }
  466. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "Yes, there is a large switch statement, not it's not really complex and doesn't really need to be rewritten at this point.")]
  467. private bool SetPropertyFromReader(XmlReader reader, SsdpDevice device)
  468. {
  469. switch (reader.LocalName)
  470. {
  471. case "friendlyName":
  472. device.FriendlyName = reader.ReadElementContentAsString();
  473. break;
  474. case "manufacturer":
  475. device.Manufacturer = reader.ReadElementContentAsString();
  476. break;
  477. case "manufacturerURL":
  478. device.ManufacturerUrl = StringToUri(reader.ReadElementContentAsString());
  479. break;
  480. case "modelDescription":
  481. device.ModelDescription = reader.ReadElementContentAsString();
  482. break;
  483. case "modelName":
  484. device.ModelName = reader.ReadElementContentAsString();
  485. break;
  486. case "modelNumber":
  487. device.ModelNumber = reader.ReadElementContentAsString();
  488. break;
  489. case "modelURL":
  490. device.ModelUrl = StringToUri(reader.ReadElementContentAsString());
  491. break;
  492. case "presentationURL":
  493. device.PresentationUrl = StringToUri(reader.ReadElementContentAsString());
  494. break;
  495. case "serialNumber":
  496. device.SerialNumber = reader.ReadElementContentAsString();
  497. break;
  498. case "UDN":
  499. device.Udn = reader.ReadElementContentAsString();
  500. SetUuidFromUdn(device);
  501. break;
  502. case "UPC":
  503. device.Upc = reader.ReadElementContentAsString();
  504. break;
  505. case "deviceType":
  506. SetDeviceTypePropertiesFromFullDeviceType(device, reader.ReadElementContentAsString());
  507. break;
  508. case "iconList":
  509. reader.Read();
  510. LoadIcons(reader, device);
  511. break;
  512. case "deviceList":
  513. reader.Read();
  514. LoadChildDevices(reader, device);
  515. break;
  516. case "serviceList":
  517. reader.Skip();
  518. break;
  519. default:
  520. if (reader.NodeType == XmlNodeType.Element && reader.Name != "device" && reader.Name != "icon")
  521. {
  522. AddCustomProperty(reader, device);
  523. break;
  524. }
  525. else
  526. return false;
  527. }
  528. return true;
  529. }
  530. private static void SetDeviceTypePropertiesFromFullDeviceType(SsdpDevice device, string value)
  531. {
  532. if (String.IsNullOrEmpty(value) || !value.Contains(":"))
  533. device.DeviceType = value;
  534. else
  535. {
  536. var parts = value.Split(':');
  537. if (parts.Length == 5)
  538. {
  539. int deviceVersion = 1;
  540. if (Int32.TryParse(parts[4], out deviceVersion))
  541. {
  542. device.DeviceTypeNamespace = parts[1];
  543. device.DeviceType = parts[3];
  544. device.DeviceVersion = deviceVersion;
  545. }
  546. else
  547. device.DeviceType = value;
  548. }
  549. else
  550. device.DeviceType = value;
  551. }
  552. }
  553. private static void SetUuidFromUdn(SsdpDevice device)
  554. {
  555. if (device.Udn != null && device.Udn.StartsWith("uuid:", StringComparison.OrdinalIgnoreCase))
  556. device.Uuid = device.Udn.Substring(5).Trim();
  557. else
  558. device.Uuid = device.Udn;
  559. }
  560. private static void LoadIcons(XmlReader reader, SsdpDevice device)
  561. {
  562. while (!reader.EOF)
  563. {
  564. while (!reader.EOF && reader.NodeType != XmlNodeType.Element)
  565. {
  566. reader.Read();
  567. }
  568. if (reader.LocalName != "icon") break;
  569. while (reader.Name == "icon")
  570. {
  571. var icon = new SsdpDeviceIcon();
  572. LoadIconProperties(reader, icon);
  573. device.Icons.Add(icon);
  574. reader.Read();
  575. }
  576. }
  577. }
  578. private static void LoadIconProperties(XmlReader reader, SsdpDeviceIcon icon)
  579. {
  580. while (!reader.EOF)
  581. {
  582. if (reader.NodeType != XmlNodeType.Element)
  583. {
  584. if (reader.NodeType == XmlNodeType.EndElement && reader.Name == "icon") break;
  585. reader.Read();
  586. continue;
  587. }
  588. switch (reader.LocalName)
  589. {
  590. case "depth":
  591. icon.ColorDepth = reader.ReadElementContentAsInt();
  592. break;
  593. case "height":
  594. icon.Height = reader.ReadElementContentAsInt();
  595. break;
  596. case "width":
  597. icon.Width = reader.ReadElementContentAsInt();
  598. break;
  599. case "mimetype":
  600. icon.MimeType = reader.ReadElementContentAsString();
  601. break;
  602. case "url":
  603. icon.Url = StringToUri(reader.ReadElementContentAsString());
  604. break;
  605. }
  606. reader.Read();
  607. }
  608. }
  609. private void LoadChildDevices(XmlReader reader, SsdpDevice device)
  610. {
  611. while (!reader.EOF && reader.NodeType != XmlNodeType.Element)
  612. {
  613. reader.Read();
  614. }
  615. while (!reader.EOF)
  616. {
  617. while (!reader.EOF && reader.NodeType != XmlNodeType.Element)
  618. {
  619. reader.Read();
  620. }
  621. if (reader.LocalName == "device")
  622. {
  623. var childDevice = new SsdpEmbeddedDevice();
  624. LoadDeviceProperties(reader, childDevice);
  625. device.AddDevice(childDevice);
  626. }
  627. else
  628. break;
  629. }
  630. }
  631. private static void AddCustomProperty(XmlReader reader, SsdpDevice device)
  632. {
  633. // If the property is an empty element, there is no value to read
  634. // Advance the reader and return
  635. if (reader.IsEmptyElement)
  636. {
  637. reader.Read();
  638. return;
  639. }
  640. var newProp = new SsdpDeviceProperty() { Namespace = reader.Prefix, Name = reader.LocalName };
  641. int depth = reader.Depth;
  642. reader.Read();
  643. while (reader.NodeType == XmlNodeType.Whitespace || reader.NodeType == XmlNodeType.Comment)
  644. {
  645. reader.Read();
  646. }
  647. if (reader.NodeType != XmlNodeType.CDATA && reader.NodeType != XmlNodeType.Text)
  648. {
  649. while (!reader.EOF && (reader.NodeType != XmlNodeType.EndElement || reader.Name != newProp.Name || reader.Prefix != newProp.Namespace || reader.Depth != depth))
  650. {
  651. reader.Read();
  652. }
  653. if (!reader.EOF)
  654. reader.Read();
  655. return;
  656. }
  657. newProp.Value = reader.Value;
  658. // We don't support complex nested types or repeat/multi-value properties
  659. if (!device.CustomProperties.Contains(newProp.FullName))
  660. device.CustomProperties.Add(newProp);
  661. }
  662. #endregion
  663. //private bool ChildDeviceExists(SsdpDevice device)
  664. //{
  665. // return (from d in _Devices where device.Uuid == d.Uuid select d).Any();
  666. //}
  667. #endregion
  668. }
  669. }