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>
This commit is contained in:
Joseph Doherty
2026-05-04 22:19:37 -04:00
parent 5ce62a5900
commit 7e4d713eb3
11 changed files with 265 additions and 361 deletions
+73 -57
View File
@@ -27,12 +27,21 @@ a P/Invoke shim as the primary solution; it is useful only as an analysis aid.
## Repository Layout
This workspace is an SDK investigation folder, not a full application repo.
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\` - full 64-bit AVEVA Historian client-side DLL set.
- `aveva-install-x86\` - full 32-bit AVEVA Historian client-side DLL set.
- `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
@@ -40,22 +49,44 @@ adjacent client APIs.
## Required SDK Surface
Keep the managed SDK narrowly scoped to the operations used in production:
The shipping public surface (all live-verified against `localhost`
see `CLAUDE.md` "Required SDK Surface" for the authoritative list and
caveats):
- `ReadRawAsync(tag, startUtc, endUtc, maxValues)`
- `ReadAggregateAsync(tag, startUtc, endUtc, mode, interval)`
- `ReadAtTimeAsync(tag, timestampsUtc)`
- `ReadEventsAsync(startUtc, endUtc)`
- `ProbeAsync()`
Reads:
The existing alarm-event write path is dormant. Do not implement write-back
unless a new requirement is supplied.
- `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 or ILSpy on `current\aahClientManaged.dll`.
Use dnSpy / ILSpy / the in-repo `dnlib-method` CLI on
`current\aahClientManaged.dll`.
Document:
@@ -66,8 +97,8 @@ Document:
- Returned sample/event models, quality fields, timestamp handling, and error
propagation.
Prefer producing small Markdown notes under a future `docs\reverse-engineering\`
folder rather than relying on memory.
Sanitized notes go under `docs\reverse-engineering\` (the folder exists and
is the canonical home for committed RE evidence).
### 2. Native ABI Mapping
@@ -137,34 +168,12 @@ newer Historian versions.
### 5. Managed Implementation Shape
When implementation starts, use this project shape unless the real repo dictates
otherwise:
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.
```text
src/AVEVA.Historian.Client/
AVEVA.Historian.Client.csproj
HistorianClient.cs
HistorianClientOptions.cs
Models/
HistorianSample.cs
HistorianAggregateSample.cs
HistorianEvent.cs
RetrievalMode.cs
Protocol/
HistorianConnection.cs
HistorianFrame.cs
HistorianMessageType.cs
HistorianProtocolReader.cs
HistorianProtocolWriter.cs
Transport/
TcpHistorianTransport.cs
ClusterEndpointPicker.cs
Internal/
BackoffPolicy.cs
```
Keep protocol parsing isolated from transport I/O so captured frames can be
tested without a live Historian.
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
@@ -188,27 +197,34 @@ Integration tests must skip cleanly when these values are not configured.
## Constraints
- Keep the final SDK pure managed .NET 10.
- Avoid adding native runtime dependencies to the production SDK.
- Avoid broad API design. Implement only the operations listed above.
- Treat AVEVA protocol details as version-sensitive; document assumptions.
- 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
For the reverse-engineering phase:
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.
- Required query flows have sanitized captures or byte-level notes.
- Message framing, request fields, response fields, and error frames are
described well enough to implement parser tests.
- 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.
For the SDK phase:
- The managed client implements the required read-only surface.
- Unit tests cover protocol parse/build behavior.
- Integration tests can validate against a configured live Historian.
- The SDK can replace the existing sidecar call sites without requiring
`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/`.