protected void HandleNotifyPacket(EndpointConfiguration config, IPEndPoint remoteEndPoint, HTTPVersion httpVersion, string date, string cacheControl, string location, string server, string nts, string usn, string bi, string ci, string sp) { uint bootID = 0; if (bi != null && !uint.TryParse(bi, out bootID)) { // Invalid message return; } uint configID = 0; if (ci != null && !uint.TryParse(ci, out configID)) { // Invalid message return; } if (!usn.StartsWith("uuid:")) { // Invalid usn return; } string deviceUUID; string messageType; if (!ParserHelper.TryParseUSN(usn, out deviceUUID, out messageType)) { // We only use messages of the type "uuid:device-UUID::..." and discard the "uuid:device-UUID" message return; } if (nts == "ssdp:alive") { if (server == null) { // Invalid message return; } int maxAge; if (!TryParseMaxAge(cacheControl, out maxAge)) { // Invalid message return; } DateTime d; if (!DateTime.TryParse(date, out d)) { d = DateTime.Now; } DateTime expirationTime = d.AddSeconds(maxAge); // The specification says the SERVER header should contain three entries, separated by space, like // "SERVER: OS/version UPnP/1.1 product/version". // Unfortunately, some clients send entries separated by ", ", like "Linux/2.x.x, UPnP/1.0, pvConnect UPnP SDK/1.0". // We try to handle all situations correctly here, that's the reason for this ugly code. // What we've seen until now: // SERVER: Linux/2.x.x, UPnP/1.0, pvConnect UPnP SDK/1.0 => tokens separated by ',' // SERVER: Windows 2003, UPnP/1.0 DLNADOC/1.50, Serviio/0.5.2 => tokens separated by ',' and additional info in UPnP version token // SERVER: 3Com-ADSL-11g/1.0 UPnP/1.0 => only two tokens string[] versionInfos = server.Contains(", ") ? server.Split(new string[] { ", " }, StringSplitOptions.RemoveEmptyEntries) : server.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); string upnpVersionInfo = versionInfos.FirstOrDefault(v => v.StartsWith(UPnPVersion.VERSION_PREFIX)); if (upnpVersionInfo == null) { // Invalid message return; } // upnpVersionInfo = 'UPnP/1.0', 'UPnP/1.1', 'UPnP/1.0 DLNADOC/1.50', ..., the UPnP version is always the first token string[] upnpVersionInfoTokens = upnpVersionInfo.Split(' '); string upnpVersionInfoToken = upnpVersionInfoTokens[0]; UPnPVersion upnpVersion; if (!UPnPVersion.TryParse(upnpVersionInfoToken, out upnpVersion)) { // Invalid message return; } if (upnpVersion.VerMax != 1) { // Incompatible UPnP version return; } int searchPort = 1900; if (upnpVersion.VerMin >= 1) { if (bi == null || ci == null) { // Invalid message return; } if (sp != null && (!int.TryParse(sp, out searchPort) || searchPort < 49152 || searchPort > 65535)) { // Invalid message return; } } RootEntry rootEntry; DeviceEntry deviceEntry = null; string serviceType = null; bool fireDeviceRebooted = false; bool fireConfigurationChanged = false; bool fireRootDeviceAdded = false; bool fireDeviceAdded = false; bool fireServiceAdded = false; lock (_cpData.SyncObj) { bool rootEntryAdded; // Use fail-safe code, see comment above about the different SERVER headers string osVersion = versionInfos.Length < 1 ? string.Empty : versionInfos[0]; string productVersion = versionInfos.Length < 3 ? string.Empty : versionInfos[2]; rootEntry = GetOrCreateRootEntry(deviceUUID, location, upnpVersion, osVersion, productVersion, expirationTime, config, httpVersion, searchPort, out rootEntryAdded); if (bi != null && rootEntry.BootID > bootID) { // Invalid message return; } uint currentConfigId = rootEntry.GetConfigID(remoteEndPoint); if (currentConfigId != 0 && currentConfigId != configID) { fireConfigurationChanged = true; } rootEntry.SetConfigID(remoteEndPoint, configID); if (!rootEntryAdded && bi != null && rootEntry.BootID < bootID) { // Device reboot // A device, which has rebooted, has lost all its links, so we must forget about the old link registrations and wait for new registrations in alive messages rootEntry.ClearLinks(); fireDeviceRebooted = true; } // Don't add the link before a reboot was detected and thus, rootEntry.ClearLinks() was called rootEntry.AddOrUpdateLink(config, location, httpVersion, searchPort); rootEntry.BootID = bootID; if (messageType == "upnp:rootdevice") { rootEntry.GetOrCreateDeviceEntry(deviceUUID); object value; if (!rootEntry.ClientProperties.TryGetValue("RootDeviceSetUp", out value)) { rootEntry = MergeOrMoveRootEntry(rootEntry, deviceUUID); fireRootDeviceAdded = true; rootEntry.ClientProperties["RootDeviceSetUp"] = true; } } else if (messageType.StartsWith("urn:")) { if (messageType.IndexOf(":device:") > -1) { string deviceType; int deviceTypeVersion; if (!ParserHelper.TryParseTypeVersion_URN(messageType, out deviceType, out deviceTypeVersion)) { // Invalid message return; } deviceEntry = rootEntry.GetOrCreateDeviceEntry(deviceUUID); fireDeviceAdded = string.IsNullOrEmpty(deviceEntry.DeviceType); deviceEntry.DeviceType = deviceType; deviceEntry.DeviceTypeVersion = deviceTypeVersion; } else if (messageType.IndexOf(":service:") > -1) { deviceEntry = rootEntry.GetOrCreateDeviceEntry(deviceUUID); serviceType = messageType; if (deviceEntry.Services.Contains(serviceType)) { return; } deviceEntry.Services.Add(serviceType); fireServiceAdded = true; } } else { // Invalid message return; } } // Raise events after returning the lock if (fireDeviceRebooted) { InvokeDeviceRebooted(rootEntry, fireConfigurationChanged); } else if (fireConfigurationChanged) { InvokeDeviceConfigurationChanged(rootEntry); } if (fireRootDeviceAdded) { InvokeRootDeviceAdded(rootEntry); } if (fireDeviceAdded) { InvokeDeviceAdded(rootEntry, deviceEntry); } if (fireServiceAdded) { InvokeServiceAdded(rootEntry, deviceEntry, serviceType); } } else if (nts == "ssdp:byebye") { RootEntry rootEntry = GetRootEntryByContainedDeviceUUID(deviceUUID); if (rootEntry != null) { if (bi != null && rootEntry.BootID > bootID) { // Invalid message return; } RemoveRootEntry(rootEntry); InvokeRootDeviceRemoved(rootEntry); } } }