Built a small SSDP Client.

This commit is contained in:
feyris-tan 2025-09-02 22:28:44 +02:00
parent a79c8a5f20
commit 887d983594
4 changed files with 218 additions and 5 deletions

View File

@ -0,0 +1,94 @@
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema
targetNamespace="urn:schemas-upnp-org:device-1-0"
xmlns:tns="urn:schemas-upnp-org:device-1-0"
xmlns="urn:schemas-upnp-org:device-1-0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:dlna="urn:schemas-dlna-org:device-1-0"
attributeFormDefault="qualified" elementFormDefault="qualified">
<xs:annotation>
<xs:documentation>
XML Schema for UPnP device descriptions in real XSD format
(not like the XDR one from Microsoft)
Created by Michael Weinrich 2007
</xs:documentation>
</xs:annotation>
<xs:element name="root">
<xs:complexType>
<xs:all>
<xs:element name="specVersion" type="SpecVersionType" minOccurs="1" maxOccurs="1" />
<xs:element name="URLBase" type="xs:string" minOccurs="0" maxOccurs="1" />
<xs:element name="device" type="DeviceType" minOccurs="1" maxOccurs="1" />
</xs:all>
<xs:anyAttribute/>
</xs:complexType>
</xs:element>
<xs:complexType name="SpecVersionType">
<xs:all>
<xs:element name="major" type="xs:int" minOccurs="1" />
<xs:element name="minor" type="xs:int" minOccurs="1"/>
</xs:all>
</xs:complexType>
<xs:complexType name="DeviceType">
<xs:all>
<xs:element name="deviceType" type="xs:string" minOccurs="1" maxOccurs="1" />
<xs:element name="friendlyName" type="xs:string" minOccurs="1" maxOccurs="1" />
<xs:element name="manufacturer" type="xs:string" minOccurs="1" maxOccurs="1" />
<xs:element name="manufacturerURL" type="xs:string" minOccurs="0" maxOccurs="1" />
<xs:element name="modelDescription" type="xs:string" minOccurs="0" maxOccurs="1" />
<xs:element name="modelName" type="xs:string" minOccurs="1" maxOccurs="1" />
<xs:element name="modelNumber" type="xs:string" minOccurs="0" maxOccurs="1" />
<xs:element name="modelURL" type="xs:string" minOccurs="0" maxOccurs="1" />
<xs:element name="serialNumber" type="xs:string" minOccurs="0" maxOccurs="1" />
<xs:element name="UDN" type="xs:string" minOccurs="1" maxOccurs="1" />
<xs:element name="UPC" type="xs:string" minOccurs="0" maxOccurs="1" />
<xs:element name="iconList" type="IconListType" minOccurs="0" maxOccurs="1" />
<xs:element name="serviceList" type="ServiceListType" minOccurs="0" maxOccurs="1" />
<xs:element name="deviceList" type="DeviceListType" minOccurs="0" maxOccurs="1" />
<xs:element name="presentationURL" type="xs:string" minOccurs="0" maxOccurs="1" />
</xs:all>
</xs:complexType>
<xs:complexType name="IconListType">
<xs:sequence>
<xs:element name="icon" minOccurs="1" maxOccurs="unbounded">
<xs:complexType>
<xs:all>
<xs:element name="mimetype" type="xs:string" minOccurs="1" maxOccurs="1" />
<xs:element name="width" type="xs:int" minOccurs="1" maxOccurs="1" />
<xs:element name="height" type="xs:int" minOccurs="1" maxOccurs="1" />
<xs:element name="depth" type="xs:int" minOccurs="1" maxOccurs="1" />
<xs:element name="url" type="xs:string" minOccurs="1" maxOccurs="1" />
</xs:all>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
<xs:complexType name="ServiceListType">
<xs:sequence>
<xs:element name="service" minOccurs="1" maxOccurs="unbounded">
<xs:complexType>
<xs:all>
<xs:element name="serviceType" type="xs:string" minOccurs="1" maxOccurs="1" />
<xs:element name="serviceId" type="xs:string" minOccurs="1" maxOccurs="1" />
<xs:element name="SCPDURL" type="xs:string" minOccurs="1" maxOccurs="1" />
<xs:element name="controlURL" type="xs:string" minOccurs="1" maxOccurs="1" />
<xs:element name="eventSubURL" type="xs:string" minOccurs="1" maxOccurs="1" />
</xs:all>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
<xs:complexType name="DeviceListType">
<xs:sequence>
<xs:element name="device" type="DeviceType" minOccurs="1" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:schema>

