public static HttpStatusCode HandleEventNotification(Stream stream, Encoding streamEncoding, CpService service,
                                                      UPnPVersion upnpVersion)
 {
     try
     {
         // Parse XML document
         using (StreamReader streamReader = new StreamReader(stream, streamEncoding))
             using (XmlReader reader = XmlReader.Create(streamReader, UPnPConfiguration.DEFAULT_XML_READER_SETTINGS))
             {
                 reader.MoveToContent();
                 reader.ReadStartElement("propertyset", UPnPConsts.NS_UPNP_EVENT);
                 while (reader.LocalName == "property" && reader.NamespaceURI == UPnPConsts.NS_UPNP_EVENT)
                 {
                     reader.ReadStartElement("property", UPnPConsts.NS_UPNP_EVENT);
                     HandleVariableChangeNotification(reader, service, upnpVersion);
                     reader.ReadEndElement(); // property
                 }
                 reader.Close();
             }
         return(HttpStatusCode.OK);
     }
     catch (Exception e)
     {
         UPnPConfiguration.LOGGER.Warn("GENAClientController: Error handling event notification", e);
         return(HttpStatusCode.BadRequest);
     }
 }
Exemplo n.º 2
0
        /// <summary>
        /// Given the <paramref name="userAgentStr"/> from an HTTP USER-AGENT header, this method extracts the UPnP version
        /// from the string.
        /// </summary>
        /// <param name="userAgentStr">USER-AGENT header entry of the form "OS/version UPnP/1.1 product/version".</param>
        /// <param name="minorVersion">Returns the minor version number in the specified <paramref name="userAgentStr"/>.</param>
        /// <returns><c>true</c>, if the user agent string could successfully be parsed and denotes a UPnP major version of 1.</returns>
        /// <exception cref="MediaPortal.Utilities.Exceptions.InvalidDataException">If the specified header value is malformed.</exception>
        public static bool ParseUserAgentUPnP1MinorVersion(string userAgentStr, out int minorVersion)
        {
            if (string.IsNullOrEmpty(userAgentStr))
            {
                minorVersion = 0;
                return(false);
            }
            UPnPVersion ver;

            if (!UPnPVersion.TryParseFromUserAgent(userAgentStr, out ver))
            {
                if (UPnPConfiguration.LAX_USER_AGENT_PARSING)
                {
                    // If a client sent a malformed USER-AGENT, we'll assume UPnP Version 1.0
                    minorVersion = 0;
                    return(true);
                }
                throw new UnsupportedRequestException(string.Format("Unsupported USER-AGENT header entry '{0}'", userAgentStr));
            }
            minorVersion = 0;
            if (ver.VerMax != 1)
            {
                return(false);
            }
            minorVersion = ver.VerMin;
            return(true);
        }
Exemplo n.º 3
0
 /// <summary>
 /// Creates a new <see cref="RootEntry"/> instance.
 /// </summary>
 /// <param name="rwLock">Synchronization object to use for multi-threaded access.</param>
 /// <param name="upnpVersion">UPnP version the remote device is using.</param>
 /// <param name="osVersion">OS and version our partner is using.</param>
 /// <param name="productVersion">Product and version our partner is using.</param>
 /// <param name="expirationTime">Time when the advertisement will expire.</param>
 public RootEntry(ReaderWriterLockSlim rwLock, UPnPVersion upnpVersion, string osVersion,
                  string productVersion, DateTime expirationTime)
 {
     _rwLock         = rwLock;
     _upnpVersion    = upnpVersion;
     _osVersion      = osVersion;
     _productVersion = productVersion;
     _expirationTime = expirationTime;
 }
Exemplo n.º 4
0
 /// <summary>
 /// Creates a new <see cref="RootEntry"/> instance.
 /// </summary>
 /// <param name="syncObj">Synchronization object to use for multithread access.</param>
 /// <param name="upnpVersion">UPnP version the remote device is using.</param>
 /// <param name="osVersion">OS and version our partner is using.</param>
 /// <param name="productVersion">Product and version our partner is using.</param>
 /// <param name="expirationTime">Time when the advertisement will expire.</param>
 public RootEntry(object syncObj, UPnPVersion upnpVersion, string osVersion,
                  string productVersion, DateTime expirationTime)
 {
     _syncObj        = syncObj;
     _upnpVersion    = upnpVersion;
     _osVersion      = osVersion;
     _productVersion = productVersion;
     _expirationTime = expirationTime;
 }
        public GENAClientController(CPData cpData, DeviceConnection connection, EndpointConfiguration endpoint, UPnPVersion upnpVersion)
        {
            _cpData                = cpData;
            _connection            = connection;
            _endpoint              = endpoint;
            _upnpVersion           = upnpVersion;
            _eventNotificationPath = "/" + Guid.NewGuid();
            IPAddress address = endpoint.EndPointIPAddress;

            _eventNotificationEndpoint = new IPEndPoint(address, address.AddressFamily == AddressFamily.InterNetwork ?
                                                        cpData.HttpPortV4 : cpData.HttpPortV6);
            _subscriptionRenewalTimer = new Timer(OnSubscriptionRenewalTimerElapsed);
        }
