/// <summary> /// Sends a NOTIFY packet "ssdp:update" to all UPnP endpoints. /// </summary> /// <param name="NT">Notification type.</param> /// <param name="USN">Unique Service Name.</param> /// <param name="rootDevice">Root device for that the message should be send.</param> public void SendMessage(string NT, string USN, DvDevice rootDevice) { SimpleHTTPRequest response = new SimpleHTTPRequest("NOTIFY", "*"); response.SetHeader("NT", NT); response.SetHeader("NTS", "ssdp:update"); response.SetHeader("USN", USN); response.SetHeader("BOOTID.UPNP.ORG", _lastBootId.ToString()); response.SetHeader("NEXTBOOTID.UPNP.ORG", _nextBootId.ToString()); // Currently, we don't support SEARCHPORT.UPNP.ORG function and header foreach (EndpointConfiguration config in _serverData.UPnPEndPoints) { if (config.AddressFamily == AddressFamily.InterNetworkV6) { response.SetHeader("OPT", "\"http://schemas.upnp.org/upnp/1/0/\"; ns=01"); response.SetHeader("01-NLS", _serverData.BootId.ToString()); } response.SetHeader("CONFIGID.UPNP.ORG", config.ConfigId.ToString()); IPEndPoint ep = new IPEndPoint(config.SSDPMulticastAddress, UPnPConsts.SSDP_MULTICAST_PORT); response.SetHeader("HOST", NetworkHelper.IPEndPointToString(ep)); if (config.SSDPUsesSpecialSearchPort) response.SetHeader("SEARCHPORT.UPNP.ORG", config.SSDPSearchPort.ToString()); response.SetHeader("LOCATION", config.GetRootDeviceDescriptionURL(rootDevice)); byte[] bytes = response.Encode(); NetworkHelper.MulticastMessage(config.SSDP_UDP_UnicastSocket, config.SSDPMulticastAddress, bytes); } }
/// <summary> /// Parses the HTTP request out of the given <paramref name="stream"/>. /// </summary> /// <param name="stream">HTTP data stream to parse.</param> /// <param name="result">Returns the parsed HTTP request instance.</param> /// <exception cref="MediaPortal.Utilities.Exceptions.InvalidDataException">If the given <paramref name="stream"/> is malformed.</exception> public static void Parse(Stream stream, out SimpleHTTPRequest result) { result = new SimpleHTTPRequest(); string firstLine; result.ParseHeaderAndBody(stream, out firstLine); string[] elements = firstLine.Split(' '); if (elements.Length != 3) throw new InvalidDataException("Invalid HTTP request header starting line '{0}'", firstLine); string httpVersion = elements[2]; if (httpVersion != "HTTP/1.0" && httpVersion != "HTTP/1.1") throw new InvalidDataException("Invalid HTTP request header starting line '{0}'", firstLine); result._method = elements[0]; result._param = elements[1]; result._httpVersion = httpVersion; }
/// <summary> /// Parses the HTTP request out of the given <paramref name="stream"/>. /// </summary> /// <param name="stream">HTTP data stream to parse.</param> /// <param name="result">Returns the parsed HTTP request instance.</param> /// <exception cref="MediaPortal.Utilities.Exceptions.InvalidDataException">If the given <paramref name="stream"/> is malformed.</exception> public static void Parse(Stream stream, out SimpleHTTPRequest result) { result = new SimpleHTTPRequest(); string firstLine; result.ParseHeaderAndBody(stream, out firstLine); string[] elements = firstLine.Split(' '); if (elements.Length != 3) { throw new InvalidDataException("Invalid HTTP request header starting line '{0}'", firstLine); } string httpVersion = elements[2]; if (httpVersion != "HTTP/1.0" && httpVersion != "HTTP/1.1") { throw new InvalidDataException("Invalid HTTP request header starting line '{0}'", firstLine); } result._method = elements[0]; result._param = elements[1]; result._httpVersion = httpVersion; }
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); } } } }
protected void HandleNotifyRequest(SimpleHTTPRequest header, EndpointConfiguration config, IPEndPoint remoteEndPoint) { if (header.Param != "*") // Invalid message return; HTTPVersion httpVersion; if (!HTTPVersion.TryParse(header.HttpVersion, out httpVersion)) // Invalid message return; // HOST not interesting //string host = header["HOST"]; string cacheControl = header["CACHE-CONTROL"]; string location = header["LOCATION"]; string server = header["SERVER"]; // NT is not evaluated, we get all information from the USN header //string nt = header["NT"]; string nts = header["NTS"]; string usn = header["USN"]; string bi = header["BOOTID.UPNP.ORG"]; string ci = header["CONFIGID.UPNP.ORG"]; string sp = header["SEARCHPORT.UPNP.ORG"]; HandleNotifyPacket(config, remoteEndPoint, httpVersion, DateTime.Now.ToUniversalTime().ToString("R"), cacheControl, location, server, nts, usn, bi, ci, sp); }
protected void HandleUpdatePacket(SimpleHTTPRequest header, EndpointConfiguration config) { if (header.Param != "*") // Invalid message return; HTTPVersion httpVersion; if (!HTTPVersion.TryParse(header.HttpVersion, out httpVersion)) // Invalid message return; // Host, NT, NTS, USN are not interesting //string host = header["HOST"]; //string nt = header["NT"]; //string nts = header["NTS"]; string usn = header["USN"]; //string location = header["LOCATION"]; string bi = header["BOOTID.UPNP.ORG"]; uint bootID; if (!uint.TryParse(bi, out bootID)) // Invalid message return; string nbi = header["NEXTBOOTID.UPNP.ORG"]; uint nextBootID; if (!uint.TryParse(nbi, out nextBootID)) // Invalid message return; if (!usn.StartsWith("uuid:")) // Invalid usn return; int separatorIndex = usn.IndexOf("::"); if (separatorIndex < 6) // separatorIndex == -1 or separatorIndex not after "uuid:" prefix with at least one char UUID // We only use messages containing a "::" substring and discard the "uuid:device-UUID" message return; string deviceUUID = usn.Substring(5, separatorIndex - 5); RootEntry rootEntry = GetRootEntryByContainedDeviceUUID(deviceUUID); if (rootEntry == null) return; if (rootEntry.BootID > bootID) // Invalid message return; bool fireDeviceRebooted = false; lock (_cpData.SyncObj) { if (rootEntry.BootID < bootID) // Device reboot fireDeviceRebooted = true; rootEntry.BootID = nextBootID; } if (fireDeviceRebooted) InvokeDeviceRebooted(rootEntry, false); }
protected void HandleSSDPRequest(SimpleHTTPRequest header, EndpointConfiguration config, IPEndPoint remoteEndPoint) { switch (header.Method) { case "NOTIFY": HandleNotifyRequest(header, config, remoteEndPoint); break; case "UPDATE": HandleUpdatePacket(header, config); break; } }
protected void MulticastSearchForST(string st) { SimpleHTTPRequest request = new SimpleHTTPRequest("M-SEARCH", "*"); request.SetHeader("MAN", "\"ssdp:discover\""); request.SetHeader("MX", UPnPConfiguration.SEARCH_MX.ToString()); request.SetHeader("ST", st); request.SetHeader("USER-AGENT", UPnPConfiguration.UPnPMachineInfoHeader); lock (_cpData.SyncObj) { foreach (EndpointConfiguration config in _cpData.Endpoints) { IPEndPoint ep = new IPEndPoint(config.SSDPMulticastAddress, UPnPConsts.SSDP_MULTICAST_PORT); request.SetHeader("HOST", NetworkHelper.IPEndPointToString(ep)); Socket socket = config.SSDP_UDP_UnicastSocket; if (socket == null) continue; byte[] bytes = request.Encode(); NetworkHelper.MulticastMessage(socket, config.SSDPMulticastAddress, bytes); // The server will send the answer to the same socket as we use to send } } }
protected void UnicastSearchForST(string st, IPEndPoint endPoint) { SimpleHTTPRequest request = new SimpleHTTPRequest("M-SEARCH", "*"); request.SetHeader("HOST", NetworkHelper.IPEndPointToString(endPoint)); request.SetHeader("MAN", "\"ssdp:discover\""); request.SetHeader("ST", st); request.SetHeader("USER-AGENT", UPnPConfiguration.UPnPMachineInfoHeader); lock (_cpData.SyncObj) { foreach (EndpointConfiguration config in _cpData.Endpoints) { if (config.AddressFamily != endPoint.AddressFamily) continue; Socket socket = config.SSDP_UDP_UnicastSocket; if (socket == null) return; byte[] bytes = request.Encode(); NetworkHelper.SendData(socket, endPoint, bytes, 1); // The server will send the answer to the same socket as we use to send return; } } }
/// <summary> /// Handles SSDP M-SEARCH requests over UDP multicast and unicast. /// </summary> /// <param name="header">HTTP request header of the request to handle.</param> /// <param name="config">Endpoint configuration which received the request.</param> /// <param name="remoteEP">Remote endpoint which sent the request.</param> protected void HandleSSDPRequest(SimpleHTTPRequest header, EndpointConfiguration config, IPEndPoint remoteEP) { switch (header.Method) { case "M-SEARCH": if (header.Param != "*" || header["MAN"] != "\"ssdp:discover\"") throw new InvalidDataException("Unsupported Request"); // We don't make a difference between multicast and unicast search requests here, // the only difference is the existance of the MX header field. // If it is present, we simply use this random delay for the answer, and if it is // not present, we send the answer at once. int mx; // Max. seconds to delay response if (!int.TryParse(header["MX"], out mx)) mx = 0; else if (mx < 1) // Malformed request throw new InvalidDataException("Invalid MX header value"); if (mx > 5) mx = 5; // Should be bounded to 5, according to (DevArch) string st = header["ST"]; // Search target if (header.ContainsHeader("USER-AGENT")) // Optional CheckUserAgentUPnPVersion(header["USER-AGENT"]); DelaySearchResponse(new PendingSearchRequest(st, config, remoteEP), mx); break; } }
public void HandleMulticastEventNotification(SimpleHTTPRequest request) { string nt = request["NT"]; string nts = request["NTS"]; string sid = request["SID"]; string usn = request["USN"]; string svcid = request["SVCID"]; string contentType = request["CONTENT-TYPE"]; lock (_cpData.SyncObj) { CpService service; if (nt != "upnp:event" || nts != "upnp:propchange" || string.IsNullOrEmpty(sid) || !TryGetService(usn, svcid, out service)) return; Encoding contentEncoding; string mediaType; if (!EncodingUtils.TryParseContentTypeEncoding(contentType, Encoding.UTF8, out mediaType, out contentEncoding) || mediaType != "text/xml") return; Stream stream = new MemoryStream(request.MessageBody); HandleEventNotification(stream, contentEncoding, service, _upnpVersion); } }