Probes the 2023 R2 gRPC browse path and records the finding. The front door does NOT hit the 2020 WCF metadata-server-pipe wall. - RetrievalService.StartTagQuery is cracked: the server (CMdServer::StartActiveTagnamesQuery over \.\pipe\aahMetadataServer\console) parses the filter as OData. startswith()/ contains()/eq/empty succeed and return the 8-byte (queryHandle, tagCount); SQL-LIKE "%" and glob "*" fail with "ODataFilter: bad token". Live: 220 Sys* tags counted. - QueryTag (paging) remains: every guessed btRequest returns a constant native error type 4 / code 72 (content-independent) -> framing needs a native capture, not guessing. Adds RE probe helpers Grpc/HistorianGrpcTagClient.ProbeStartTagQuery + ProbeTagQuerySequence, a gated StartTagQuery_OverGrpc_AcceptsODataFilter test, and the finding doc docs/reverse-engineering/grpc-tag-query-odata.md. Browse is not yet wired (QueryTag open). 217 unit tests pass; 5/5 live gRPC tests pass. No tag names/identities committed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B6mcaT2PjRFKcogzp9UkfC
2.4 KiB
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 (one capture away)
QueryTag(strHandle, uiQueryHandle, btRequest) is the paging call that should return the actual
tag-name rows. Every btRequest shape tried returns a constant native error type 4 / code 72
(independent of content: empty, count, column-name historian-string, $select=TagName,
marker+version+name all give the same 04 48000000). The constant code regardless of input means the
request framing is wrong, not the field values — this needs a native capture of the real 2023 R2
client driving a browse to recover the exact QueryTag btRequest (and the row framing in
btResonse). 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.