Files
histsdk/tests/AVEVA.Historian.Client.Tests/WcfAuthenticationProtocolTests.cs
dohertj2 c95824a65d Initial commit: managed .NET 10 AVEVA Historian SDK + reverse-engineering toolkit
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>
2026-05-04 06:31:48 -04:00

98 lines
3.1 KiB
C#

using AVEVA.Historian.Client.Wcf;
namespace AVEVA.Historian.Client.Tests;
public sealed class WcfAuthenticationProtocolTests
{
[Fact]
public void WrapValidateClientCredentialToken_UsesNativeRoundAndLengthEnvelope()
{
byte[] actual = HistorianWcfAuthenticationProtocol.WrapValidateClientCredentialToken(
isFirstRound: true,
[0x4E, 0x54, 0x4C, 0x4D]);
Assert.Equal([0x01, 0x04, 0x00, 0x00, 0x00, 0x4E, 0x54, 0x4C, 0x4D], actual);
}
[Fact]
public void TryReadWrappedValidateClientCredentialToken_ReadsNativeEnvelope()
{
ValidateClientCredentialToken? actual =
HistorianWcfAuthenticationProtocol.TryReadWrappedValidateClientCredentialToken(
[0x00, 0x03, 0x00, 0x00, 0x00, 0xAA, 0xBB, 0xCC]);
Assert.NotNull(actual);
Assert.False(actual.IsFirstRound);
Assert.Equal([0xAA, 0xBB, 0xCC], actual.Token);
}
[Fact]
public void TryReadWrappedValidateClientCredentialToken_RejectsLengthMismatch()
{
Assert.Null(HistorianWcfAuthenticationProtocol.TryReadWrappedValidateClientCredentialToken(
[0x01, 0x04, 0x00, 0x00, 0x00, 0xAA]));
}
[Fact]
public void TryReadValidateClientCredentialResponse_ReadsContinueFlagAndServerToken()
{
ValidateClientCredentialResponse? actual =
HistorianWcfAuthenticationProtocol.TryReadValidateClientCredentialResponse(
[0x01, 0x11, 0x22, 0x33]);
Assert.NotNull(actual);
Assert.True(actual.Continue);
Assert.Equal([0x11, 0x22, 0x33], actual.Token);
}
[Fact]
public void TryReadValidateClientCredentialResponse_ReadsTerminalOneByteResponse()
{
ValidateClientCredentialResponse? actual =
HistorianWcfAuthenticationProtocol.TryReadValidateClientCredentialResponse([0x00]);
Assert.NotNull(actual);
Assert.False(actual.Continue);
Assert.Empty(actual.Token);
}
[Fact]
public void TryReadValidateClientCredentialResponse_RejectsEmptyResponse()
{
Assert.Null(HistorianWcfAuthenticationProtocol.TryReadValidateClientCredentialResponse([]));
}
[Fact]
public void TryApplyNativeNtlmNegotiateVersionFlag_MatchesObservedNativeFirstTokenFlag()
{
byte[] token =
[
0x4E, 0x54, 0x4C, 0x4D, 0x53, 0x53, 0x50, 0x00,
0x01, 0x00, 0x00, 0x00,
0xB7, 0xB2, 0x08, 0xE2
];
bool changed = HistorianWcfAuthenticationProtocol.TryApplyNativeNtlmNegotiateVersionFlag(token);
Assert.True(changed);
Assert.Equal(
[
0x4E, 0x54, 0x4C, 0x4D, 0x53, 0x53, 0x50, 0x00,
0x01, 0x00, 0x00, 0x00,
0xB7, 0xB2, 0x18, 0xE2
],
token);
}
[Fact]
public void TryApplyNativeNtlmNegotiateVersionFlag_IgnoresNonNtlmNegotiateTokens()
{
byte[] token = [0x4B, 0x52, 0x42, 0x35];
bool changed = HistorianWcfAuthenticationProtocol.TryApplyNativeNtlmNegotiateVersionFlag(token);
Assert.False(changed);
Assert.Equal([0x4B, 0x52, 0x42, 0x35], token);
}
}