View File

@ -39,7 +39,13 @@ namespace skyscraper5
private static readonly ILog logger = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name);
private static void IntegrationTest()
{
//List<SsdpDevice> ssdpDevices = SsdpClient.GetSsdpDevices(1000).ToList();
List<SsdpDevice> ssdpDevices = SsdpClient.GetSsdpDevices(1000).ToList();
foreach (SsdpDevice ssdpDevice in ssdpDevices)
{
Console.WriteLine("SSDP device: {0}", ssdpDevice.Server);
}
Console.WriteLine("yeet!");
/*RtspClient rtspClient = new RtspClient("172.20.20.121", 554);
rtspClient.AutoReconnect = true;
RtspOptionsResponse options = rtspClient.GetOptions("/");

View File

@ -44,6 +44,7 @@ namespace skyscraper8.SimpleServiceDiscoveryProtocol
DateTime timeStarted = DateTime.Now;
while (true)
{
bool yielded = false;
for (int i = 0; i < udpSockets.Length; i++)
{
if (udpSockets[i] == null)
@ -57,12 +58,16 @@ namespace skyscraper8.SimpleServiceDiscoveryProtocol
if (recvBytes > 0)
{
string s = Encoding.UTF8.GetString(buffer, 0, recvBytes);
Console.WriteLine(s);
//Console.WriteLine(s);
yield return new SsdpDevice(s);
yielded = true;
}
}
}
if (yielded)
continue;
Thread.Sleep(1);
if ((DateTime.Now - timeStarted).TotalMilliseconds >= timeout)
break;

View File

@ -1,17 +1,125 @@
using System;
using log4net;
using skyscraper5.Skyscraper;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
namespace skyscraper8.SimpleServiceDiscoveryProtocol
{
internal class SsdpDevice
internal class SsdpDevice : Validatable
{
private static readonly ILog logger = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name);
public SsdpDevice(string notification)
{
StringReader sr = new StringReader(notification);
throw new NotImplementedException(notification);
string httpHeaderLine = sr.ReadLine();
int indexOf = httpHeaderLine.IndexOf(' ');
string protocol = httpHeaderLine.Substring(0, indexOf);
httpHeaderLine = httpHeaderLine.Substring(indexOf + 1);
if (!protocol.StartsWith("HTTP"))
{
Valid = false;
return;
}
HttpStatusCode = int.Parse(httpHeaderLine.Substring(0, 3));
httpHeaderLine = httpHeaderLine.Substring(4);
string line = httpHeaderLine;
while (!string.IsNullOrEmpty(line = sr.ReadLine()))
{
int seperator = line.IndexOf(": ");
if (seperator == -1)
continue;
string key = line.Substring(0, seperator);
string value = line.Substring(seperator + 2);
key = key.ToLowerInvariant();
switch (key)
{
case "cache-control":
this.CacheControl = value;
break;
case "st":
this.SearchTarget = value;
break;
case "usn":
this.UniqueServiceName = value;
break;
case "server":
Server = value;
break;
case "location":
Location = value;
break;
case "opt":
Options = value;
break;
case "01-nls":
//Same as BOOT ID
break;
case "bootid.upnp.org":
BootId = value;
break;
case "configid.upnp.org":
ConfigId = value;
break;
case "deviceid.ses.com":
SatIpDeviceId = value;
break;
case "date":
Date = value;
break;
case "x-user-agent":
UserAgent = value;
break;
case "content-length":
//Irrelevant. Always 0, because these thingies dont contain any http payload.
break;
default:
logger.WarnFormat("Don't know how to format: {0}", line);
break;
}
}
Valid = true;
}
public string UserAgent { get; set; }
public string Date { get; set; }
public string SatIpDeviceId { get; set; }
public string ConfigId { get; set; }
public string BootId { get; set; }
public string Options { get; set; }
/// <summary>
/// This is an URL to an XML file
/// </summary>
public string Location { get; set; }
/// <summary>
/// This just says what software is running on the device, not what it actually is.
/// </summary>
public string Server { get; set; }
/// <summary>
/// This can only be used to identify one specific device, not a device type.
/// </summary>
public string UniqueServiceName { get; set; }
public string SearchTarget { get; set; }
public string CacheControl { get; private set; }
public int HttpStatusCode { get; private set; }
}
}