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>
8.2 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Mission
Build a fully managed .NET 10 replacement for AVEVA Historian's aahClientManaged / aahClient.dll stack by reverse-engineering the proprietary binary protocol. The production SDK under src/AVEVA.Historian.Client/ must remain pure managed .NET 10 — no P/Invoke, no native AVEVA runtime dependency, no REST. Tools under tools/ and scripts under scripts/ are reverse-engineering aids only.
Read AGENTS.md (standing constraints), instructions.md (decision record), and docs/reverse-engineering/handoff.md (current evidence + active blocker) before starting non-trivial work. The handoff doc is the entry point — it tracks the live blocker, next pickup steps, and the canonical list of primary reference docs.
Required SDK Surface
Read-only operations only. Do not implement write-back unless explicitly requested:
ProbeAsync,ReadRawAsync,ReadAggregateAsync,ReadAtTimeAsync,ReadEventsAsyncBrowseTagNamesAsync,GetTagMetadataAsync- Status helpers:
GetConnectionStatusAsync,GetStoreForwardStatusAsync,GetSystemParameterAsync
Methods without protocol evidence currently throw ProtocolEvidenceMissingException from Historian2020ProtocolDialect. Do not stub fake behavior — leave them throwing until evidence supports an implementation.
Build & Test
dotnet build .\Histsdk.slnx --no-restore
dotnet test .\Histsdk.slnx --no-build --logger "console;verbosity=minimal"
Run a single test:
dotnet test .\Histsdk.slnx --no-build --filter "FullyQualifiedName~WcfDataQueryProtocolTests"
Live integration tests in tests/AVEVA.Historian.Client.Tests/HistorianClientIntegrationTests.cs are gated and skip cleanly without these env vars:
$env:HISTORIAN_HOST, $env:HISTORIAN_PORT (32568), $env:HISTORIAN_USER, $env:HISTORIAN_PASSWORD,
$env:HISTORIAN_TEST_TAG, $env:HISTORIAN_TAG_FILTER
Never write real credentials, hostnames, user names, or customer tag names into docs, scripts, captures, or commit messages.
Reverse-Engineering CLI
tools/AVEVA.Historian.ReverseEngineering is the .NET 10 CLI for static inspection, WCF probes, and IL-rewrite instrumentation. Common entry points:
dotnet run --no-build --project tools\AVEVA.Historian.ReverseEngineering -- wcf-probe $env:HISTORIAN_HOST 32568
dotnet run --no-build --project tools\AVEVA.Historian.ReverseEngineering -- wcf-cert-probe $env:HISTORIAN_HOST 32568 localhost
dotnet run --no-build --project tools\AVEVA.Historian.ReverseEngineering -- wcf-like-tag-browse $env:HISTORIAN_HOST 32568 $env:HISTORIAN_TAG_FILTER
dotnet run --no-build --project tools\AVEVA.Historian.ReverseEngineering -- wcf-start-query $env:HISTORIAN_HOST 32568 $env:HISTORIAN_TEST_TAG --max-attempts 1 --timeout-seconds 3
dotnet run --project tools\AVEVA.Historian.NativeTraceHarness -- --scenario history --tag $env:HISTORIAN_TEST_TAG --lookback-minutes 1440
The wcf-start-query matrix is expensive — always pass --max-attempts / --timeout-seconds for negative probes. See docs/reverse-engineering/capture-workflow.md for the full repeatable capture sequence (manifest, mark, exports, Frida winsock attach, etc.).
Code Architecture
Production SDK (src/AVEVA.Historian.Client/)
Three layered subsystems, intentionally decoupled so protocol parsing can be unit-tested without a live server:
HistorianClient+HistorianClientOptions— public façade. Validates inputs, delegates reads toHistorian2020ProtocolDialect, delegates probe/tag-metadata/browse to the WCF layer.Wcf/— managed WCF/MDAS layer. The Historian uses Net.TCP on port32568with a customapplication/x-mdascontent type wrapping a binary SOAP 1.2 / WS-Addressing 1.0 envelope.MdasMessageEncoder+MdasMessageEncodingBindingElementimplement that wrapper.HistorianWcfBindingFactoryproduces three flavors: plain MDAS, MDAS+Windows transport (used for/Hist-Integrated), and MDAS+certificate (used for/HistCert). Service paths live inHistorianWcfServiceNames. WCF data contracts (Wcf/Contracts/) are reproduced from server-side static analysis and are versioned per native interface (e.g.,IRetrievalServiceContract2..4).Protocol/— binary frame layer (HistorianFrameReader/Writer,HistorianBinaryPrimitives,HistorianMessageType).Historian2020ProtocolDialectis the version-anchored bridge betweenHistorianClientand the frame layer; methods without sufficient evidence throwProtocolEvidenceMissingExceptionrather than guessing wire bytes.Transport/— pluggableIHistorianTransport(default: TCP). Tests inject a fake transport.Models/— public DTOs and enums (HistorianSample,RetrievalMode, etc.).HistorianDataValuerepresents the discriminated value type.
InternalsVisibleTo exposes internals to the test assembly and the reverse-engineering tool.
The Active Protocol Blocker
The native wrapper does not use the simple Open2 session handle for query reads. The successful native flow is CClientContext.AuthenticateClient → two ValidateClientCredential SSPI rounds → CHistoryConnectionWCF.OpenConnection3 → CClientCommon.StartQuery → /Retr.StartQuery2. OpenConnection3 mints the transient /Retr client handle the server accepts. Managed Open2 alone reaches server logic but Retr.StartQuery2 returns false with empty buffers.
DataQueryRequest and EventQueryRequest byte serialization is already byte-matched against native captures. The remaining gap is reproducing the auth/session state that lets the server accept a client-generated context GUID before OpenConnection3. See handoff.md "Active Blocker" and docs/reverse-engineering/openconnection3-correlation-latest.json.
Tools Layer
tools/AVEVA.Historian.NativeTraceHarness/— .NET Framework (not .NET 10) harness that loadscurrent/aahClientManaged.dlland records sanitized reflection snapshots aroundOpenConnection,StartQuery,MoveNext. Exists specifically to parity-test against the native wrapper.tools/AVEVA.Historian.NetFxWcfProbe/— .NET Framework WCF probe to rule out .NET 10-only WCF behavior differences.tools/AVEVA.Historian.ReverseInstrumentation/— assembly injected into IL-rewritten copies ofaahClientManaged.dllfor sanitized logging. Rewrites land indocs/reverse-engineering/dnlib-write-copy/, never incurrent/.tools/AVEVA.Historian.WcfCaptureServer/— fake server for endpoint experiments.scripts/— PowerShell + Frida runners for native attach captures (winsock, system boundary, runtime pointers, ValCl SSPI context).
Evidence & Artifacts
docs/reverse-engineering/— sanitized Markdown summaries + small JSON evidence. Always commit-safe.artifacts/reverse-engineering/— raw / identity-bearing runtime output. Never committed; never copy contents intodocs/without sanitizing.fixtures/protocol/— sanitized golden byte fixtures, named to matchmanifestscenarios.current/andaveva-install-{x64,x86}/— AVEVA binaries. Never modify, delete, or redistribute. Usecurrent/first because it matches the deployed sidecar.
Testing Conventions
Unit tests are golden-byte and round-trip oriented — WcfDataQueryProtocolTests, WcfEventQueryProtocolTests, WcfTagQueryProtocolTests, WcfOpen2ProtocolTests, FrameTests, BinaryPrimitiveTests. ProtocolGuardrailTests enforces that unimplemented methods throw ProtocolEvidenceMissingException rather than returning empty results. When adding a new protocol code path, add a golden-byte fixture before/alongside the implementation.
Safety
- Never commit credentials, hostnames, user names, customer tag names, or raw packet captures. Use placeholders in docs.
- Run a sanitization scan after touching auth/capture docs (the rg pattern is in handoff.md "Next Pickup Steps").
- Production code under
src/must remain pure managed .NET 10 with no native AVEVA reference. Reverse-engineering harnesses undertools/may reference native binaries. - This workspace is not a Git working tree in the current checkout — track changes via file timestamps or external backup.