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.
187 lines
7.0 KiB
187 lines
7.0 KiB
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Net;
|
|
using System.Net.NetworkInformation;
|
|
using System.Net.Sockets;
|
|
using System.Text;
|
|
using System.Text.RegularExpressions;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using System.Xml;
|
|
using System.Xml.Serialization;
|
|
|
|
namespace EC.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
|
|
}
|
|
}
|