diff --git a/docs/reverse-engineering/grpc-tag-query-odata.md b/docs/reverse-engineering/grpc-tag-query-odata.md index 1650f6a..26c18db 100644 --- a/docs/reverse-engineering/grpc-tag-query-odata.md +++ b/docs/reverse-engineering/grpc-tag-query-odata.md @@ -28,15 +28,26 @@ Success response `btResponse` is the 8-byte `(queryHandle:uint, tagCount:uint)` `*` → 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 — OPEN (needs native RE of aahClient.dll) -`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). +`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.