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>
102 lines
8.2 KiB
Markdown
102 lines
8.2 KiB
Markdown
# 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`, `ReadEventsAsync`
|
|
- `BrowseTagNamesAsync`, `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
|
|
|
|
```powershell
|
|
dotnet build .\Histsdk.slnx --no-restore
|
|
dotnet test .\Histsdk.slnx --no-build --logger "console;verbosity=minimal"
|
|
```
|
|
|
|
Run a single test:
|
|
|
|
```powershell
|
|
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:
|
|
|
|
```powershell
|
|
$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:
|
|
|
|
```powershell
|
|
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 to `Historian2020ProtocolDialect`, delegates probe/tag-metadata/browse to the WCF layer.
|
|
- **`Wcf/`** — managed WCF/MDAS layer. The Historian uses Net.TCP on port `32568` with a custom `application/x-mdas` content type wrapping a binary SOAP 1.2 / WS-Addressing 1.0 envelope. `MdasMessageEncoder` + `MdasMessageEncodingBindingElement` implement that wrapper. `HistorianWcfBindingFactory` produces three flavors: plain MDAS, MDAS+Windows transport (used for `/Hist-Integrated`), and MDAS+certificate (used for `/HistCert`). Service paths live in `HistorianWcfServiceNames`. 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`). `Historian2020ProtocolDialect` is the version-anchored bridge between `HistorianClient` and the frame layer; methods without sufficient evidence throw `ProtocolEvidenceMissingException` rather than guessing wire bytes.
|
|
- **`Transport/`** — pluggable `IHistorianTransport` (default: TCP). Tests inject a fake transport.
|
|
- **`Models/`** — public DTOs and enums (`HistorianSample`, `RetrievalMode`, etc.). `HistorianDataValue` represents 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 loads `current/aahClientManaged.dll` and records sanitized reflection snapshots around `OpenConnection`, `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 of `aahClientManaged.dll` for sanitized logging. Rewrites land in `docs/reverse-engineering/dnlib-write-copy/`, never in `current/`.
|
|
- `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 into `docs/` without sanitizing.
|
|
- `fixtures/protocol/` — sanitized golden byte fixtures, named to match `manifest` scenarios.
|
|
- `current/` and `aveva-install-{x64,x86}/` — AVEVA binaries. **Never modify, delete, or redistribute.** Use `current/` 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 under `tools/` may reference native binaries.
|
|
- This workspace is not a Git working tree in the current checkout — track changes via file timestamps or external backup.
|