using System; using System.IO; using System.IO.Compression; using System.Net.Security; using System.ServiceModel; using System.ServiceModel.Channels; using System.Text; using System.Threading; using System.Xml; namespace AVEVA.Historian.WcfCaptureServer; internal static class Program { private static int Main(string[] args) { int port = args.Length > 0 && int.TryParse(args[0], out int parsedPort) ? parsedPort : 33268; string hostName = args.Length > 1 && !string.IsNullOrWhiteSpace(args[1]) ? args[1] : "localhost"; Uri baseAddress = new($"net.tcp://{hostName}:{port}/"); using ServiceHost host = new(typeof(HistoryCaptureService), baseAddress); host.AddServiceEndpoint(typeof(IHistoryServiceContract2), MdasBinding.Create(TimeSpan.FromSeconds(30)), "Hist"); host.AddServiceEndpoint(typeof(IHistoryServiceContract2), MdasBinding.CreateWindows(TimeSpan.FromSeconds(30)), "Hist-Integrated"); host.AddServiceEndpoint(typeof(IRetrievalServiceContract4), MdasBinding.Create(TimeSpan.FromSeconds(30)), "Retr"); host.Open(); Console.WriteLine($"READY net.tcp://{hostName}:{port}/Hist"); Console.WriteLine($"READY net.tcp://{hostName}:{port}/Hist-Integrated"); Console.WriteLine($"READY net.tcp://{hostName}:{port}/Retr"); Console.Out.Flush(); Thread.Sleep(Timeout.Infinite); return 0; } } [ServiceContract(Name = "Hist", Namespace = "aa")] public interface IHistoryServiceContract { [OperationContract(Name = "GetV")] uint GetInterfaceVersion(out uint version); [OperationContract(Name = "Open")] uint OpenConnection( string HostName, string ProcessName, uint ProcessId, string UserName, byte[] Password, ushort pwdLength, byte clientType, ushort clientVersion, uint ConnectionMode, uint ConnectionTimeout, ref string StorageSessionId, out uint Handle, out long ConnectTime, out uint ServerStatus); [OperationContract(Name = "Close")] uint CloseConnection(uint handle); [OperationContract(Name = "AddT")] uint AddTags(uint handle, uint elementCount, uint inByteCount, byte[] inputBuffer, out uint outByteCount, out byte[] outputBuffer); [OperationContract(Name = "RTag")] uint RegisterTags(uint handle, uint elementCount, uint inByteCount, byte[] inputBuffer, out uint outByteCount, out byte[] outputBuffer); } [ServiceContract(Name = "Hist", Namespace = "aa")] public interface IHistoryServiceContract2 : IHistoryServiceContract { [OperationContract(Name = "Open2")] bool OpenConnection2(ref byte[] inParameters, out byte[] outParameters, out byte[] err); [OperationContract(Name = "RTag2")] bool RegisterTags2(string handle, uint elementCount, byte[] inputBuffer, out byte[] outputBuffer, out byte[] errorBuffer); } [ServiceContract(Name = "Retr", Namespace = "aa")] public interface IRetrievalServiceContract { [OperationContract(Name = "GetV")] uint GetInterfaceVersion(out uint version); [OperationContract] uint IsOriginalAllowed(uint clientHandle, out bool isAllowed); } [ServiceContract(Name = "Retr", Namespace = "aa")] public interface IRetrievalServiceContract2 : IRetrievalServiceContract { [OperationContract] bool StartQuery2( uint clientHandle, ushort queryRequestType, uint requestSize, byte[] requestBuffer, out uint responseSize, out byte[] responseBuffer, ref uint queryHandle, out uint errorSize, out byte[] errorBuffer); [OperationContract] bool GetNextQueryResultBuffer2( uint clientHandle, uint queryHandle, out uint resultSize, out byte[] resultBuffer, out uint errorSize, out byte[] errorBuffer); [OperationContract] bool EndQuery2(uint clientHandle, uint queryHandle, out uint errorSize, out byte[] errorBuffer); } [ServiceContract(Name = "Retr", Namespace = "aa")] public interface IRetrievalServiceContract3 : IRetrievalServiceContract2 { [OperationContract] uint StartQuery( uint clientHandle, ushort queryRequestType, uint requestSize, byte[] requestBuffer, out uint responseSize, out byte[] responseBuffer, ref uint queryHandle); [OperationContract] uint GetNextQueryResultBuffer( uint clientHandle, uint queryHandle, out uint resultSize, out byte[] resultBuffer); [OperationContract] uint EndQuery(uint clientHandle, uint queryHandle); } [ServiceContract(Name = "Retr", Namespace = "aa")] public interface IRetrievalServiceContract4 : IRetrievalServiceContract3 { [OperationContract] bool StartEventQuery( uint clientHandle, ushort queryRequestType, uint requestSize, byte[] requestBuffer, out uint responseSize, out byte[] responseBuffer, ref uint queryHandle, out uint errorSize, out byte[] errorBuffer); [OperationContract] bool GetNextEventQueryResultBuffer( uint clientHandle, uint queryHandle, out uint resultSize, out byte[] resultBuffer, out uint errorSize, out byte[] errorBuffer); [OperationContract] bool EndEventQuery(uint clientHandle, uint queryHandle, out uint errorSize, out byte[] errorBuffer); } public sealed class HistoryCaptureService : IHistoryServiceContract2, IRetrievalServiceContract4 { private const uint Handle = 1234; public uint GetInterfaceVersion(out uint version) { version = 11; Console.WriteLine("{\"Operation\":\"GetV\",\"Version\":11}"); return 0; } public uint OpenConnection( string HostName, string ProcessName, uint ProcessId, string UserName, byte[] Password, ushort pwdLength, byte clientType, ushort clientVersion, uint ConnectionMode, uint ConnectionTimeout, ref string StorageSessionId, out uint Handle, out long ConnectTime, out uint ServerStatus) { string storageSessionIdIn = StorageSessionId ?? string.Empty; StorageSessionId = Guid.Empty.ToString("D"); Handle = HistoryCaptureService.Handle; ConnectTime = DateTime.UtcNow.ToFileTimeUtc(); ServerStatus = 0; Console.WriteLine("{" + "\"Operation\":\"Open\"," + $"\"HostName\":\"{Escape(HostName)}\"," + $"\"ProcessName\":\"{Escape(ProcessName)}\"," + $"\"ProcessId\":{ProcessId}," + $"\"UserName\":\"{Escape(UserName)}\"," + $"\"PasswordLength\":{(Password?.Length ?? 0)}," + $"\"pwdLength\":{pwdLength}," + $"\"clientType\":{clientType}," + $"\"clientVersion\":{clientVersion}," + $"\"ConnectionMode\":{ConnectionMode}," + $"\"ConnectionTimeout\":{ConnectionTimeout}," + $"\"StorageSessionIdIn\":\"{Escape(storageSessionIdIn)}\"," + $"\"StorageSessionIdOut\":\"{Escape(StorageSessionId)}\"" + "}"); Console.Out.Flush(); return 0; } public uint CloseConnection(uint handle) { Console.WriteLine($"{{\"Operation\":\"Close\",\"Handle\":{handle}}}"); return 0; } public uint AddTags(uint handle, uint elementCount, uint inByteCount, byte[] inputBuffer, out uint outByteCount, out byte[] outputBuffer) { byte[] request = inputBuffer ?? Array.Empty(); outByteCount = 0; outputBuffer = Array.Empty(); bool includeBuffers = string.Equals(Environment.GetEnvironmentVariable("WCF_CAPTURE_INCLUDE_BUFFERS"), "1", StringComparison.Ordinal); Console.WriteLine("{" + "\"Operation\":\"AddT\"," + $"\"Handle\":{handle}," + $"\"ElementCount\":{elementCount}," + $"\"InputByteCount\":{inByteCount}," + $"\"InputByteArrayLength\":{request.Length}," + $"\"InputSha256\":\"{Hash(request)}\"" + (includeBuffers ? $",\"InputBase64\":\"{Convert.ToBase64String(request)}\"" : string.Empty) + "}"); Console.Out.Flush(); return 0; } public uint RegisterTags(uint handle, uint elementCount, uint inByteCount, byte[] inputBuffer, out uint outByteCount, out byte[] outputBuffer) { byte[] request = inputBuffer ?? Array.Empty(); outByteCount = 0; outputBuffer = Array.Empty(); bool includeBuffers = string.Equals(Environment.GetEnvironmentVariable("WCF_CAPTURE_INCLUDE_BUFFERS"), "1", StringComparison.Ordinal); Console.WriteLine("{" + "\"Operation\":\"RTag\"," + $"\"Handle\":{handle}," + $"\"ElementCount\":{elementCount}," + $"\"InputByteCount\":{inByteCount}," + $"\"InputByteArrayLength\":{request.Length}," + $"\"InputSha256\":\"{Hash(request)}\"" + (includeBuffers ? $",\"InputBase64\":\"{Convert.ToBase64String(request)}\"" : string.Empty) + "}"); Console.Out.Flush(); return 0; } public bool OpenConnection2(ref byte[] inParameters, out byte[] outParameters, out byte[] err) { byte[] input = inParameters ?? Array.Empty(); Console.WriteLine("{" + "\"Operation\":\"Open2\"," + $"\"InputByteCount\":{input.Length}," + $"\"InputSha256\":\"{Hash(input)}\"" + "}"); Console.Out.Flush(); outParameters = CreateOpen2Output(); err = Array.Empty(); return true; } public bool RegisterTags2(string handle, uint elementCount, byte[] inputBuffer, out byte[] outputBuffer, out byte[] errorBuffer) { byte[] request = inputBuffer ?? Array.Empty(); outputBuffer = Array.Empty(); errorBuffer = Array.Empty(); bool includeBuffers = string.Equals(Environment.GetEnvironmentVariable("WCF_CAPTURE_INCLUDE_BUFFERS"), "1", StringComparison.Ordinal); Console.WriteLine("{" + "\"Operation\":\"RTag2\"," + $"\"Handle\":\"{Escape(handle)}\"," + $"\"ElementCount\":{elementCount}," + $"\"InputByteArrayLength\":{request.Length}," + $"\"InputSha256\":\"{Hash(request)}\"" + (includeBuffers ? $",\"InputBase64\":\"{Convert.ToBase64String(request)}\"" : string.Empty) + "}"); Console.Out.Flush(); return true; } public uint IsOriginalAllowed(uint clientHandle, out bool isAllowed) { isAllowed = true; Console.WriteLine($"{{\"Operation\":\"IsOriginalAllowed\",\"ClientHandle\":{clientHandle}}}"); Console.Out.Flush(); return clientHandle == Handle ? 0u : 4u; } public bool StartQuery2( uint clientHandle, ushort queryRequestType, uint requestSize, byte[] requestBuffer, out uint responseSize, out byte[] responseBuffer, ref uint queryHandle, out uint errorSize, out byte[] errorBuffer) { byte[] request = requestBuffer ?? Array.Empty(); queryHandle = 1; responseSize = 0; responseBuffer = Array.Empty(); errorSize = 5; errorBuffer = new byte[] { 4, 1, 0, 0, 0 }; bool includeBuffers = string.Equals(Environment.GetEnvironmentVariable("WCF_CAPTURE_INCLUDE_BUFFERS"), "1", StringComparison.Ordinal); Console.WriteLine("{" + "\"Operation\":\"StartQuery2\"," + $"\"ClientHandle\":{clientHandle}," + $"\"QueryRequestType\":{queryRequestType}," + $"\"RequestSize\":{requestSize}," + $"\"RequestByteCount\":{request.Length}," + $"\"RequestSha256\":\"{Hash(request)}\"" + (includeBuffers ? $",\"RequestBase64\":\"{Convert.ToBase64String(request)}\"" : string.Empty) + "}"); Console.Out.Flush(); return false; } public uint StartQuery( uint clientHandle, ushort queryRequestType, uint requestSize, byte[] requestBuffer, out uint responseSize, out byte[] responseBuffer, ref uint queryHandle) { byte[] request = requestBuffer ?? Array.Empty(); queryHandle = 1; responseSize = 0; responseBuffer = Array.Empty(); bool includeBuffers = string.Equals(Environment.GetEnvironmentVariable("WCF_CAPTURE_INCLUDE_BUFFERS"), "1", StringComparison.Ordinal); Console.WriteLine("{" + "\"Operation\":\"StartQuery\"," + $"\"ClientHandle\":{clientHandle}," + $"\"QueryRequestType\":{queryRequestType}," + $"\"RequestSize\":{requestSize}," + $"\"RequestByteCount\":{request.Length}," + $"\"RequestSha256\":\"{Hash(request)}\"" + (includeBuffers ? $",\"RequestBase64\":\"{Convert.ToBase64String(request)}\"" : string.Empty) + "}"); Console.Out.Flush(); return 4; } public bool GetNextQueryResultBuffer2( uint clientHandle, uint queryHandle, out uint resultSize, out byte[] resultBuffer, out uint errorSize, out byte[] errorBuffer) { resultSize = 0; resultBuffer = Array.Empty(); errorSize = 5; errorBuffer = new byte[] { 4, 1, 0, 0, 0 }; Console.WriteLine($"{{\"Operation\":\"GetNextQueryResultBuffer2\",\"ClientHandle\":{clientHandle},\"QueryHandle\":{queryHandle}}}"); Console.Out.Flush(); return false; } public uint GetNextQueryResultBuffer( uint clientHandle, uint queryHandle, out uint resultSize, out byte[] resultBuffer) { resultSize = 0; resultBuffer = Array.Empty(); Console.WriteLine($"{{\"Operation\":\"GetNextQueryResultBuffer\",\"ClientHandle\":{clientHandle},\"QueryHandle\":{queryHandle}}}"); Console.Out.Flush(); return 4; } public bool EndQuery2(uint clientHandle, uint queryHandle, out uint errorSize, out byte[] errorBuffer) { errorSize = 0; errorBuffer = Array.Empty(); Console.WriteLine($"{{\"Operation\":\"EndQuery2\",\"ClientHandle\":{clientHandle},\"QueryHandle\":{queryHandle}}}"); Console.Out.Flush(); return true; } public uint EndQuery(uint clientHandle, uint queryHandle) { Console.WriteLine($"{{\"Operation\":\"EndQuery\",\"ClientHandle\":{clientHandle},\"QueryHandle\":{queryHandle}}}"); Console.Out.Flush(); return 0; } public bool StartEventQuery( uint clientHandle, ushort queryRequestType, uint requestSize, byte[] requestBuffer, out uint responseSize, out byte[] responseBuffer, ref uint queryHandle, out uint errorSize, out byte[] errorBuffer) { byte[] request = requestBuffer ?? Array.Empty(); queryHandle = 1; responseSize = 0; responseBuffer = Array.Empty(); errorSize = 5; errorBuffer = new byte[] { 4, 1, 0, 0, 0 }; bool includeBuffers = string.Equals(Environment.GetEnvironmentVariable("WCF_CAPTURE_INCLUDE_BUFFERS"), "1", StringComparison.Ordinal); Console.WriteLine("{" + "\"Operation\":\"StartEventQuery\"," + $"\"ClientHandle\":{clientHandle}," + $"\"QueryRequestType\":{queryRequestType}," + $"\"RequestSize\":{requestSize}," + $"\"RequestByteCount\":{request.Length}," + $"\"RequestSha256\":\"{Hash(request)}\"" + (includeBuffers ? $",\"RequestBase64\":\"{Convert.ToBase64String(request)}\"" : string.Empty) + "}"); Console.Out.Flush(); return false; } public bool GetNextEventQueryResultBuffer( uint clientHandle, uint queryHandle, out uint resultSize, out byte[] resultBuffer, out uint errorSize, out byte[] errorBuffer) { resultSize = 0; resultBuffer = Array.Empty(); errorSize = 5; errorBuffer = new byte[] { 4, 1, 0, 0, 0 }; Console.WriteLine($"{{\"Operation\":\"GetNextEventQueryResultBuffer\",\"ClientHandle\":{clientHandle},\"QueryHandle\":{queryHandle}}}"); Console.Out.Flush(); return false; } public bool EndEventQuery(uint clientHandle, uint queryHandle, out uint errorSize, out byte[] errorBuffer) { errorSize = 0; errorBuffer = Array.Empty(); Console.WriteLine($"{{\"Operation\":\"EndEventQuery\",\"ClientHandle\":{clientHandle},\"QueryHandle\":{queryHandle}}}"); Console.Out.Flush(); return true; } private static byte[] CreateOpen2Output() { using MemoryStream stream = new MemoryStream(); using BinaryWriter writer = new BinaryWriter(stream); writer.Write(Handle); writer.Write(Guid.Empty.ToByteArray()); writer.Write(DateTime.UtcNow.ToFileTimeUtc()); writer.Write(0u); return stream.ToArray(); } private static string Escape(string? value) { return (value ?? string.Empty).Replace("\\", "\\\\").Replace("\"", "\\\""); } private static string Hash(byte[] value) { using System.Security.Cryptography.SHA256 sha256 = System.Security.Cryptography.SHA256.Create(); byte[] hash = sha256.ComputeHash(value); StringBuilder builder = new(hash.Length * 2); foreach (byte b in hash) { builder.Append(b.ToString("x2")); } return builder.ToString(); } } internal static class MdasBinding { public static Binding Create(TimeSpan timeout) { CustomBinding binding = new( new MdasMessageEncodingBindingElement(new BinaryMessageEncodingBindingElement { MessageVersion = MessageVersion.Soap12WSAddressing10 }), new TcpTransportBindingElement { MaxReceivedMessageSize = 64 * 1024 * 1024, TransferMode = TransferMode.Buffered }) { OpenTimeout = timeout, CloseTimeout = timeout, SendTimeout = timeout, ReceiveTimeout = timeout }; return binding; } public static Binding CreateWindows(TimeSpan timeout) { NetTcpBinding nativeShape = new NetTcpBinding(SecurityMode.Transport) { MaxReceivedMessageSize = 64 * 1024 * 1024, MaxBufferSize = 64 * 1024 * 1024 }; nativeShape.ReaderQuotas.MaxArrayLength = nativeShape.MaxBufferSize; nativeShape.Security.Transport.ClientCredentialType = TcpClientCredentialType.Windows; nativeShape.Security.Transport.ProtectionLevel = ProtectionLevel.None; BindingElementCollection elements = nativeShape.CreateBindingElements(); for (int i = 0; i < elements.Count; i++) { if (elements[i] is MessageEncodingBindingElement encoding) { elements[i] = new MdasMessageEncodingBindingElement(encoding); break; } } return new CustomBinding(elements) { OpenTimeout = timeout, CloseTimeout = timeout, SendTimeout = timeout, ReceiveTimeout = timeout }; } } internal sealed class MdasMessageEncodingBindingElement : MessageEncodingBindingElement { private readonly MessageEncodingBindingElement inner; public MdasMessageEncodingBindingElement(MessageEncodingBindingElement inner) { this.inner = inner ?? throw new ArgumentNullException(nameof(inner)); } private MdasMessageEncodingBindingElement(MdasMessageEncodingBindingElement source) { inner = (MessageEncodingBindingElement)source.inner.Clone(); } public override MessageVersion MessageVersion { get => inner.MessageVersion; set => inner.MessageVersion = value; } public override MessageEncoderFactory CreateMessageEncoderFactory() { return new MdasMessageEncoderFactory(inner.CreateMessageEncoderFactory()); } public override BindingElement Clone() { return new MdasMessageEncodingBindingElement(this); } public override IChannelFactory BuildChannelFactory(BindingContext context) { context.BindingParameters.Add(this); return context.BuildInnerChannelFactory(); } public override IChannelListener BuildChannelListener(BindingContext context) { context.BindingParameters.Add(this); return context.BuildInnerChannelListener(); } public override bool CanBuildChannelFactory(BindingContext context) { context.BindingParameters.Add(this); return context.CanBuildInnerChannelFactory(); } public override bool CanBuildChannelListener(BindingContext context) { context.BindingParameters.Add(this); return context.CanBuildInnerChannelListener(); } public override T GetProperty(BindingContext context) { return inner.GetProperty(context) ?? context.GetInnerProperty(); } } internal sealed class MdasMessageEncoderFactory : MessageEncoderFactory { private readonly MessageEncoderFactory inner; private readonly MessageEncoder encoder; public MdasMessageEncoderFactory(MessageEncoderFactory inner) { this.inner = inner ?? throw new ArgumentNullException(nameof(inner)); encoder = new MdasMessageEncoder(inner.Encoder); } public override MessageEncoder Encoder => encoder; public override MessageVersion MessageVersion => inner.MessageVersion; } internal sealed class MdasMessageEncoder : MessageEncoder { private const string MdasContentType = "application/x-mdas"; private readonly MessageEncoder inner; public MdasMessageEncoder(MessageEncoder inner) { this.inner = inner ?? throw new ArgumentNullException(nameof(inner)); } public override string ContentType => MdasContentType; public override string MediaType => MdasContentType; public override MessageVersion MessageVersion => inner.MessageVersion; public override bool IsContentTypeSupported(string contentType) { return contentType.StartsWith(MdasContentType, StringComparison.OrdinalIgnoreCase) || inner.IsContentTypeSupported(contentType); } public override Message ReadMessage(ArraySegment buffer, BufferManager bufferManager, string contentType) { Message message = inner.ReadMessage(buffer, bufferManager); message.Properties.Encoder = this; return message; } public override Message ReadMessage(Stream stream, int maxSizeOfHeaders, string contentType) { return inner.ReadMessage(stream, maxSizeOfHeaders); } public override void WriteMessage(Message message, Stream stream) { inner.WriteMessage(message, stream); } public override ArraySegment WriteMessage(Message message, int maxMessageSize, BufferManager bufferManager, int messageOffset) { return inner.WriteMessage(message, maxMessageSize, bufferManager, messageOffset); } }