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);
                }
            }
        }