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 /// /// Discover new onvif devices on the network /// /// A timeout in seconds to wait for onvif devices /// A cancellation token /// a list of /// Use the /// overload (with an action as a parameter) if you want to retrieve devices as they reply. public async Task> DiscoverByAdapter(IEnumerable adapterList, int timeout, CancellationToken cancellationToken = default) { List deviceList = new(); await Discover(adapterList, d => deviceList.Add(d), timeout, cancellationToken); deviceList.Sort((a, b) => CastIp(a.Address).CompareTo(CastIp(b.Address))); return deviceList; } /// /// Discover new onvif devices on the network /// /// A timeout in seconds to wait for onvif devices /// A cancellation token /// a list of /// Use the /// overload (with an action as a parameter) if you want to retrieve devices as they reply. public async Task> DiscoverAll(int timeout, CancellationToken cancellationToken = default) { IEnumerable adapterList = OnvifUdpClient.GetVaildNetworkAdapters(); IEnumerable deviceList = await DiscoverByAdapter(adapterList, timeout, cancellationToken); return deviceList; } /// /// Discover new onvif devices on the network passing a callback /// to retrieve devices as they reply /// /// A method that is called each time a new device replies. /// A timeout in seconds to wait for onvif devices /// A cancellation token /// The Task to be awaited private async Task Discover(IEnumerable adapterList, Action onDeviceDiscovered, int timeout, CancellationToken cancellationToken = default) { IEnumerable 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 onDeviceDiscovered, int timeout, CancellationToken cancellationToken = default) { Guid messageId = Guid.NewGuid(); List 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 ConvertToList(string spacedListString) { string[] strings = spacedListString.Split(null); List 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 }