diff --git a/docs/reverse-engineering/grpc-event-query-capture.md b/docs/reverse-engineering/grpc-event-query-capture.md index 1acee4c..b39b510 100644 --- a/docs/reverse-engineering/grpc-event-query-capture.md +++ b/docs/reverse-engineering/grpc-event-query-capture.md @@ -381,15 +381,54 @@ live-server access reference. TLS-decrypting mitm on the loopback forward — to see any connection-level header/affinity our capture can't see. -4. **Server-side ground truth.** Via the SOCKS→SQL relay (user-authorized, read-only), inspect the - `Runtime.dbo.Events` schema for any per-connection / per-client / source-session column that would - explain why the server returns the rows to the native connection but not ours. Also check whether the - StorageService/event-store path has a connection-scoping notion the History-service event query - depends on. +2. **TLS client identity / certificate.** The native used `SecurityMode=TransportCertificate`. Determine + whether it presents a **client certificate** the server uses to scope events (our SDK presents none — + `AllowUntrustedServerCertificate=true`, server cert only). TEST: capture the TLS handshake (e.g. + `SSLKEYLOGFILE` + Wireshark, or a decrypting proxy) for a native `capture-event` run and check the + Certificate message; if a client cert is presented, replicate it. **Lower-probability after #1: the + plain-HTTP/2 path presents no client cert either, yet auth + registration still succeed and the gate + persists — so the gate is not at the TLS-identity layer the cert would affect.** -If 1–4 don't crack it, the realistic conclusion is that gRPC event-row retrieval has a server-side -connection-identity dependency not reachable from a pure-managed client, and it stays documented as -auth-solved / retrieval-connection-gated. +3. **HTTP/2-level capture.** The byte[]/handle capture is RPC-payload only. Capture the actual HTTP/2 + frames (HEADERS/SETTINGS/stream IDs, connection reuse) for the native run vs ours — via a + TLS-decrypting mitm on the loopback forward — to see any connection-level header/affinity our capture + can't see. + +4. ~~**Server-side ground truth.**~~ **ANSWERED 2026-06-23 (DISPROVES the data-scoping premise).** Via + the SOCKS→SQL relay (read-only; `artifacts/.../sqlschema/`, gitignored), dumped the full event schema + on the live `Runtime` DB. Findings: + - **No per-connection / per-client / per-session column exists anywhere in the event store.** The only + "scoping-like" columns on `Events`/`EventHistory`/snapshots are event *content* — `Source_*` (event + origin area/object/PV), `User_*` (who acknowledged), `Provider_NodeName` (alarm provider node), + `SourceServer`/`SourceTag` (cross-server replication). None is "which client connection requested + this." + - **The rich `Events` view is not a relational table — it is served live by the Historian engine via + the `INSQL` OLE DB provider** (`sys.servers` shows linked servers `INSQL` + `INSQLD`; + `OBJECT_DEFINITION('dbo.Events')` is `NULL` = encrypted remote view). The Historian's own + `EventHistory` base table holds just 168 rows / 1 tag (the internal event-tag detector log); the + alarm/event journal the gRPC query reads lives in the engine, surfaced through INSQL. + - **Decisive: same engine, same `-90d..now` window, two paths diverge.** The `Events` view (via INSQL) + returns **71,332 events** for that window — most recent `Alarm.Set` firing seconds before the probe + (live, every few seconds) — while gRPC `StartEventQuery` for **our** connection returns **0**. The + data is global, abundant, recent, and identical-window-addressable; the engine simply does not hand + it to our gRPC connection. + + → There is **nothing in the data to scope by**, so the zero-row gate is **not** data scoping. It is the + gRPC RetrievalService's **per-connection in-process execution state** — the same class of wall as + `DeleteTagExtendedProperties` (server-side native in-process working-set, not reconstructable from + byte-identical wire requests). Reproduce: `artifacts/.../sqlschema/` (Program.cs = SOCKS5 relay + + `Microsoft.Data.SqlClient`; authenticate with the server's SQL login, not the domain Historian acct — + creds in the gitignored creds file). + +**Conclusion (after #1 disproven + #4 answered).** Three independent angles are now exhausted: client +payload (byte-identical), transport (native HTTP/2 == gRPC-Web, both 0 rows), and data store (global, +unscoped, 71,332 events the engine serves via INSQL but withholds from our gRPC connection). The gate is +a **server-internal per-connection retrieval working-set** that a pure-managed client cannot reconstruct +by matching wire bytes, transport, or data. The remaining angles (#2 client-cert, #3 HTTP/2-frame +capture) are low-probability — #1 showed auth+registration succeed with no client cert over plain HTTP/2 +and the gate still holds. **gRPC event-row retrieval stands documented as auth-solved / +retrieval-server-gated**; `ReadEventsAsync` over gRPC keeps the honest no-row throw, and event reads use +the WCF transport. **2 of 3 layers cleared** (key exchange + client key); the 3rd (token construction) is localized to a specific managed method, pending dnlib extraction. ExchangeKey + the v8 serializer are committed; the