HistorianSspiClient rewritten on top of System.Net.Security.NegotiateAuthentication
in place of P/Invoke into secur32.dll's InitializeSecurityContextW. The class
keeps the same Next() / Dispose() / two-constructor surface so callers don't
change. RequiredProtectionLevel=EncryptAndSign + AllowedImpersonationLevel=
Identification produces a request-flag set equivalent to the captured native
0x2081C / 0x81C bitmasks (still preserved as constants for the existing unit
tests). Removes the only Windows P/Invoke in the production SDK; the
[SupportedOSPlatform("windows")] gating elsewhere stays in place pending a
separate sweep.
HistorianStorageType (Cyclic = 1, Delta = 2):
Captured 2026-05-04 via --write-storage-type on the harness. Delta differs
from Cyclic in three places — header byte 10 (0x02 -> 0x06), flag-block
byte 1 (0x01 -> 0x02), and 4 zero bytes inserted after StorageRate before
the FILETIME. Server persists Tag.StorageType=1/2 accordingly. Plumbed
through HistorianTagDefinition.StorageType + serializer + orchestrator + 2
new tests (golden bytes + live SQL persistence verification).
Docs polish:
CLAUDE.md no longer claims "no P/Invoke" (HistorianSspiClient is the one
allowed P/Invoke surface); updated test count to 169+; AGENTS.md Required
SDK Surface and Repository Layout brought up to date with the live state
including the write surface; handoff.md "not a git working tree" obsolete
note removed.
171/171 tests pass with the NegotiateAuthentication replacement (was 169;
+2 new tests for StorageType).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
8.0 KiB
AGENTS.md
Mission
Build a fully managed .NET 10 replacement for AVEVA Historian's
aahClientManaged / aahClient.dll stack by reverse-engineering the native
binary Historian protocol.
The target is an in-process managed SDK that can replace the current .NET
Framework/native sidecar used by OtOpcUa.Driver.Historian.Wonderware.
Chosen Approach
Use the reverse-engineering path from instructions.md:
- Decompile
current\aahClientManaged.dllto understand the managed wrapper surface, connection flow, query types, data models, error handling, and native calls. - Inspect
current\aahClient.dlland the matching AVEVA install DLLs to map the native ABI and identify transport/framing responsibilities. - Capture traffic from real Historian sessions and derive the on-the-wire protocol for the required read-only operations.
- Implement the protocol in a pure managed .NET 10 client.
Do not pursue the REST API implementation unless explicitly asked. Do not build a P/Invoke shim as the primary solution; it is useful only as an analysis aid.
Repository Layout
This workspace is a full Git repo (origin: gitea.dohertylan.com) with the
shipping SDK under src/, tests under tests/, RE tooling under tools/,
and decoded protocol notes under docs/. See CLAUDE.md for the
authoritative architecture overview.
instructions.md- source planning document and decision record.src\AVEVA.Historian.Client\- the production managed SDK (pure .NET 10, no native AVEVA references).tests\AVEVA.Historian.Client.Tests\- unit + gated live integration tests.tools\- reverse-engineering tooling (CLI, native trace harness, WCF capture server, IL-rewrite instrumentation helper).docs\reverse-engineering\- sanitized RE evidence and decoded notes.current\- the seven DLLs the existing sidecar links against today.aveva-install-x64\andaveva-install-x86\- full AVEVA Historian client-side DLL sets for cross-version reference.
Use current\ first because it represents the deployed sidecar dependency set.
Use aveva-install-* to compare architecture-specific behavior and locate
adjacent client APIs.
Required SDK Surface
The shipping public surface (all live-verified against localhost —
see CLAUDE.md "Required SDK Surface" for the authoritative list and
caveats):
Reads:
ProbeAsyncReadRawAsyncReadAggregateAsyncReadAtTimeAsyncReadEventsAsyncBrowseTagNamesAsyncGetTagMetadataAsync- Status helpers:
GetConnectionStatusAsync,GetStoreForwardStatusAsync,GetSystemParameterAsync
Writes (added 2026-05-04 by explicit request):
EnsureTagAsyncfor analog Float / Double / Int2 / Int4 / UInt4 (with optionalApplyScaling=truefor distinct MinRaw/MaxRaw and optionalStorageRateMsfor non-default sampling).DeleteTagAsync.
AddS2 (write samples) is architecturally blocked — the server's
runtime cache only ingests from configured IOServer / Application Server
pipelines. Do not extend write support without an explicit new request.
Reverse-Engineering Workflow
The bulk of the original RE workflow has been executed and is now backed
by docs/reverse-engineering/ evidence. The notes below are the durable
process in case new captures are needed (e.g., for a new Historian version
or a new write op).
1. Managed Wrapper Analysis
Use dnSpy / ILSpy / the in-repo dnlib-method CLI on
current\aahClientManaged.dll.
Document:
- Public types and methods used for connections and queries.
- P/Invoke or native interop entry points.
- Constructor arguments, enum values, flags, and default values.
- Query argument structures for raw, aggregate, at-time, and event reads.
- Returned sample/event models, quality fields, timestamp handling, and error propagation.
Sanitized notes go under docs\reverse-engineering\ (the folder exists and
is the canonical home for committed RE evidence).
2. Native ABI Mapping
Inspect current\aahClient.dll and compare with:
aveva-install-x64\aahClient.dllaveva-install-x86\aahClient.dllaahClientCommon.dllaahDataSetClient.dllaahClientConfig.dll
Useful tools:
dumpbin /exports- Dependencies or Process Monitor
- API Monitor
- Detours or equivalent call hooks
Document function names, calling conventions, pointer ownership, HRESULT/error patterns, string encodings, and architecture differences.
3. Wire Capture
Capture real Historian sessions with Wireshark while running the existing Wonderware sidecar/client against a development Historian.
Capture each scenario independently:
- Connection open/close and health probe.
- Raw history query.
- Aggregate query for each required retrieval mode.
- At-time/interpolated query.
- Event query.
- Error cases: bad tag, empty range, invalid credentials, server offline.
For every capture, record:
- Historian version and architecture.
- Client DLL version and file hash.
- Server host/port.
- Query parameters.
- Expected logical result set.
- Packet capture filename.
Do not commit sensitive packet captures, credentials, server names, or customer tag names. Sanitize before adding any fixtures or notes.
4. Protocol Model
Derive and document:
- Session handshake.
- Authentication exchange, if present.
- Message framing and length prefixes.
- Message type identifiers.
- Request/response correlation.
- Endianness.
- Timestamp encoding.
- String encoding.
- Numeric value encoding.
- Quality/status encoding.
- Error frame format.
- Event payload format.
Add version notes whenever behavior differs between the installed 2020 DLLs and newer Historian versions.
5. Managed Implementation Shape
The implementation has landed and is the authoritative reference. See
CLAUDE.md "Code Architecture" for the actual layout. The original
abstract shape is preserved as historical context only.
Key design rule still in force: keep protocol parsing isolated from transport I/O so captured frames can be tested without a live Historian.
Testing Expectations
Start with deterministic tests around protocol encoding/decoding:
- Golden byte fixtures for each message kind.
- Round-trip tests for request builders.
- Parser tests for captured and sanitized response frames.
- Timestamp, quality, and string encoding edge cases.
- Error frame parsing.
Add live integration tests only behind explicit configuration, such as:
HISTORIAN_HOSTHISTORIAN_PORTHISTORIAN_USERHISTORIAN_PASSWORDHISTORIAN_TEST_TAG
Integration tests must skip cleanly when these values are not configured.
Constraints
- Keep the final SDK managed .NET 10. The single P/Invoke surface allowed
in production is
HistorianSspiClient(Windows SSPI for integrated auth); do not add unrelated P/Invokes. - Avoid adding native runtime dependencies to the production SDK. No
reference to
aahClientManaged.dll/aahClient.dllfromsrc/. - Avoid broad API design. Implement only the operations listed in "Required SDK Surface".
- Treat AVEVA protocol details as version-sensitive; document assumptions
in
docs/reverse-engineering/. - Do not redistribute AVEVA binaries.
- Do not commit credentials, proprietary captures, or customer data.
- Do not delete or overwrite DLLs in
current\oraveva-install-*.
Definition of Done
Both the RE phase and the SDK phase are met as of 2026-05-04:
- Managed wrapper public surface and native entry points are documented in
docs/reverse-engineering/. - Required query flows have sanitized captures + byte-level notes; golden
fixtures live under
fixtures/protocol/. - Message framing, request/response/error layouts are decoded sufficiently for round-trip parser tests.
- The shipping SDK implements the Required SDK Surface (reads + writes).
- 169 unit + live integration tests pass.
- Local consumers can replace the sidecar without
aahClientManaged.dlloraahClient.dllat runtime.
Future RE work (e.g., new Historian version, additional write ops) should
follow the same workflow above; new evidence updates docs/reverse-engineering/
and the relevant plan file under docs/plans/.