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)