Exemplo n.º 6
0
        public GENAClientController(CPData cpData, DeviceConnection connection, EndpointConfiguration endpoint, UPnPVersion upnpVersion)
        {
            _cpData                = cpData;
            _connection            = connection;
            _endpoint              = endpoint;
            _upnpVersion           = upnpVersion;
            _eventNotificationPath = cpData.ServicePrefix + "/" + Guid.NewGuid();
            IPAddress address = endpoint.EndPointIPAddress;
            var       port    = UPnPServer.DEFAULT_UPNP_AND_SERVICE_PORT_NUMBER;

            _eventNotificationEndpoint = new IPEndPoint(address, port);
            _subscriptionRenewalTimer  = new Timer(OnSubscriptionRenewalTimerElapsed);
        }
        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);
                }
            }
        }
 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);
     }
 }
Exemplo n.º 9
0
        /// <summary>
        /// Takes the XML document provided by the given <paramref name="body"/> stream, parses it in the given
        /// <paramref name="contentEncoding"/> and provides the action result to the appropriate receiver.
        /// </summary>
        /// <param name="body">Body stream of the SOAP XML action result message.</param>
        /// <param name="contentEncoding">Encoding of the body stream.</param>
        /// <param name="action">Action which was called before.</param>
        /// <param name="clientState">State object which was given in the action call and which will be returned to the client.</param>
        /// <param name="upnpVersion">UPnP version of the UPnP server.</param>
        public static void HandleResult(Stream body, Encoding contentEncoding, CpAction action, object clientState, UPnPVersion upnpVersion)
        {
            bool           sourceSupportsUPnP11 = upnpVersion.VerMin >= 1;
            IList <object> outParameterValues;

            try
            {
                if (!body.CanRead)
                {
                    UPnPConfiguration.LOGGER.Error("SOAPHandler: Empty action result document");
                    action.ActionErrorResultPresent(new UPnPError(501, "Invalid server result"), clientState);
                    return;
                }
                using (TextReader textReader = new StreamReader(body, contentEncoding))
                    outParameterValues = ParseResult(textReader, action, sourceSupportsUPnP11);
            }
            catch (Exception e)
            {
                UPnPConfiguration.LOGGER.Error("SOAPHandler: Error parsing action result document", e);
                action.ActionErrorResultPresent(new UPnPError(501, "Invalid server result"), clientState);
                return;
            }
            try
            {
                // Invoke action result
                action.ActionResultPresent(outParameterValues, clientState);
            }
            catch (Exception e)
            {
                UPnPConfiguration.LOGGER.Error("UPnP subsystem: Error invoking action '{0}'", e, action.FullQualifiedName);
            }
        }
Exemplo n.º 10
0
        /// <summary>
        /// Encodes a call of the specified <paramref name="action"/> with the given <paramref name="inParamValues"/> and
        /// returns the resulting SOAP XML string.
        /// </summary>
        /// <param name="action">Action to be called.</param>
        /// <param name="inParamValues">List of parameter values which must match the action's signature.
        /// Can be <c>null</c> if the parameter list is empty.</param>
        /// <param name="upnpVersion">UPnP version to use for the encoding.</param>
        /// <returns>XML string which contains the SOAP document.</returns>
        public static string EncodeCall(CpAction action, IList <object> inParamValues, UPnPVersion upnpVersion)
        {
            bool          targetSupportsUPnP11 = upnpVersion.VerMin >= 1;
            StringBuilder result = new StringBuilder(5000);

            using (StringWriterWithEncoding stringWriter = new StringWriterWithEncoding(result, UPnPConsts.UTF8_NO_BOM))
                using (XmlWriter writer = XmlWriter.Create(stringWriter, UPnPConfiguration.DEFAULT_XML_WRITER_SETTINGS))
                {
                    SoapHelper.WriteSoapEnvelopeStart(writer, true);
                    writer.WriteStartElement("u", action.Name, action.ParentService.ServiceTypeVersion_URN);

                    // Check input parameters
                    IList <CpArgument> formalArguments = action.InArguments;
                    if (inParamValues == null)
                    {
                        inParamValues = EMPTY_OBJECT_LIST;
                    }
                    if (inParamValues.Count != formalArguments.Count)
                    {
                        throw new ArgumentException("Invalid argument count");
                    }
                    for (int i = 0; i < formalArguments.Count; i++)
                    {
                        CpArgument argument = formalArguments[i];
                        object     value    = inParamValues[i];
                        writer.WriteStartElement(argument.Name);
                        argument.SoapSerializeArgument(value, !targetSupportsUPnP11, writer);
                        writer.WriteEndElement(); // argument.Name
                    }
                    SoapHelper.WriteSoapEnvelopeEndAndClose(writer);
                }
            return(result.ToString());
        }
        protected static void HandleVariableChangeNotification(XmlReader reader, CpService service, UPnPVersion upnpVersion)
        {
            string          variableName = reader.LocalName;
            CpStateVariable stateVariable;

            if (!service.StateVariables.TryGetValue(variableName, out stateVariable))
            {
                // We don't know that variable - this is an error case but we won't raise an exception here
                return;
            }
            object value = stateVariable.DataType.SoapDeserializeValue(reader, upnpVersion.VerMin == 0);

            service.InvokeStateVariableChanged(stateVariable, value);
        }