Files
histsdk/docs/reverse-engineering/wcf-contract-evidence.md
T
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

164 lines
9.4 KiB
Markdown

# WCF Contract Evidence
## Local run evidence
- `current\aahClient.dll` export inventory ran successfully. SHA256:
`77a778988e2d8f2d0e88113f8c8b0788a0ef34fa5134938a353976778144dc83`.
- `ArchestrA.HistorianAccess.OpenConnection` succeeded against `localhost:32568`
using `HistorianConnectionArgs` with `ConnectionType=Process` and `ReadOnly=true`.
- Holding that native connection open produced established TCP sessions from the
native PowerShell process to `127.0.0.1:32568`; the server-side listener was
owned by `SMSvcHost.exe`, consistent with WCF Net.TCP port sharing.
- The managed harness command
`dotnet run --no-build --project tools\AVEVA.Historian.ReverseEngineering -- wcf-probe localhost 32568`
successfully called `GetV` through fully managed WCF/MDAS:
- `net.tcp://localhost:32568/Hist` returned version `11`
- `net.tcp://localhost:32568/Retr` returned version `4`
- `net.tcp://localhost:32568/Stat` returned version `0`
- `net.tcp://localhost:32568/Trx` returned version `2`
- `net.tcp://localhost:32568/Storage` did not listen on this local install
- `net.tcp://localhost:32568/HistCert` and `/Hist-Integrated` reset when
called with the plain managed `GetV` contract, while prefixed variants such
as `/HCAP/HistCert` returned `EndpointNotFound`
Sanitized output is stored in `docs\reverse-engineering\wcf-probe-localhost.json`.
- `netsh trace` and `pktmon` produced ETL files under
`%TEMP%\histsdk-captures`, but their converted PCAPNG files contained zero
packets. Built-in Windows packet capture is not sufficient for loopback
evidence on this machine.
- A dedicated managed certificate-binding probe now reaches `HistCert.GetV`
through MDAS over WCF Net.TCP transport security:
- `net.tcp://localhost:32568/HistCert` returned version `11`
- `net.tcp://10.100.0.48:32568/HistCert` initially failed endpoint identity
validation because the server certificate presented DNS identity
`localhost`
- the same remote endpoint returned version `11` when the client supplied
endpoint DNS identity `localhost`
Sanitized outputs are stored in
`docs\reverse-engineering\wcf-cert-probe-localhost-latest.json`,
`docs\reverse-engineering\wcf-cert-probe-remote-latest.json`, and
`docs\reverse-engineering\wcf-cert-probe-remote-localhost-identity-latest.json`.
- The same remote server also accepts the plain managed WCF/MDAS probe on the
expected non-security-specific service paths:
- `net.tcp://10.100.0.48:32568/Hist` returned version `11`
- `net.tcp://10.100.0.48:32568/Retr` returned version `4`
- `net.tcp://10.100.0.48:32568/Stat` returned version `0`
- `net.tcp://10.100.0.48:32568/Trx` returned version `2`
Sanitized output is stored in
`docs\reverse-engineering\wcf-probe-remote-latest.json`.
- Managed remote `Open2` evidence matches localhost: integrated Windows auth
succeeds on `net.tcp://10.100.0.48:32568/Hist-Integrated`, while the same
Windows transport binding fails on plain `/Hist` before dispatch. The
successful returned handle is accepted by `Retr.IsOriginalAllowed`. Session
output bytes and transient handle values are redacted in
`docs\reverse-engineering\wcf-open2-remote-latest.json`.
- Managed remote `StartQuery2` evidence is still negative but sharper: all 22
reconstructed `DataQueryRequest` variants successfully open the integrated
session and pass `Retr.IsOriginalAllowed`, then `StartQuery2` returns `false`
with zero response and error sizes. The legacy `StartQuery` call returns code
`238` for each request and also returns zero response size/no response buffer.
Sanitized request hashes are stored in
`docs\reverse-engineering\wcf-start-query-remote-latest.json`.
- A later bounded managed replay of the first byte-matched full-history
candidate used the same integrated open and `Retr.IsOriginalAllowed` path;
`StartQuery2` still returned `false` with zero response/error sizes, while
legacy `StartQuery` faulted with a server null-reference. This keeps
`Open2` as useful connection evidence, but not as a viable replacement for
the native `OpenConnection3` session state required by query reads.
- Managed wildcard tag browse remains positive evidence for an `Open2`-backed
operation: `Retr.StartLikeTagNameSearch` returned `0`, and one
`GetLikeTagnames` batch returned the deterministic 66-byte single-tag buffer
with SHA-256
`2d450a55f392aed0026e9a957fefa3b116aab6ec81912c5d824c6b9a1ff5a4a1`.
- Managed remote scalar tag calls also accept the integrated session handle:
`Retr.GetTagTypeFromName` returns code `0` and tag type `1` for
`OtOpcUaParityTest_001.Counter`; `Retr.IsManualTag` returns code `0` and
`false`; legacy `Retr.GetTagInfoFromName` returns `238` with zero metadata
bytes. Five `GetTgByNm` tag-name buffer variants and five `GetTg` tag-id
buffer variants all return `238`, sequence `0`, and zero output bytes. This
suggests the calls are reaching server logic but the metadata-returning
contract shape or request buffer is still incomplete. Sanitized output is
stored in
`docs\reverse-engineering\wcf-tag-info-remote-latest.json`.
## Decompiled service contracts
`current\aahClientManaged.dll` contains WCF contracts with namespace `aa`:
- `HistoryServiceContract.IHistoryServiceContract`
- `[ServiceContract(Name = "Hist", Namespace = "aa")]`
- `GetInterfaceVersion` as operation `GetV`
- `OpenConnection` as operation `Open`
- `CloseConnection` as operation `Close`
- `ValidateClient` as operation `VldC`
- `UpdateClientStatus` as operation `UpdC`
- `AddTags` as `AddT`, `RegisterTags` as `RTag`
- `AddStreamValues` as `AddS`, `SetClientTimeOut` as `SetT`
- `HistoryServiceContract.IHistoryServiceContract2`
- `[ServiceContract(Name = "Hist", Namespace = "aa")]`
- byte-buffer session open uses `OpenConnection2` as operation `Open2`
- extended client status uses `UpdC2` / `UpdC3`
- extended write and maintenance calls include `EnsT`, `DelT`, `AddS2`,
`ExKey`, `ValCl`, and `GetI`
- `RetrievalServiceContract.IRetrievalServiceContract`
- `[ServiceContract(Name = "Retr", Namespace = "aa")]`
- `StartQuery`, `GetNextQueryResultBuffer`, `EndQuery` use default
operation names
- tag type/name helpers and tag info calls use default operation names
- `RetrievalServiceContract.IRetrievalServiceContract2/3/4`
- extended bool/error-buffer variants
- SQL/recordset byte stream calls
- tag query calls `QTB`, `QTG`, `QTE`
- event query calls use default operation names
- extended property calls include `GetTgByNm2` and `GetTepByNm`
- `StorageServiceContract.IStorageServiceContract`
- `[ServiceContract(Name = "Storage", Namespace = "aa")]`
- storage/session, metadata, streamed-value, block, snapshot, and delete-tag
calls
- `StatusServiceContract.IStatusServiceContract`
- `[ServiceContract(Name = "Stat", Namespace = "aa")]`
- `GetInterfaceVersion` as `GetV`
- server time, timezone, DB case sensitivity, and logging use default
operation names
- `StatusServiceContract.IStatusServiceContract2`
- extended status operations include `GetSystemParameter`, `GetTimeZoneNames`,
license checks, historian info, and process/ping helpers
- ping and historian-info helpers use aliases `PNGS`, `PNGP`, and `GETHI`
- `TransactionServiceContract.ITransactionServiceContract`
- `[ServiceContract(Name = "Trx", Namespace = "aa")]`
- snapshot forwarding and non-streamed value transactions
`aahMDASEncoder.ClientMessageEncoder` wraps an inner WCF encoder and exposes
media/content type `application/x-mdas`. This means the first managed driver
transport target should be WCF Net.TCP plus the MDAS content-type encoder, not
the earlier speculative raw-frame layer.
## Current unknowns
- Endpoint URI paths `net.tcp://{host}:32568/Hist`, `/Retr`, `/Stat`, and
`/Trx` are confirmed for `GetV` calls on the local 2020 install.
- Relay and local WCF probe evidence also identify security-specific history
endpoints `/HistCert` and `/Hist-Integrated`. `/HistCert` is confirmed as a
`Hist` contract endpoint when called with MDAS over TLS transport security;
`/Hist-Integrated` remains the Windows negotiate endpoint for integrated
session open.
- Managed `Open2` evidence confirms `/Hist-Integrated` is the correct endpoint
for integrated Windows auth. The plain `/Hist` endpoint rejects the Windows
transport-security upgrade before dispatching the operation.
- The storage contract is confirmed statically, but `/Storage` was not a
listening endpoint in the local probe; storage may be routed through
session-specific storage/shard endpoints.
- `Hist.OpenConnection` reaches server logic, but the native password/session
packet encoding is not decoded yet. See `wcf-open-localhost.md`.
- `Hist.Open2` is confirmed reachable with a managed version-1 byte buffer.
Empty credentials return custom native error `171` (`AuthenticationFailed`),
and the harness decodes observed five-byte error buffers as type plus
little-endian error code. This confirms framing has progressed past
packet-version rejection. See `wcf-open2-localhost.md`.
- `Stat` is reachable, but `CStatusConnectionWCF.GetServerTime` is a no-op stub
in the decompiled native WCF path and handle-dependent status calls fail with
handle `0`. See `wcf-status-localhost.md`.
- Query request and response byte-buffer layouts are still proprietary payloads
inside WCF operations such as `StartQuery` and `GetNextQueryResultBuffer`.
- Write payload layouts remain out of scope until read/query payloads are
decoded and fixture-backed.