using Infrastructure.Extensions; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; using System.Net.Sockets; using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; namespace IoT.Shared.DeviceServices.Onvif { public class OnvifDeviceManagement : IOnvifDeviceManagement, IDisposable { private readonly IHttpClientFactory _httpClientFactory; private readonly object thisLock = new object(); private readonly UdpClient _client = new UdpClient(); private readonly CancellationTokenSource _tokenSource = new CancellationTokenSource(); public Action Callback { get; set; } public OnvifDeviceManagement(IHttpClientFactory httpClientFactory) { this._httpClientFactory = httpClientFactory; Task.Run(() => { try { var ep = new IPEndPoint(IPAddress.Any, 3702); while (!_tokenSource.IsCancellationRequested) { if (_client.Available > 0) { var buffer = _client.Receive(ref ep); var result = Encoding.UTF8.GetString(buffer); var camera = new IPCamera { DiscoveryXml = result }; try { camera.ParseDiscovery(); camera.GetCapabilitiesXml = GetCapabilities(camera.DeviceUrl); camera.ParseCapabilities(); this.Callback(camera); } catch (Exception ex) { ex.PrintStack(); } } Thread.Sleep(1); } } catch (Exception ex) { Console.WriteLine(ex.Message); } }, _tokenSource.Token); Console.WriteLine("OnvifDeviceManagement init complete"); } public void Discovery() { Console.WriteLine("search onvif devices..."); var list = new ConcurrentBag(); lock (thisLock) { try { var probeMessage = string.Format(Template.GetServiceRequestTemplage, Guid.NewGuid()); var message = Encoding.UTF8.GetBytes(probeMessage); _client.Send(message, message.Length, new IPEndPoint(IPAddress.Parse("239.255.255.250"), 3702)); } catch (Exception ex) { ex.PrintStack(); } } } public string GetCapabilities(string deviceUrl) { return SoapRequest(deviceUrl, Template.GetCapabilitiesAction, Template.GetCapabilitiesMessage); } public string GetProfiles(string deviceUrl, string mediaUrl) { return SoapRequest(mediaUrl, Template.GetProfilesAction, string.Format(Template.Templage, Template.GetProfilesMessageBody)); } public string GetProfiles(string deviceUrl, string mediaUrl, string userName, string password) { return RequestXml(mediaUrl, Template.GetProfilesAction, Template.GetProfilesMessageBody, userName, password, GetOnoce(deviceUrl)); } public string GetStreamUri(string deviceUrl, string mediaUrl, string token) { return SoapRequest(mediaUrl, Template.GetStreamUriAction, string.Format(Template.Templage, String.Format(Template.GetStreamUriMessage, token))); } public string GetStreamUri(string deviceUrl, string mediaUrl, string userName, string password, string token) { return RequestXml(mediaUrl, Template.GetStreamUriAction, String.Format(Template.GetStreamUriMessage, token), userName, password, GetOnoce(deviceUrl)); } public string GetSnapshotUri(string deviceUrl, string mediaUrl, string token) { return SoapRequest(mediaUrl, Template.GetSnapshotUriAction, string.Format(Template.Templage, String.Format(Template.GetSnapshotUriMessage, token))); } public string GetSnapshotUri(string deviceUrl, string mediaUrl, string userName, string password, string token) { return RequestXml(mediaUrl, Template.GetSnapshotUriAction, String.Format(Template.GetSnapshotUriMessage, token), userName, password, GetOnoce(deviceUrl)); } private string SoapRequest(string url, string action, string message) { var hc = this._httpClientFactory.CreateClient(); hc.DefaultRequestHeaders.Add("ContentType", $"application/soap+xml; charset=utf-8; action=\"{action}\""); var task = hc.PostAsync(url, new StringContent(message)); var result = task.Result; var content = result.Content.ReadAsStringAsync().Result; return result.StatusCode == HttpStatusCode.OK ? content : ""; } private string RequestXml(string url, string action, string body, string userName, string password, string onoce) { var nonce_b = Convert.FromBase64String(onoce); var now = DateTime.Now.ToUniversalTime().ToString("yyyy-MM-ddThh:mm:ss.fffZ"); var creationtime_b = Encoding.ASCII.GetBytes(now); var password_b = Encoding.ASCII.GetBytes(password); var concatenation_b = new byte[nonce_b.Length + creationtime_b.Length + password_b.Length]; Buffer.BlockCopy(nonce_b, 0, concatenation_b, 0, nonce_b.Length); Buffer.BlockCopy(creationtime_b, 0, concatenation_b, nonce_b.Length, creationtime_b.Length); Buffer.BlockCopy(password_b, 0, concatenation_b, nonce_b.Length + creationtime_b.Length, password_b.Length); var sha = new SHA1CryptoServiceProvider(); var pdresult = sha.ComputeHash(concatenation_b); var passworddigest = Convert.ToBase64String(pdresult); var message = string.Format(Template.AuthTemplate, userName, passworddigest, Convert.ToBase64String(nonce_b), now, body); var result = SoapRequest(url, action, message); return result; } private string GetOnoce(string deviceUrl) { var message = @" "; var hc = this._httpClientFactory.CreateClient(); hc.DefaultRequestHeaders.Add("ContentType", $"application/soap+xml; charset=utf-8; action=\"{Template.GetDeviceInformationAction}\""); var task = hc.PostAsync(deviceUrl, new StringContent(message)); var result = task.Result.Headers.WwwAuthenticate; return Regex.Match(result.ToString(), "nonce=\"([^\"]+)\"").Groups[1].Value; } public void Dispose() { this._tokenSource.Cancel(); this._client.Dispose(); } } }