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);
          }
        }
      }
    }
Esempio n. 2
0
 /// <summary>
 /// Adds the specified <paramref name="service"/>.
 /// </summary>
 /// <remarks>
 /// Should be done at the time when the UPnP system is not bound to the network yet, but if the binding is already
 /// established, the system will notify a configuration update.
 /// Note that default services (defined by a UPnP Forum working committee) must be added first.
 /// </remarks>
 /// <param name="service">Service to add to this device.</param>
 public void AddService(DvService service)
 {
     service.ParentDevice = this;
     _services.Add(service);
 }
 /// <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. 4
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. 5
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;
 }
Esempio n. 6
0
 /// <summary>
 /// Adds the specified <paramref name="service"/>.
 /// </summary>
 /// <remarks>
 /// Should be done at the time when the UPnP system is not bound to the network yet, but if the binding is already
 /// established, the system will notify a configuration update.
 /// Note that default services (defined by a UPnP Forum working committee) must be added first.
 /// </remarks>
 /// <param name="service">Service to add to this device.</param>
 public void AddService(DvService service)
 {
   service.ParentDevice = this;
   _services.Add(service);
 }
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;
      }
    }
Esempio n. 8
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);
 }