Recorded the native-disassembly attempt on aahClientManaged.dll (mixed-mode): ilspycmd cannot decompile it; capstone byte-search can't locate the StartTagQuery 0x6751 marker (not a plain immediate — it's an .rdata constant loaded RIP-relative, the .text "51 67 00 00" hits are coincidental jump-table data). Managed metadata gives QueryTag field semantics but not the binary packet-id. Finishing QueryTag needs Ghidra/IDA xref analysis or the live IL-rewrite capture. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B6mcaT2PjRFKcogzp9UkfC
4.6 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.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
0x6751is not a plainmovimmediate in.text(no66 C7 … 51 67and no cleanB8 51 67 00 00); the three51 67 00 00hits in.textare coincidental jump-table data (disassembly around them is garbage). The constant lives in the.rdatapool (which holds the51 67bytes) 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 codeQTLQ(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.