Files
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

5.8 KiB

WCF Open2 Evidence

Command:

dotnet run --no-build --project tools\AVEVA.Historian.ReverseEngineering -- wcf-open2 localhost 32568

Confirmed:

  • Native CHistoryConnectionWCF.OpenConnection2 serializes a byte buffer and calls HistoryServiceContract.IHistoryServiceContract2.OpenConnection2, WCF operation Open2.
  • The managed contract for Open2 is reachable through the fully managed WCF/MDAS binding.
  • The version-1 open buffer layout from CServiceUtility.SaveOpenConnectionParams is:
    • ushort packet version 1
    • strings encoded as uint32 UTF-16 character count followed by UTF-16LE bytes without a null terminator
    • uint32 process id
    • uint32 password byte length followed by password bytes
    • byte client type
    • ushort client version
    • uint32 connection mode
    • default metadata namespace: flag byte plus three empty encoded strings
  • Empty credential attempts now return a native error buffer instead of InvalidPacketVersion.

Observed sanitized result:

  • Empty credential modes return error bytes 04 AB 00 00 00.
  • Interpreted as native custom error type 4, code 171, this maps to AuthenticationFailed in ArchestrA.HistorianAccessError.ErrorValue.
  • Integrated-security mode 1026 with WCF transport security fails before the operation call with ProtocolException: The requested upgrade is not supported by 'net.tcp://localhost:32568/Hist'. This proves the server's MDAS /Hist endpoint is not using standard WCF transport-security upgrade semantics.
  • The same version-1 integrated-security buffer succeeds when sent to net.tcp://localhost:32568/Hist-Integrated through the managed WCF Windows transport-security binding. The response has a 32-byte output buffer and no error buffer. Do not commit the output bytes; they are treated as an ephemeral session artifact.
  • Client-version values 0, 4, and 11 all return the same 32-byte output shape for this version-1 request.
  • The successful 32-byte output is not yet decoded. Close(1) returns native return code 4; Retr.IsOriginalAllowed(1) also returns native return code 4. Close2 candidates derived from the output buffer all fail with native error code 51: first 16 bytes as .NET Guid, first 16 bytes as RFC4122 Guid, first 16 bytes as hex/base64, and all 32 bytes as hex/base64. Therefore the managed session-open path is known, but the usable client handle/session-token decoding is still unknown.
  • Decompiled CClientInfo.DeserializeOpenConnectionOutParams expects a different native-consumable output shape: first byte must be packet version 2 or 3, followed by a 4-byte field, a 16-byte field, an 8-byte field, and for version 3 another 8-byte field. The 32-byte output from the current version-1 managed request does not match that shape.
  • Earlier integrated-security probing through the no-security MDAS binding could trigger server-side ServiceSecurityContext.WindowsIdentity null-reference failures because that binding does not establish a Windows security context.
  • The server message Failed to get user token... check that the username has been added to Historian Users is authentication/configuration evidence, not packet-version evidence. It indicates the supplied Windows/user credential is not authorized as a Historian user on the target server.
  • The managed harness now decodes these five-byte buffers as:
    • byte 0: native error type
    • bytes 1..4: little-endian unsigned error code
    • known names for codes 1, 73, and 171

Interpretation:

  • Hist.Open2 byte-buffer framing is confirmed for the legacy version-1 path.
  • Fully managed integrated Windows session-open is confirmed for the local 2020 Historian when the client uses /Hist-Integrated.
  • The remaining blocker is decoding the successful 32-byte Open2 output into the handle/token shape used by close, status, retrieval, and query calls.

Native integrated-auth baseline

Reverse-engineering script:

powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\scripts\Test-AahClientManagedOpen.ps1 -HostName localhost -Port 32568 -IntegratedSecurity -ConnectionWaitSeconds 15

Sanitized result:

  • Success = true
  • ConnectedToServer = true
  • ConnectionPending = false
  • ConnectionErrorOccurred = false
  • ErrorCode = Success

The native wrapper returns from OpenConnection before the connection is fully settled, so scripts must poll GetConnectionStatus before treating the open as usable.

Reverse-engineering read smoke:

powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\scripts\Test-AahClientManagedReadIntegrated.ps1 -TagName __codex_missing_probe_tag__ -LookbackMinutes 1 -MaxRows 1 -ConnectionWaitSeconds 15

Sanitized result:

  • OpenSuccess = true
  • ConnectedToServer = true
  • StartQuerySuccess = false
  • StartQueryErrorCode = TagNotFound
  • StartQueryErrorDescription = error = 127 (Tag not found)

This is useful positive evidence: the native integrated-auth path reaches the real Historian query service and fails only because the deliberately fake probe tag does not exist.

Known historized tags can be discovered from the local Galaxy Repository:

powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\scripts\Find-GalaxyHistorizedTags.ps1 -Limit 5

Observed candidates:

  • OtOpcUaParityTest_001.Counter
  • TestMachine_001.TestHistoryValue
  • TestMachine_002.TestHistoryValue
  • TestMachine_003.TestHistoryValue
  • TestMachine_004.TestHistoryValue

Using OtOpcUaParityTest_001.Counter over a 1440-minute lookback through aahClientManaged.dll returned a successful one-row native query:

  • StartQuerySuccess = true
  • RowCount = 1
  • StartDateTime = 2026-04-30T11:00:29.4340342Z
  • EndDateTime = 2026-04-30T11:00:29.4340342Z
  • Quality = 133
  • OpcQuality = 192
  • QualityDetail = 248
  • Value = 0
  • PercentGood = 100