From 222eed9c0271a609098657c1481e2dc69d8ec3b0 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Sun, 21 Jun 2026 18:58:38 -0400 Subject: [PATCH] =?UTF-8?q?M3=20R3.1:=20durable=20capture=20plan=20?= =?UTF-8?q?=E2=80=94=20drive=20native=202023=20R2=20gRPC=20client=20+=20IL?= =?UTF-8?q?-rewrite=20byte[]=20payloads?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Records the feasibility-verified plan to capture the two remaining buffers (regular-tag RegisterTags btTagInfos + AddNonStreamValues btInput): - 2023 R2 aahClientManaged.dll is self-contained mixed-mode C++/CLI (only Windows + VC++ runtime native imports) — loadable in a net481 x64 process, no AVEVA install needed. - gRPC routes through the managed Archestra.Historian.GrpcClient.dll, so the byte[] payloads are capturable by IL-rewriting GrpcHistoryClient.RegisterTags / AddNonStreamValues (dnlib, the instrument-wcf-writemessage pattern; rewrite a copy, never the originals). - Connection is reflection-drivable: HistorianAccess.OpenConnection(HistorianConnectionArgs) with ConnectionMode=HistorianConnectionMode.Historian (the gRPC mode), TcpPort=32565, cert. - gRPC runtime deps (Grpc.Net.Client / Grpc.Core.Api / Google.Protobuf / ...) are present in msi-extract/ArchestrA/Toolkits/Bin/x64. - Risk: the C++ AddNonStreamedValue TagNotFoundInCache(129) gate (the 2020 D2 blocker) may block btInput; mitigation = read the tag first. RegisterTags is emitted before that gate. Build order documented (read-only connect -> IL-rewrite -> write capture -> serializer -> commit+read-back -> AddHistoricalValuesAsync), each live step gated on per-action auth. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01B6mcaT2PjRFKcogzp9UkfC --- docs/plans/revision-write-path.md | 40 +++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/docs/plans/revision-write-path.md b/docs/plans/revision-write-path.md index a52a3e0..519195f 100644 --- a/docs/plans/revision-write-path.md +++ b/docs/plans/revision-write-path.md @@ -173,6 +173,46 @@ is now correctly identified as front-door `RegisterTags` (NOT `OpenStorageConnec `AddHistoricalValuesAsync` is blocked on capturing the regular-tag `RegisterTags` `btTagInfos` + the `AddNonStreamValues` `btInput`.** +### R3.1 capture plan (2026-06-21): drive the native 2023 R2 gRPC client + IL-rewrite the byte[] payloads + +Feasibility verified end-to-end against `histsdk-2023r2-analysis/bin`: + +- **Self-contained, loadable.** 2023 R2 `aahClientManaged.dll` is a 20 MB **mixed-mode C++/CLI** + assembly whose native imports are only Windows + VC++ runtime (`MSVCP140`/`VCRUNTIME140_1`) — **no + external AVEVA native dependency / no Historian install required** to load it in a `net481` x64 + process. The native C++ `HistorianClient` (the `.HistorianClient.*` globals, + e.g. `AddNonStreamedValueAsync(client, &HISTORIAN_VALUE2, &SError)`) is compiled *into* it and is + what builds `btInput`; it then hands the `byte[]` to the **managed** gRPC client. +- **gRPC routes through managed code → IL-rewrite-able.** `Archestra.Historian.GrpcClient.dll` + (`Grpc.Net`-based) is pure managed; `GrpcHistoryClient` holds both `m_historyClient` and + `m_transactionClient`. Capture targets: + - `GrpcHistoryClient.RegisterTags(string handle, byte[] tagInfos, …)` → dump `tagInfos` + - `GrpcHistoryClient.AddNonStreamValues(string handle, string transactionId, byte[] inBuff, …)` → dump `inBuff` + Use the existing dnlib IL-rewrite tooling (`tools/AVEVA.Historian.ReverseInstrumentation` + + `instrument-wcf-writemessage` pattern), writing rewrites to a copy under + `docs/reverse-engineering/dnlib-write-copy/` — never touch `histsdk-2023r2-analysis/bin` originals. +- **gRPC runtime deps are available.** `Archestra.Historian.GrpcClient.dll` references `Grpc.Net.Client`, + `Grpc.Core.Api`, `Grpc.Net.Client.Web`, `Google.Protobuf`, etc. — the full set is present in + `histsdk-2023r2-analysis/msi-extract/ArchestrA/Toolkits/Bin/x64/` (alongside the 5 core DLLs in + `…/bin/`). Assemble all of them into the harness runtime dir so `Assembly.LoadFrom` + the sibling + resolver can satisfy the gRPC stack. +- **Driving the write (reflection, like `NativeTraceHarness`).** `ArchestrA.HistorianAccess.OpenConnection(HistorianConnectionArgs, out err)` + with `HistorianConnectionArgs { ServerName, TcpPort=32565, ConnectionMode=HistorianConnectionMode.Historian + (the 2023 R2 gRPC mode; `ClassicHistorian`=legacy), ConnectionType=Process, ReadOnly=false, + IntegratedSecurity/UserName/Password, AllowUnTrustedConnection=true, SecurityInfo=cert }`, then + `AddNonStreamedValue(ConnectionIndex.Process, HistorianDataValue, bVersioned:false, out err)`. +- **Cache-gate risk (the D2 blocker).** The C++ `AddNonStreamedValueAsync` has a per-connection + `TagNotFoundInCache (129)` gate that, in the 2020 D2 probe, rejected the value **before any bytes + left the client**. Mitigation to try: **read the target tag first** (populate the per-connection + cache) before `AddNonStreamedValue`. `RegisterTags` is emitted during registration *before* this + gate, so its `tagInfos` is capturable **even if** the gate still blocks `btInput`. + +Build order (each live step = prod write, per-action auth): (1) `net481` x64 harness loads the 2023 R2 +DLL + opens a **read-only** gRPC connection + reads the tag (proves load+connect, no write); (2) +IL-rewrite `Archestra.Historian.GrpcClient.dll`; (3) write-enabled run → capture `RegisterTags` +`tagInfos` (+ `btInput` if the gate passes); (4) build golden serializer(s) in `src/`; (5) real +`bCommit=true` write + SQL read-back on a sandbox tag → ship `AddHistoricalValuesAsync`. + --- ## Legacy WCF analysis (preserved — still accurate for the 2020 WCF transport)