193 lines
5.9 KiB
C#
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;
|
|
}
|
|
|
|
}
|
|
}
|