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

143 lines
5.8 KiB
Markdown

# WCF Open2 Evidence
Command:
```powershell
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
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
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
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`