# R0.1 browse over gRPC — StartTagQuery takes an OData filter (2026-06-21) Live-probed `RetrievalService.StartTagQuery` / `QueryTag` against a real **2023 R2** server over the gRPC front door (string-handle = uppercase Open2 storage GUID). Key result: **browse is feasible on 2023 R2** — the 2020 WCF "metadata-server pipe" wall does **not** block here. ## StartTagQuery — CRACKED `StartTagQuery(strHandle, btRequest)` where `btRequest` = the native `marker(26449) + version(1) + WriteHistorianString(filter)` buffer (`HistorianTagQueryProtocol.CreateStartTagQueryAttempt`). The server runs `CMdServer::StartTagQuery::StartActiveTagnamesQuery` over `\\.\pipe\aahMetadataServer\console` and **parses the filter string as OData** (not SQL-LIKE). Swept filters: | filter | result | |---|---| | `startswith(TagName,'Sys')` | ✅ success, 8-byte response | | `contains(TagName,'Sys')` | ✅ success | | `TagName eq 'SysTimeSec'` | ✅ success | | `` (empty) | ✅ success (all tags) | | `Sys*` / `*` | ❌ `ODataFilter ... bad token` | | `TagName like 'Sys%'` / `Name like 'Sys%'` | ❌ rejected | Success response `btResponse` is the 8-byte `(queryHandle:uint, tagCount:uint)` pair (`ParseStartTagQueryResponse`). Live: `startswith(TagName,'Sys')` → tagCount = 220. **Implication for the public API:** browse must translate the SDK's glob filter to OData — `*` → empty, `Pre*` → `startswith(TagName,'Pre')`, `*sub*` → `contains(TagName,'sub')`, exact → `TagName eq '...'`. (Escaping single-quotes in names still TBD.) ## QueryTag — OPEN (needs native RE of aahClient.dll) `QueryTag(strHandle, uiQueryHandle, btRequest)` is the paging call that returns the tag-name rows. Every `btRequest` shape tried returns the constant native error **type 4 / code 72 = `InvalidPacketId`** (`ArchestrA.CloudHistorian.Contract.ErrorCode.InvalidPacketId = 72`; the empty buffer alone gives a different code). So the `btRequest` must carry a **packet-id header specific to QueryTag** that we don't have — the generic `0x6751`/version-1 header (which StartTagQuery accepts) is rejected here. **Semantic fields are known** from `ArchestrA.CloudHistorian.Contract`: - request `QueryTagRequest` = `QueryHandle:uint("q") + QueryType:ushort("t") + StartIndex:uint("s") + TagCount:uint("c")` - response `QueryTagResponse` = `QueryHandle:uint + TagNames:string[]("t") + NextIndex:uint("i") + TagMetadataBuffer:byte[]("tb")` — so QueryTag returns the names directly (plus an optional metadata buffer), and pages via StartIndex/NextIndex. What's missing is the **binary `btRequest` packet framing** (the QueryTag packet id + how those fields are laid out). That serializer lives in **native `aahClient.dll`** — `aahClientManaged.dll` is **mixed-mode (C++/CLI)** so ilspycmd cannot decompile it, and no managed assembly builds the buffer. Completing QueryTag therefore requires **native RE (Ghidra/IDA on `aahClient.dll`)** or a **live gRPC capture** of the stock 2023 R2 client browsing. Do not ship a guessed QueryTag request (project discipline: no guessed wire bytes). Probe helpers live in `Grpc/HistorianGrpcTagClient` (`ProbeStartTagQuery`, `ProbeTagQuerySequence`) and the gated `StartTagQuery_OverGrpc_AcceptsODataFilter` test pins the StartTagQuery+OData result. ### Native-RE attempt (2026-06-21) — needs Ghidra/IDA The QueryTag packet-id is built in native code (the C++ HCAL inside the **mixed-mode** `aahClientManaged.dll`). Lightweight tooling was exhausted and is insufficient: - **ilspycmd** cannot decompile or even list the mixed-mode assembly (throws). - **capstone** byte-search: the StartTagQuery marker `0x6751` is **not** a plain `mov` immediate in `.text` (no `66 C7 … 51 67` and no clean `B8 51 67 00 00`); the three `51 67 00 00` hits in `.text` are coincidental jump-table data (disassembly around them is garbage). The constant lives in the `.rdata` pool (which holds the `51 67` bytes) and is loaded RIP-relative — so the serializer can't be found by immediate-scanning without cross-reference analysis. - Managed metadata (`ArchestrA.CloudHistorian.Contract`) gives the field semantics — `QueryTagRequest{SessionId, QueryHandle:uint, QueryType:ushort, StartIndex:uint, TagCount:uint}`, DataContract code `QTLQ` (StartTagQuery = `TLQR`) — but **not** the binary packet-id/framing. Finishing QueryTag therefore requires **Ghidra/IDA** (decompiler + xref to the `.rdata` marker constant to find the serializer and read the QueryTag packet-id + byte layout), or the **live capture** option (IL-rewrite `Archestra.Historian.GrpcClient.QueryTag` to log `requestBuffer` while a real 2023 R2 client browses). StartTagQuery (the hard OData part) remains cracked and live-verified.