Drops a small helper for the gated live-integration tests that need
HISTORIAN_USER + HISTORIAN_PASSWORD set in the process environment
(currently GetTagMetadataAsync_ExplicitCredentials_AgainstLocalHistorian).
Three modes:
- Default: prompts for username (default <COMPUTERNAME>\<USERNAME>) and
password (silent), saves to %USERPROFILE%\.histsdk\credentials.xml via
Export-Clixml. The SecureString inside the PSCredential is DPAPI-encrypted
and decryptable only by the same Windows user account on the same machine.
- -Load: reads the saved credential and exports HISTORIAN_USER +
HISTORIAN_PASSWORD into the current PowerShell session's environment.
- -Clear: deletes the saved credential file.
Also accepts -Path to override the storage location (e.g. for keeping
multiple credential sets side by side) and -UserName to skip the username
prompt for password-only re-saves.
Stored under the user profile, never inside the repo, so it cannot be
committed accidentally. The file format is plain Export-Clixml — no custom
encoding shenanigans.
Live-verified locally: -Load + dotnet test passes the previously-skipped
GetTagMetadataAsync_ExplicitCredentials_AgainstLocalHistorian test against
the local Historian with IntegratedSecurity=false.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
histsdk
Pure managed .NET 10 client for AVEVA Historian's binary WCF protocol. The
production SDK has no dependency on aahClientManaged.dll, aahClient.dll, or
any other AVEVA native runtime — the wire protocol is reverse-engineered and
re-implemented in C#.
Read-only by design. The required surface (per CLAUDE.md):
| Operation | Status |
|---|---|
ProbeAsync |
live-verified |
ReadRawAsync |
live-verified |
ReadAggregateAsync |
live-verified (TimeWeightedAverage; other modes need fixtures) |
ReadAtTimeAsync |
live-verified |
ReadEventsAsync |
live-verified (typed event + 31-property property bag) |
BrowseTagNamesAsync |
live-verified |
GetTagMetadataAsync |
live-verified for 17 distinct native data-type codes |
GetConnectionStatusAsync |
synthesized from authenticated probe (matches native semantic) |
GetStoreForwardStatusAsync |
synthesized defaults (no SF sidecar to probe) |
GetSystemParameterAsync |
live-verified via Stat/GetSystemParameter |
Out of scope: write-back, store-forward write, configuration changes.
Quick start
using AVEVA.Historian.Client;
using AVEVA.Historian.Client.Models;
await using HistorianClient client = new(new HistorianClientOptions
{
Host = "localhost",
IntegratedSecurity = true,
Transport = HistorianTransport.LocalPipe,
});
DateTime endUtc = DateTime.UtcNow;
DateTime startUtc = endUtc - TimeSpan.FromMinutes(10);
await foreach (HistorianSample sample in client.ReadRawAsync(
"SysTimeSec", startUtc, endUtc, maxValues: 100))
{
Console.WriteLine($"{sample.TimestampUtc:o} {sample.NumericValue} Q={sample.Quality}");
}
For a remote Historian over Net.TCP set Transport = HistorianTransport.RemoteTcpIntegrated
and Host to the server hostname. Both RemoteTcpIntegrated (Windows transport
auth) and RemoteTcpCertificate (server-cert TLS) are now live-verified for
ProbeAsync; RemoteTcpIntegrated is additionally live-verified for the full
read / browse / metadata / event / status surface.
Build & test
dotnet build .\Histsdk.slnx --no-restore
dotnet test .\Histsdk.slnx --no-build --logger "console;verbosity=minimal"
Run a single test class:
dotnet test .\Histsdk.slnx --no-build --filter "FullyQualifiedName~WcfDataQueryProtocolTests"
Live integration tests (tests/AVEVA.Historian.Client.Tests/HistorianClientIntegrationTests.cs)
are gated and skip cleanly without these env vars:
$env:HISTORIAN_HOST = 'localhost'
$env:HISTORIAN_TEST_TAG = 'SysTimeSec' # any tag the server has data for
# Optional for non-integrated auth:
$env:HISTORIAN_USER, $env:HISTORIAN_PASSWORD
$env:HISTORIAN_TAG_FILTER = 'Sys*' # or any LIKE-pattern
Repository layout
src/AVEVA.Historian.Client/ Production SDK — pure managed .NET 10. No native AVEVA dependency.
tests/ Unit tests (golden-byte / round-trip) + gated live integration tests.
tools/ Reverse-engineering tooling (NOT shipped):
AVEVA.Historian.ReverseEngineering/ .NET 10 CLI: WCF probes, dnlib IL inspection,
IL-rewrite instrumentation (instrument-wcf-{write,read}message,
instrument-openconnection*, instrument-startdataquery, etc.).
AVEVA.Historian.NativeTraceHarness/ .NET Framework harness that loads aahClientManaged.dll for
byte-for-byte parity testing against the native wrapper.
AVEVA.Historian.NetFxWcfProbe/ .NET Framework WCF probe for ruling out .NET 10-only differences.
AVEVA.Historian.ReverseInstrumentation/ Helper assembly injected into IL-rewritten wrapper copies.
AVEVA.Historian.WcfCaptureServer/ Fake server for endpoint experiments.
scripts/ PowerShell + Frida runners + Python decoders for capture analysis.
fixtures/protocol/ Sanitized golden-byte fixtures.
docs/reverse-engineering/ Sanitized handoff evidence; commit-safe summaries only.
Native AVEVA binaries (current/, aveva-install-x64/, aveva-install-x86/) are
gitignored. Each developer fetches their own copy from the AVEVA installer; we
neither modify nor redistribute them.
Documentation
Read in order before non-trivial work:
| Doc | Purpose |
|---|---|
AGENTS.md |
Standing project constraints and safety rules. |
CLAUDE.md |
Build commands, code architecture, the active protocol blocker (now resolved) and SDK surface. |
docs/reverse-engineering/handoff.md |
Decision record + decoded protocol evidence (binding shapes, descriptor layouts, Open2 v6 response, event-row wire format, property-bag value encoding). |
instructions.md |
Original plan + decision record. |
Architecture
Three intentionally decoupled subsystems under src/AVEVA.Historian.Client/:
HistorianClient+HistorianClientOptions— public façade. Validates inputs; delegates reads toHistorian2020ProtocolDialect; delegates probe / tag metadata / browse / status helpers to the WCF layer.Wcf/— managed WCF/MDAS layer. CustomMdasMessageEncoderwraps SOAP 1.2 + WS-Addressing 1.0 in AVEVA'sapplication/x-mdasframing. Three binding flavors viaHistorianWcfBindingFactory: plain MDAS over Net.NamedPipe (local), MDAS + Windows transport (remote/Hist-Integrated), MDAS + certificate (remote/HistCert). Service contracts inWcf/Contracts/mirror the server-side WCF surface (versioned per native interface —IHistoryServiceContract,IRetrievalServiceContract2..4,IStatusServiceContract2, etc.).Protocol/— binary frame layer.Historian2020ProtocolDialectis the version-anchored bridge between the public façade and the WCF + frame layers. Methods without protocol evidence throwProtocolEvidenceMissingExceptionrather than guessing wire bytes.Models/— public DTOs and enums.HistorianSample,HistorianAggregateSample,HistorianEvent,HistorianTagMetadata,HistorianDataType,RetrievalMode,HistorianTransport, etc.
Read flow end-to-end (live-verified against localhost):
Hist.GetV → Hist.ValCl × N → Hist.Open2 → Retr.GetV → Retr.IsOriginalAllowed →
Retr.StartQuery2 → loop Retr.GetNextQueryResultBuffer2 → typed HistorianSample rows
Event flow end-to-end (live-verified):
Hist.GetV → Hist.ValCl × N → Hist.Open2 → Hist.UpdC3 → 6× Stat.GetSystemParameter →
Hist.RTag2 → Stat.GetSystemParameter(AllowRenameTags) → Trx.GetV → Stat.GetV →
Retr.GetV → Hist.EnsT2(CmEventTagId) → Retr.StartEventQuery →
loop Retr.GetNextEventQueryResultBuffer → typed HistorianEvent rows with
property dictionary → Retr.EndEventQuery → Hist.Close2
Safety
- Production code under
src/must remain pure managed .NET 10 with no native AVEVA reference. Reverse-engineering harnesses undertools/may reference native binaries. - Never commit credentials, hostnames, user names, customer tag names, or raw packet
captures.
*.ndjson,current/,aveva-install-*/, andartifacts/are gitignored precisely because they accumulate identity-bearing runtime data. - Methods without protocol evidence throw
ProtocolEvidenceMissingException. Do not stub fake behavior — leave them throwing until evidence supports an implementation.
Status
124 unit + live integration tests pass (dotnet test --logger "console;verbosity=minimal").
Full read-only SDK surface verified end-to-end against both a local Historian
(LocalPipe) and a remote Historian (RemoteTcpIntegrated over Net.TCP with
Windows transport auth). RemoteTcpCertificate ProbeAsync is live-verified;
the other ops over the certificate transport plus the explicit-credentials
path await live verification.