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

9.4 KiB

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.