private void OnStateVariableChanged(DvStateVariable variable)
        {
            lock (_serverData.SyncObj)
            {
                // Unicast event notifications
                DvService service = variable.ParentService;
                foreach (EndpointConfiguration config in _serverData.UPnPEndPoints)
                {
                    foreach (EventSubscription subscription in config.EventSubscriptions)
                    {
                        if (subscription.Service == service && !subscription.IsDisposed)
                        {
                            subscription.StateVariableChanged(variable);
                        }
                    }
                }

                // Multicast event notifications
                if (variable.Multicast)
                {
                    EventingState eventingState = _serverData.GetMulticastEventKey(variable.ParentService);
                    if (eventingState.EventKey == 0)
                    {
                        // Avoid sending "normal" change events before the initial event was sent
                        return;
                    }
                    eventingState.ModerateChangeEvent(variable);
                    ScheduleMulticastEvents();
                }
            }
        }
Esempio n. 2
0
        /// <summary>
        /// Returns the next event key for a multicast event for the given <paramref name="service"/>.
        /// </summary>
        /// <param name="service">Service for that the new multicast event key should be created.</param>
        /// <returns>New multicast event key.</returns>
        public uint GetNextMulticastEventKey(DvService service)
        {
            EventingState state  = GetMulticastEventKey(service);
            uint          result = state.EventKey;

            state.IncEventKey();
            return(result);
        }
Esempio n. 3
0
        /// <summary>
        /// Returns the event key for a multicast event for the given <paramref name="service"/> or <c>null</c>.
        /// </summary>
        public EventingState GetMulticastEventKey(DvService service)
        {
            EventingState result;

            if (!ServiceMulticastEventingState.TryGetValue(service, out result))
            {
                ServiceMulticastEventingState[service] = result = new EventingState();
            }
            return(result);
        }
        protected void SendMulticastEventNotification(DvService service, IEnumerable <DvStateVariable> variables)
        {
            DvDevice      device        = service.ParentDevice;
            EventingState eventingState = _serverData.GetMulticastEventKey(service);
            // First cluster variables by multicast event level so we can put variables of the same event level into a single message
            IDictionary <string, ICollection <DvStateVariable> > variablesByLevel =
                new Dictionary <string, ICollection <DvStateVariable> >();

            foreach (DvStateVariable variable in variables)
            {
                ICollection <DvStateVariable> variablesCollection;
                if (!variablesByLevel.TryGetValue(variable.MulticastEventLevel, out variablesCollection))
                {
                    variablesByLevel[variable.MulticastEventLevel] = variablesCollection = new List <DvStateVariable>();
                }
                variablesCollection.Add(variable);
            }
            foreach (KeyValuePair <string, ICollection <DvStateVariable> > varByLevel in variablesByLevel)
            {
                // Use a maximum cluster size of GENA_MAX_MULTICAST_EVENT_VAR_COUNT to keep UDP message small
                ICollection <IList <DvStateVariable> > variableClusters = CollectionUtils.Cluster(
                    varByLevel.Value, UPnPConsts.GENA_MAX_MULTICAST_EVENT_VAR_COUNT);
                foreach (IList <DvStateVariable> cluster in variableClusters)
                {
                    foreach (DvStateVariable variable in cluster)
                    {
                        eventingState.UpdateModerationData(variable);
                    }
                    eventingState.IncEventKey();
                    byte[] bodyData = UPnPConsts.UTF8_NO_BOM.GetBytes(GENAMessageBuilder.BuildEventNotificationMessage(
                                                                          cluster, false)); // Albert TODO: Is it correct not to force the simple string equivalent for extended data types here?
                    SimpleHTTPRequest request = new SimpleHTTPRequest("NOTIFY", "*");
                    request.SetHeader("CONTENT-LENGTH", bodyData.Length.ToString());
                    request.SetHeader("CONTENT-TYPE", "text/xml; charset=\"utf-8\"");
                    request.SetHeader("USN", device.UDN + "::" + service.ServiceTypeVersion_URN);
                    request.SetHeader("SVCID", service.ServiceId);
                    request.SetHeader("NT", "upnp:event");
                    request.SetHeader("NTS", "upnp:propchange");
                    request.SetHeader("SEQ", eventingState.EventKey.ToString());
                    request.SetHeader("LVL", varByLevel.Key);
                    request.SetHeader("BOOTID.UPNP.ORG", _serverData.BootId.ToString());

                    foreach (EndpointConfiguration config in _serverData.UPnPEndPoints)
                    {
                        IPEndPoint ep = new IPEndPoint(config.GENAMulticastAddress, UPnPConsts.GENA_MULTICAST_PORT);
                        request.SetHeader("HOST", NetworkHelper.IPEndPointToString(ep));
                        request.MessageBody = bodyData;
                        byte[] bytes = request.Encode();
                        NetworkHelper.SendData(config.GENA_UDP_Socket, ep, bytes, 1);
                    }
                }
            }
        }
 /// <summary>
 /// Adds an event subscriber to the collection of subscribers for the given <paramref name="service"/>.
 /// </summary>
 /// <remarks>
 /// After this method returned <c>true</c>, the caller must send the HTTP response to notify the subscriber
 /// that the event subscription was accepted and to give it the necessary <paramref name="sid"/> which was generated
 /// for the event subscription. After that response was sent, the method <see cref="SendInitialEventNotification"/>
 /// needs to be called with the generated <paramref name="sid"/> value, to complete the event subscription.
 /// </remarks>
 /// <param name="config">Network endpoint which received the event subscription.</param>
 /// <param name="service">UPnP service where the subscription is made.</param>
 /// <param name="callbackURLs">Collection of URLs where change events are sent to (over the given endpoint
 /// <paramref name="config"/>). The URLs will be tried in order until one succeeds.</param>
 /// <param name="httpVersion">HTTP version of the subscriber.</param>
 /// <param name="subscriberSupportsUPnP11">Should be set to <c>true</c> if the subscriber has a user agent header which
 /// says that it uses UPnP 1.1.</param>
 /// <param name="timeout">The input value contains the timeout in seconds which was requested by the subscriber.
 /// The returned value for this parameter will contain the actual timeout in seconds for the new event
 /// subscription.</param>
 /// <param name="date">Date and time when the event subscription was registered. The event subscription
 /// will expire at <paramref name="date"/> plus <paramref name="timeout"/>.</param>
 /// <param name="sid">Generated sid for the new event subscription.</param>
 /// <returns><c>true</c>, if the event registration was accepted, else <c>false</c>.</returns>
 public bool Subscribe(EndpointConfiguration config, DvService service, ICollection <string> callbackURLs,
                       string httpVersion, bool subscriberSupportsUPnP11, ref int timeout, out DateTime date, out string sid)
 {
     lock (_serverData.SyncObj)
     {
         Guid id = Guid.NewGuid();
         sid  = "uuid:" + id.ToString("D");
         date = DateTime.Now;
         DateTime expiration = date.AddSeconds(timeout);
         config.EventSubscriptions.Add(new EventSubscription(sid, service, callbackURLs, expiration, httpVersion, subscriberSupportsUPnP11, config, _serverData));
         return(true);
     }
 }
