2025-09-09 22:08:03 +02:00

193 lines
5.9 KiB
C#

using log4net;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Serialization;
using skyscraper8.SatIp;
using skyscraper8.Ssdp.Schema;
namespace skyscraper8.SimpleServiceDiscoveryProtocol
{
internal class SsdpClient
{
private static readonly ILog logger = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name);
private const string SEARCH_STRING =
"M-SEARCH * HTTP/1.1\r\n" +
"HOST:239.255.255.250:1900\r\n" +
"MAN:\"ssdp:discover\"\r\n" +
"ST:ssdp:all\r\n" +
"MX:3\r\n\r\n";
public static IEnumerable<SsdpDevice> GetSsdpDevices(int timeout)
{
NetworkInterface[] networkInterfaces = NetworkInterface.GetAllNetworkInterfaces();
IPAddress[] ipAddresses = Array.ConvertAll(networkInterfaces, x => GetIPAddress(x));
IPEndPoint[] localEndPoints = Array.ConvertAll(ipAddresses, x => new IPEndPoint(x, 1901));
IPEndPoint remoteEndPoint = new IPEndPoint(IPAddress.Parse("239.255.255.250"), 1900);
Socket[] udpSockets = Array.ConvertAll(localEndPoints,
x =>
{
byte[] addressBytes = x.Address.GetAddressBytes();
if (addressBytes[0] == 169)
return null;
Socket temp = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
temp.Bind(x);
temp.SendTo(Encoding.UTF8.GetBytes(SEARCH_STRING), SocketFlags.None, remoteEndPoint);
return temp;
});
byte[] buffer = new byte[1600];
int recvBytes = 0;
DateTime timeStarted = DateTime.Now;
while (true)
{
bool yielded = false;
for (int i = 0; i < udpSockets.Length; i++)
{
if (udpSockets[i] == null)
continue;
Array.Clear(buffer);
recvBytes = 0;
if (udpSockets[i].Available > 0)
{
recvBytes = udpSockets[i].Receive(buffer, SocketFlags.None);
if (recvBytes > 0)
{
string s = Encoding.UTF8.GetString(buffer, 0, recvBytes);
//Console.WriteLine(s);
yield return new SsdpDevice(s);
yielded = true;
}
}
}
if (yielded)
continue;
Thread.Sleep(1);
if ((DateTime.Now - timeStarted).TotalMilliseconds >= timeout)
break;
}
yield break;
}
private static IPAddress GetIPAddress(NetworkInterface networkInterface)
{
IPInterfaceProperties ipProperties = networkInterface.GetIPProperties();
UnicastIPAddressInformationCollection addresses = ipProperties.UnicastAddresses;
UnicastIPAddressInformation[] addressArray = addresses.ToArray();
Array.Sort(addressArray, (a, b) => RateIpAddress(b).CompareTo(RateIpAddress(a)));
UnicastIPAddressInformation address = addressArray[0];
return address.Address;
}
private static int RateIpAddress(UnicastIPAddressInformation address)
{
IPAddress ipAddress = address.Address;
byte[] addressBytes = ipAddress.GetAddressBytes();
if (addressBytes.Length == 0x10 && addressBytes[0] == 0xfe && addressBytes[1] == 0x80)
{
return 1; //Link local IPv6 is okay, but we prefer IPv4.
}
if (addressBytes.Length == 4 && addressBytes[0] == 169 && addressBytes[1] == 254)
{
return 2; //Auto-Assigned Link-Local IPv4, this is okay,
}
if (addressBytes.Length == 4 && addressBytes[0] == 192 && addressBytes[1] == 168)
{
return 3; //A non-auto-assigned Link-Local. This is what we want.
}
if (addressBytes.Length == 4 && addressBytes[0] == 172 && (addressBytes[1] >= 16 && addressBytes[1] <= 31))
{
return 3; //A non-auto-assigned Link-Local. This is what we want.
}
if (addressBytes.Length == 4 && addressBytes[0] == 127)
{
return Int32.MinValue + 1; //A loopback address. We don't want these.
}
if (addressBytes.Length == 16 && addressBytes[0] == 0 && addressBytes[15] == 1)
{
return Int32.MinValue; //An IPv6 loopback. That's even less wanted.
}
logger.WarnFormat("Don't know how to rate IP address", address.Address.ToString());
return Int32.MinValue;
}
public static List<SsdpDevice> GetSsdpDevices(int timeout = 1000, string searchTarget = null, IRtspCache cache = null)
{
List<SsdpDevice> inputList = GetSsdpDevices(timeout).ToList();
List<SsdpDevice> outputList = new List<SsdpDevice>();
WebClient webClient = new WebClient();
foreach (SsdpDevice ssdpDevice in inputList)
{
if (!string.IsNullOrEmpty(searchTarget))
{
if (!searchTarget.Equals(ssdpDevice.SearchTarget))
{
continue;
}
}
skyscraper8.Ssdp.Schema.root ssdpMetadata = null;
if (cache != null)
{
if (cache.SsdpDeviceKnown(ssdpDevice))
{
byte[] ssdpMetadataByteArray = cache.SsdpGetMetadata(ssdpDevice);
ssdpMetadata = UnpackSsdpMetadata(ssdpMetadataByteArray);
}
}
if (ssdpMetadata == null)
{
byte[] ssdpMetadataByteArray = webClient.DownloadData(ssdpDevice.Location);
cache?.SsdpStoreMetadata(ssdpDevice, ssdpMetadataByteArray);
ssdpMetadata = UnpackSsdpMetadata(ssdpMetadataByteArray);
}
ssdpDevice.DeviceMetadata = ssdpMetadata;
if (ssdpMetadata != null)
{
logger.Debug(String.Format("{2} is a {0} {1}", ssdpMetadata.device.manufacturer, ssdpMetadata.device.modelName,ssdpDevice.Server));
}
outputList.Add(ssdpDevice);
}
return outputList;
}
public static SsdpDevice GetFirstSatIpServer(int timeout = 1000, IRtspCache cache = null)
{
List<SsdpDevice> ssdpDevices = GetSsdpDevices(1000, "urn:ses-com:device:SatIPServer:1", cache);
if (ssdpDevices.Count == 0)
return null;
return ssdpDevices.First();
}
private static XmlSerializer xmlSerializer;
private static skyscraper8.Ssdp.Schema.root UnpackSsdpMetadata(byte[] xmlBuffer)
{
if (xmlSerializer == null)
xmlSerializer = new XmlSerializer(typeof(root));
MemoryStream ms = new MemoryStream(xmlBuffer);
object deserialize = xmlSerializer.Deserialize(ms);
ms.Dispose();
return (skyscraper8.Ssdp.Schema.root)deserialize;
}
}
}