Files
Joseph Doherty 7e4d713eb3 Cross-platform NegotiateAuthentication; StorageType field; docs polish
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>
2026-05-04 22:19:37 -04:00

231 lines
8.0 KiB
Markdown

# 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`:
1. Decompile `current\aahClientManaged.dll` to understand the managed wrapper
surface, connection flow, query types, data models, error handling, and
native calls.
2. Inspect `current\aahClient.dll` and the matching AVEVA install DLLs to map
the native ABI and identify transport/framing responsibilities.
3. Capture traffic from real Historian sessions and derive the on-the-wire
protocol for the required read-only operations.
4. 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\` and `aveva-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:
- `ProbeAsync`
- `ReadRawAsync`
- `ReadAggregateAsync`
- `ReadAtTimeAsync`
- `ReadEventsAsync`
- `BrowseTagNamesAsync`
- `GetTagMetadataAsync`
- Status helpers: `GetConnectionStatusAsync`, `GetStoreForwardStatusAsync`,
`GetSystemParameterAsync`
Writes (added 2026-05-04 by explicit request):
- `EnsureTagAsync` for analog Float / Double / Int2 / Int4 / UInt4
(with optional `ApplyScaling=true` for distinct MinRaw/MaxRaw and
optional `StorageRateMs` for 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.dll`
- `aveva-install-x86\aahClient.dll`
- `aahClientCommon.dll`
- `aahDataSetClient.dll`
- `aahClientConfig.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_HOST`
- `HISTORIAN_PORT`
- `HISTORIAN_USER`
- `HISTORIAN_PASSWORD`
- `HISTORIAN_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.dll` from `src/`.
- 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\` or `aveva-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.dll` or
`aahClient.dll` at 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/`.