Files
histsdk/docs/reverse-engineering/grpc-tag-query-odata.md
T
Joseph Doherty 4c9f0d476c docs: QueryTag error = InvalidPacketId (72); needs native aahClient.dll RE
Deepened the R0.1 browse finding. QueryTag's constant rejection decodes to
ArchestrA.CloudHistorian.Contract.ErrorCode.InvalidPacketId (72): the btRequest needs
a QueryTag-specific packet-id header (the generic 0x6751/v1 header StartTagQuery accepts
is rejected). The semantic fields are known from CloudHistorian.Contract
(QueryHandle/QueryType/StartIndex/TagCount request; TagNames[]+TagMetadataBuffer response),
but the binary packet framing lives in native aahClient.dll — aahClientManaged.dll is
mixed-mode (ilspycmd cannot decompile it) and no managed assembly builds the buffer.
Finishing QueryTag needs native RE (Ghidra/IDA) or a live gRPC capture of the stock client.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B6mcaT2PjRFKcogzp9UkfC
2026-06-21 15:16:19 -04:00

3.2 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 (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.dllaahClientManaged.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.