Camera Information System
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

180 lines
7.7 KiB

using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;
using System.Xml.Serialization;
namespace EC.Helper.Onvif.RemoteDiscovery;
public class Discovery
{
#region Discover
/// <summary>
/// Discover new onvif devices on the network
/// </summary>
/// <param name="timeout">A timeout in seconds to wait for onvif devices</param>
/// <param name="cancellationToken">A cancellation token</param>
/// <returns>a list of <see cref="DiscoveryDevice"/></returns>
/// <remarks>Use the <see cref="Discover(int, Action{DiscoveryDevice}, CancellationToken)"/>
/// overload (with an action as a parameter) if you want to retrieve devices as they reply.</remarks>
public async Task<IEnumerable<DiscoveryDevice>> DiscoverByAdapter(IEnumerable<NetworkInterface> adapterList,
int timeout, CancellationToken cancellationToken = default)
{
List<DiscoveryDevice> deviceList = new();
await Discover(adapterList, d => deviceList.Add(d), timeout, cancellationToken);
deviceList.Sort((a, b) => CastIp(a.Address).CompareTo(CastIp(b.Address)));
return deviceList;
}
/// <summary>
/// Discover new onvif devices on the network
/// </summary>
/// <param name="timeout">A timeout in seconds to wait for onvif devices</param>
/// <param name="cancellationToken">A cancellation token</param>
/// <returns>a list of <see cref="DiscoveryDevice"/></returns>
/// <remarks>Use the <see cref="Discover(int, Action{DiscoveryDevice}, CancellationToken)"/>
/// overload (with an action as a parameter) if you want to retrieve devices as they reply.</remarks>
public async Task<IEnumerable<DiscoveryDevice>> DiscoverAll(int timeout, CancellationToken cancellationToken = default)
{
IEnumerable<NetworkInterface> adapterList = OnvifUdpClient.GetVaildNetworkAdapters();
IEnumerable<DiscoveryDevice> deviceList = await DiscoverByAdapter(adapterList, timeout, cancellationToken);
return deviceList;
}
/// <summary>
/// Discover new onvif devices on the network passing a callback
/// to retrieve devices as they reply
/// </summary>
/// <param name="onDeviceDiscovered">A method that is called each time a new device replies.</param>
/// <param name="timeout">A timeout in seconds to wait for onvif devices</param>
/// <param name="cancellationToken">A cancellation token</param>
/// <returns>The Task to be awaited</returns>
private async Task Discover(IEnumerable<NetworkInterface> adapterList, Action<DiscoveryDevice> onDeviceDiscovered,
int timeout, CancellationToken cancellationToken = default)
{
IEnumerable<OnvifUdpClient> clientList = OnvifUdpClient.CreateClientList(adapterList);
if (!clientList.Any())
{
throw new Exception("Missing valid NetworkInterfaces, UdpClients could not be created");
}
Task[] discoveries = clientList.Select(client => Discover(client, onDeviceDiscovered, timeout, cancellationToken)).ToArray();
await Task.WhenAll(discoveries);
}
private async Task Discover(OnvifUdpClient client, Action<DiscoveryDevice> onDeviceDiscovered, int timeout, CancellationToken cancellationToken = default)
{
Guid messageId = Guid.NewGuid();
List<UdpReceiveResult> responseList = new();
CancellationTokenSource cts = new(TimeSpan.FromSeconds(timeout));
try
{
await OnvifUdpClient.SendProbe(client, messageId);
while (true)
{
if (client.IsClosed) { return; }
if (cts.IsCancellationRequested || cancellationToken.IsCancellationRequested) { break; }
try
{
UdpReceiveResult response = await client.ReceiveAsync().WithCancellation(cancellationToken).WithCancellation(cts.Token);
if (responseList.Exists(resp => resp.RemoteEndPoint.Address.Equals(response.RemoteEndPoint.Address))) { continue; }
responseList.Add(response);
DiscoveryDevice discoveredDevice = ProcessResponse(response, messageId);
if (discoveredDevice != null)
{
await Task.Run(() => onDeviceDiscovered(discoveredDevice), cancellationToken);
}
}
catch (OperationCanceledException)
{
// Either the user canceled the action or the timeout has fired
}
catch (Exception)
{
// we catch all exceptions !
// Something might be bad in the response of a camera when call ReceiveAsync (BeginReceive in socket) fail
}
}
}
finally
{
client.Close();
}
}
#endregion Discover
#region Discover Helper
private DiscoveryDevice ProcessResponse(UdpReceiveResult response, Guid messageId)
{
if (response.Buffer == null) { return null; }
string strResponse = Encoding.UTF8.GetString(response.Buffer);
XmlProbeReponse xmlResponse = DeserializeResponse(strResponse);
if (xmlResponse.Header.RelatesTo.Contains(messageId.ToString())
&& xmlResponse.Body.ProbeMatches.Any()
&& !string.IsNullOrEmpty(xmlResponse.Body.ProbeMatches[0].Scopes))
{
return CreateDevice(xmlResponse.Body.ProbeMatches[0], response.RemoteEndPoint);
}
return null;
}
private XmlProbeReponse DeserializeResponse(string xml)
{
XmlSerializer serializer = new(typeof(XmlProbeReponse));
XmlReaderSettings settings = new();
using StringReader textReader = new(xml);
using XmlReader xmlReader = XmlReader.Create(textReader, settings);
return (XmlProbeReponse)serializer.Deserialize(xmlReader);
}
private DiscoveryDevice CreateDevice(ProbeMatch probeMatch, IPEndPoint remoteEndpoint)
{
string scopes = probeMatch.Scopes;
DiscoveryDevice discoveryDevice = new();
discoveryDevice.Address = remoteEndpoint.Address.ToString();
discoveryDevice.Model = Regex.Match(scopes, "(?<=hardware/).*?(?= )")?.Value;
discoveryDevice.Mfr = ParseMfrFromScopes(scopes);
discoveryDevice.XAdresses = ConvertToList(probeMatch.XAddrs);
discoveryDevice.Types = ConvertToList(probeMatch.Types);
return discoveryDevice;
}
private string ParseMfrFromScopes(string scopes)
{
string name = Regex.Match(scopes, "(?<=name/).*?(?= )")?.Value;
if (!string.IsNullOrEmpty(name)) { return name; }
string mfr = Regex.Match(scopes, "(?<=mfr/).*?(?= )")?.Value;
if (!string.IsNullOrEmpty(mfr)) { return mfr; }
return string.Empty;
}
private List<string> ConvertToList(string spacedListString)
{
string[] strings = spacedListString.Split(null);
List<string> list = new();
strings.ToList().ForEach(str => list.Add(str.Trim()));
return list;
}
private long CastIp(string ip)
{
byte[] addressBytes = IPAddress.Parse(ip).GetAddressBytes();
if (addressBytes.Length != 4)
{
return 0;
throw new ArgumentException("Must be an IPv4 address");
}
uint networkOrder = BitConverter.ToUInt32(addressBytes, 0);
return networkOrder;
//byte[] addressBytes = address.GetAddressBytes();
//int networkOrder = BitConverter.ToInt32(addressBytes, 0);
//return (uint)IPAddress.NetworkToHostOrder(networkOrder);
}
#endregion Discover Helper
}