Files
histsdk/docs/reverse-engineering/grpc-tag-query-odata.md
T
Joseph Doherty 26ef5e5645 R0.1 browse probe: StartTagQuery over gRPC takes an OData filter (live)
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
2026-06-21 14:58:12 -04:00

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.