Esempio n. 6
0
 public EventSubscription(string sid, DvService service, ICollection <string> callbackURLs, DateTime expiration,
                          string httpVersion, bool subscriberSupportsUPnP11, EndpointConfiguration config, ServerData serverData)
 {
     _sid                      = sid;
     _service                  = service;
     _callbackURLs             = callbackURLs;
     _expiration               = expiration;
     _subscriberHTTPVersion    = httpVersion;
     _subscriberSupportsUPnP11 = subscriberSupportsUPnP11;
     _config                   = config;
     _serverData               = serverData;
     _notificationTimer        = new Timer(OnNotificationTimerElapsed, null, Timeout.Infinite, Timeout.Infinite);
 }
Esempio n. 7
0
        /// <summary>
        /// Handler method for SOAP control requests.
        /// </summary>
        /// <param name="service">The service whose action was called.</param>
        /// <param name="messageStream">The stream which contains the HTTP message body with the SOAP envelope.</param>
        /// <param name="streamEncoding">Encoding of the <paramref name="messageStream"/>.</param>
        /// <param name="subscriberSupportsUPnP11">Should be set if the requester sent a user agent header which denotes a UPnP
        /// version of 1.1. If set to <c>false</c>, in- and out-parameters with extended data type will be deserialized/serialized
        /// using the string-equivalent of the values.</param>
        /// <param name="context">Context object holding data for the current action call.</param>
        /// <param name="result">SOAP result - may be an action result, a SOAP fault or <c>null</c> if no body should
        /// be sent in the HTTP response.</param>
        /// <returns>HTTP status code to be sent. Should be
        /// <list>
        /// <item><see cref="HttpStatusCode.OK"/> If the action could be evaluated correctly and produced a SOAP result.</item>
        /// <item><see cref="HttpStatusCode.InternalServerError"/> If the result is a SOAP fault.</item>
        /// <item><see cref="HttpStatusCode.BadRequest"/> If the message stream was malformed.</item>
        /// </list>
        /// </returns>
        public static HttpStatusCode HandleRequest(DvService service, Stream messageStream, Encoding streamEncoding,
                                                   bool subscriberSupportsUPnP11, CallContext context, out string result)
        {
            UPnPError res;

            try
            {
                IList <object> inParameterValues = null; // Default to null if there aren't parameters, will be lazily initialized later
                DvAction       action;
                using (StreamReader streamReader = new StreamReader(messageStream, streamEncoding))
                    using (XmlReader reader = XmlReader.Create(streamReader, UPnPConfiguration.DEFAULT_XML_READER_SETTINGS))
                    {
                        reader.MoveToContent();
                        // Parse SOAP envelope
                        reader.ReadStartElement("Envelope", UPnPConsts.NS_SOAP_ENVELOPE);
                        reader.ReadStartElement("Body", UPnPConsts.NS_SOAP_ENVELOPE);
                        // Reader is positioned at the action element
                        string serviceTypeVersion_URN = reader.NamespaceURI;
                        string type;
                        int    version;
                        // Parse service and action
                        if (!ParserHelper.TryParseTypeVersion_URN(serviceTypeVersion_URN, out type, out version))
                        {
                            throw new MediaPortal.Utilities.Exceptions.InvalidDataException("Unable to parse service type and version URN '{0}'", serviceTypeVersion_URN);
                        }
                        string actionName = reader.LocalName;
                        if (!service.Actions.TryGetValue(actionName, out action))
                        {
                            result = CreateFaultDocument(401, "Invalid Action");
                            return(HttpStatusCode.InternalServerError);
                        }
                        IEnumerator <DvArgument> formalArgumentEnumer = action.InArguments.GetEnumerator();
                        if (!SoapHelper.ReadEmptyStartElement(reader)) // Action name
                        {
                            while (reader.NodeType != XmlNodeType.EndElement)
                            {
                                string argumentName = reader.Name; // Arguments don't have a namespace, so take full name
                                if (!formalArgumentEnumer.MoveNext() || formalArgumentEnumer.Current.Name != argumentName)
                                {                                  // Too many arguments
                                    result = CreateFaultDocument(402, "Invalid Args");
                                    return(HttpStatusCode.InternalServerError);
                                }
                                object value;
                                if (SoapHelper.ReadNull(reader))
                                {
                                    value = null;
                                }
                                else
                                {
                                    res = formalArgumentEnumer.Current.SoapParseArgument(reader, !subscriberSupportsUPnP11, out value);
                                    if (res != null)
                                    {
                                        result = CreateFaultDocument(res.ErrorCode, res.ErrorDescription);
                                        return(HttpStatusCode.InternalServerError);
                                    }
                                }
                                if (inParameterValues == null)
                                {
                                    inParameterValues = new List <object>();
                                }
                                inParameterValues.Add(value);
                            }
                        }
                        if (formalArgumentEnumer.MoveNext())
                        { // Too few arguments
                            result = CreateFaultDocument(402, "Invalid Args");
                            return(HttpStatusCode.InternalServerError);
                        }
                    }
                IList <object> outParameterValues;
                // Invoke action
                try
                {
                    res = action.InvokeAction(inParameterValues, out outParameterValues, false, context);
                    // outParameterValues can be null if the action has no output parameters. Setting it to an empty list makes
                    // it easier to check parameter count later.
                    if (outParameterValues == null)
                    {
                        outParameterValues = EMPTY_OBJECT_LIST;
                    }
                }
                catch (Exception e)
                {
                    UPnPConfiguration.LOGGER.Warn("SOAPHandler: Error invoking UPnP action '{0}'", e, action.Name);
                    result = CreateFaultDocument(501, "Action Failed");
                    return(HttpStatusCode.InternalServerError);
                }
                if (res != null)
                {
                    result = CreateFaultDocument(res.ErrorCode, res.ErrorDescription);
                    return(HttpStatusCode.InternalServerError);
                }
                // Check output parameters
                IList <DvArgument> formalArguments = action.OutArguments;
                if (outParameterValues.Count != formalArguments.Count)
                {
                    result = CreateFaultDocument(501, "Action Failed");
                    return(HttpStatusCode.InternalServerError);
                }
                IList <OutParameter> outParams = formalArguments.Select((t, i) => new OutParameter(t, outParameterValues[i])).ToList();
                result = CreateResultDocument(action, outParams, !subscriberSupportsUPnP11);
                return(HttpStatusCode.OK);
            }
            catch (Exception e)
            {
                string message = "Error handling SOAP request: " + e.Message;
                UPnPConfiguration.LOGGER.Warn(message);     // Don't log the whole exception; it's only a communication error with a client

                result = CreateFaultDocument(500, message); // Also send message to client
                return(HttpStatusCode.InternalServerError);
            }
        }