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>
164 lines
9.4 KiB
Markdown
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.
|