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.
iot/projects/IoT/IoT.Shared/DeviceServices/Onvif/OnvifDeviceManagement.cs

165 lines
7.6 KiB

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<IPCamera> 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<string>();
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 = @"<s:Envelope xmlns:s=""http://www.w3.org/2003/05/soap-envelope"">
<s:Body xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"" xmlns:xsd=""http://www.w3.org/2001/XMLSchema"">
<GetDeviceInformation xmlns=""http://www.onvif.org/ver10/device/wsdl""></GetDeviceInformation>
</s:Body>
</s:Envelope>";
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();
}
}
}