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