protected RootEntry GetOrCreateRootEntry(string deviceUUID, string descriptionLocation, UPnPVersion upnpVersion, string osVersion,
                                          string productVersion, DateTime expirationTime, EndpointConfiguration endpoint, HTTPVersion httpVersion, int searchPort, out bool wasAdded)
 {
     // Because the order of the UDP advertisement packets isn't guaranteed (and even not really specified by the UPnP specification),
     // in the general case it is not possible to find the correct root entry for each advertisement message.
     // - We cannot search by root device UUID because the upnp:rootdevice message might not be the first message, so before that message, we don't know the root device ID and
     //   thus we cannot use the root device id as unique key to find the root entry
     // - We cannot use the device description because for multi-homed devices, more than one device description can belong to the same root device
     //
     // Assume the message arrive in an order so that device A over network interface N1 is announced first. Second, device B over network interface N2 is announced.
     // In that case, we cannot judge if those two devices belong to the same root device or not.
     //
     // To face that situation, we first add all advertised devices to _pendingDeviceEntries. When a upnp:rootdevice message is received,
     // we either simply move the root entry from _pendingDeviceEntries into _cpData.DeviceEntries or we merge the pending entry with an already existing
     // entry in _cpData.DeviceEntries. At that time the merge is possible because we then have the root device id for both root entries.
     lock (_cpData.SyncObj)
     {
         RootEntry result = GetRootEntryByContainedDeviceUUID(deviceUUID) ?? GetRootEntryByDescriptionLocation(descriptionLocation);
         if (result != null)
         {
             result.ExpirationTime = expirationTime;
             wasAdded = false;
         }
         else
         {
             result = new RootEntry(_cpData.SyncObj, upnpVersion, osVersion, productVersion, expirationTime);
             _pendingDeviceEntries.Add(result);
             wasAdded = true;
         }
         return(result);
     }
 }
        protected RootEntry GetRootEntryByDescriptionLocation(string descriptionLocation)
        {
            RootEntry rootEntry = _cpData.DeviceEntries.Values.FirstOrDefault(entry => entry.AllLinks.ContainsKey(descriptionLocation)) ??
                                  _pendingDeviceEntries.FirstOrDefault(entry => entry.AllLinks.ContainsKey(descriptionLocation));

            return(rootEntry);
        }
 protected void RemoveRootEntry(RootEntry rootEntry)
 {
     lock (_cpData.SyncObj)
     {
         _cpData.DeviceEntries.Remove(rootEntry.RootDeviceUUID);
         _pendingDeviceEntries.Remove(rootEntry);
     }
 }
 /// <summary>
 /// Returns the root entry which contains any device of the given <paramref name="deviceUUID"/>.
 /// </summary>
 /// <param name="deviceUUID">UUID of any device to search the enclosing root entry for.</param>
 /// <returns>Root entry instance or <c>null</c>, if no device with the given UUID was found.</returns>
 protected RootEntry GetRootEntryByContainedDeviceUUID(string deviceUUID)
 {
     lock (_cpData.SyncObj)
     {
         RootEntry result = _cpData.DeviceEntries.Values.FirstOrDefault(rootEntry => rootEntry.Devices.ContainsKey(deviceUUID));
         if (result != null)
         {
             return(result);
         }
         return(_pendingDeviceEntries.FirstOrDefault(rootEntry => rootEntry.Devices.ContainsKey(deviceUUID)));
     }
 }
 protected void InvokeDeviceConfigurationChanged(RootEntry rootEntry)
 {
     try
     {
         DeviceConfigurationChangedDlgt dlgt = DeviceConfigurationChanged;
         if (dlgt != null)
         {
             dlgt(rootEntry);
         }
     }
     catch (Exception e)
     {
         UPnPConfiguration.LOGGER.Warn("SSDPClientController: Error invoking DeviceConfigurationChanged delegate", e);
     }
 }
 protected void InvokeServiceAdded(RootEntry rootEntry, DeviceEntry deviceEntry, string serviceTypeVersion_URN)
 {
     try
     {
         ServiceAddedDlgt dlgt = ServiceAdded;
         if (dlgt != null)
         {
             dlgt(rootEntry, deviceEntry, serviceTypeVersion_URN);
         }
     }
     catch (Exception e)
     {
         UPnPConfiguration.LOGGER.Warn("SSDPClientController: Error invoking ServiceAdded delegate", e);
     }
 }
 protected RootEntry MergeOrMoveRootEntry(RootEntry pendingRootEntry, string rootDeviceUUID)
 {
     lock (_cpData.SyncObj)
     {
         _pendingDeviceEntries.Remove(pendingRootEntry);
         RootEntry targetEntry;
         if (_cpData.DeviceEntries.TryGetValue(rootDeviceUUID, out targetEntry))
         {
             targetEntry.MergeRootEntry(pendingRootEntry);
             return(targetEntry);
         }
         targetEntry = pendingRootEntry; // From here on, the entry is not pending any more, so we use the variable targetEntry for clearness
         targetEntry.RootDeviceUUID            = rootDeviceUUID;
         _cpData.DeviceEntries[rootDeviceUUID] = targetEntry;
         return(targetEntry);
     }
 }
