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>
143 lines
5.8 KiB
Markdown
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`
|