c95824a65d
Full read-only SDK (src/AVEVA.Historian.Client) implementing the CLAUDE.md required
surface against AVEVA Historian's binary WCF protocol — no native AVEVA runtime
dependency. All operations live-verified against a local Historian:
- ProbeAsync, ReadRawAsync, ReadAggregateAsync, ReadAtTimeAsync, ReadEventsAsync
- BrowseTagNamesAsync, GetTagMetadataAsync (17 native data-type codes mapped)
- GetConnectionStatusAsync, GetStoreForwardStatusAsync, GetSystemParameterAsync
- 108/108 unit + integration tests pass
Includes the reverse-engineering toolkit (tools/AVEVA.Historian.ReverseEngineering)
used to decode the protocol: WCF probes, IL inspection via dnlib, and IL-rewrite
instrumentation (instrument-wcf-{write,read}message etc.) plus the .NET Framework
trace harness (tools/AVEVA.Historian.NativeTraceHarness) for parity testing.
Sanitized handoff evidence under docs/reverse-engineering/. Native AVEVA binaries
(current/, aveva-install-x64/, aveva-install-x86/) are gitignored — fetch separately
from the AVEVA installer.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
675 lines
23 KiB
C#
675 lines
23 KiB
C#
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<byte>();
|
|
outByteCount = 0;
|
|
outputBuffer = Array.Empty<byte>();
|
|
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<byte>();
|
|
outByteCount = 0;
|
|
outputBuffer = Array.Empty<byte>();
|
|
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<byte>();
|
|
Console.WriteLine("{" +
|
|
"\"Operation\":\"Open2\"," +
|
|
$"\"InputByteCount\":{input.Length}," +
|
|
$"\"InputSha256\":\"{Hash(input)}\"" +
|
|
"}");
|
|
Console.Out.Flush();
|
|
|
|
outParameters = CreateOpen2Output();
|
|
err = Array.Empty<byte>();
|
|
return true;
|
|
}
|
|
|
|
public bool RegisterTags2(string handle, uint elementCount, byte[] inputBuffer, out byte[] outputBuffer, out byte[] errorBuffer)
|
|
{
|
|
byte[] request = inputBuffer ?? Array.Empty<byte>();
|
|
outputBuffer = Array.Empty<byte>();
|
|
errorBuffer = Array.Empty<byte>();
|
|
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<byte>();
|
|
queryHandle = 1;
|
|
responseSize = 0;
|
|
responseBuffer = Array.Empty<byte>();
|
|
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<byte>();
|
|
queryHandle = 1;
|
|
responseSize = 0;
|
|
responseBuffer = Array.Empty<byte>();
|
|
|
|
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<byte>();
|
|
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<byte>();
|
|
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<byte>();
|
|
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<byte>();
|
|
queryHandle = 1;
|
|
responseSize = 0;
|
|
responseBuffer = Array.Empty<byte>();
|
|
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<byte>();
|
|
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<byte>();
|
|
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<TChannel> BuildChannelFactory<TChannel>(BindingContext context)
|
|
{
|
|
context.BindingParameters.Add(this);
|
|
return context.BuildInnerChannelFactory<TChannel>();
|
|
}
|
|
|
|
public override IChannelListener<TChannel> BuildChannelListener<TChannel>(BindingContext context)
|
|
{
|
|
context.BindingParameters.Add(this);
|
|
return context.BuildInnerChannelListener<TChannel>();
|
|
}
|
|
|
|
public override bool CanBuildChannelFactory<TChannel>(BindingContext context)
|
|
{
|
|
context.BindingParameters.Add(this);
|
|
return context.CanBuildInnerChannelFactory<TChannel>();
|
|
}
|
|
|
|
public override bool CanBuildChannelListener<TChannel>(BindingContext context)
|
|
{
|
|
context.BindingParameters.Add(this);
|
|
return context.CanBuildInnerChannelListener<TChannel>();
|
|
}
|
|
|
|
public override T GetProperty<T>(BindingContext context)
|
|
{
|
|
return inner.GetProperty<T>(context) ?? context.GetInnerProperty<T>();
|
|
}
|
|
}
|
|
|
|
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<byte> 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<byte> WriteMessage(Message message, int maxMessageSize, BufferManager bufferManager, int messageOffset)
|
|
{
|
|
return inner.WriteMessage(message, maxMessageSize, bufferManager, messageOffset);
|
|
}
|
|
}
|