Beispiel #8
0
 /// <summary>
 /// Merges the data of the <paramref name="other"/> root entry into this entry.
 /// </summary>
 /// <param name="other">Other root entry to merge. The <paramref name="other"/> entry must not contain any of this entry's link data nor
 /// any of this entry's devices.</param>
 internal void MergeRootEntry(RootEntry other)
 {
     _expirationTime = _expirationTime > other.ExpirationTime ? _expirationTime : other.ExpirationTime;
     foreach (LinkData linkData in other.AllLinks.Values)
     {
         AddOrUpdateLink(linkData.Endpoint, linkData.DescriptionLocation, linkData.HTTPVersion, linkData.SearchPort);
     }
     foreach (DeviceEntry deviceEntry in other.Devices.Values)
     {
         _devices.Add(deviceEntry.UUID, deviceEntry);
     }
     _bootID = Math.Max(_bootID, other.BootID);
     foreach (KeyValuePair <IPEndPoint, uint> kvp in other._configIDs)
     {
         _configIDs[kvp.Key] = kvp.Value;
     }
     foreach (KeyValuePair <string, object> clientProperty in other.ClientProperties)
     {
         if (!_clientProperties.ContainsKey(clientProperty.Key))
         {
             _clientProperties[clientProperty.Key] = clientProperty.Value;
         }
     }
 }
        protected void HandleUpdatePacket(SimpleHTTPRequest header, EndpointConfiguration config)
        {
            if (header.Param != "*")
            {
                // Invalid message
                return;
            }
            HTTPVersion httpVersion;

            if (!HTTPVersion.TryParse(header.HttpVersion, out httpVersion))
            {
                // Invalid message
                return;
            }
            // Host, NT, NTS, USN are not interesting
            //string host = header["HOST"];
            //string nt = header["NT"];
            //string nts = header["NTS"];
            string usn = header["USN"];
            //string location = header["LOCATION"];
            string bi = header["BOOTID.UPNP.ORG"];
            uint   bootID;

            if (!uint.TryParse(bi, out bootID))
            {
                // Invalid message
                return;
            }
            string nbi = header["NEXTBOOTID.UPNP.ORG"];
            uint   nextBootID;

            if (!uint.TryParse(nbi, out nextBootID))
            {
                // Invalid message
                return;
            }
            if (!usn.StartsWith("uuid:"))
            {
                // Invalid usn
                return;
            }
            int separatorIndex = usn.IndexOf("::");

            if (separatorIndex < 6) // separatorIndex == -1 or separatorIndex not after "uuid:" prefix with at least one char UUID
            // We only use messages containing a "::" substring and discard the "uuid:device-UUID" message
            {
                return;
            }
            string    deviceUUID = usn.Substring(5, separatorIndex - 5);
            RootEntry rootEntry  = GetRootEntryByContainedDeviceUUID(deviceUUID);

            if (rootEntry == null)
            {
                return;
            }
            if (rootEntry.BootID > bootID)
            {
                // Invalid message
                return;
            }
            bool fireDeviceRebooted = false;

            lock (_cpData.SyncObj)
            {
                if (rootEntry.BootID < bootID)
                {
                    // Device reboot
                    fireDeviceRebooted = true;
                }
                rootEntry.BootID = nextBootID;
            }
            if (fireDeviceRebooted)
            {
                InvokeDeviceRebooted(rootEntry, false);
            }
        }
        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);
                }
            }
        }