R0.1 browse over gRPC SHIPPED — QueryTag cracked, M0 gRPC parity complete
Wires HistorianClient.BrowseTagNamesAsync over gRPC (Transport==RemoteGrpc) via
Grpc/HistorianGrpcTagClient.BrowseTagNamesAsync: StartTagQuery(OData) -> paged
QueryTag -> EndTagQuery. Live-verified against a real 2023 R2 server (returns Sys* tags).
QueryTag packet-id recovered WITHOUT native disassembly: a .rdata packet-descriptor
table in aahClientManaged.dll lists {0x6751,1}=StartTagQuery immediately followed by
{0x6752,1}=QueryTag (found via pefile byte-scan of .rdata), confirmed live.
Wire format (live-verified):
- request btRequest = u16 0x6752 + u16 version(1) + u16 queryType(1=names) + u32 startIndex + u32 count
- response btResonse = u32 count + per-name(u32 charCount + UTF-16LE) + trailer (NextIndex/metadata, ignored)
- new HistorianTagQueryProtocol.ParseTagNameQueryPage tolerates the trailer
- GlobToODataFilter translates the SDK glob filter to OData (Pre*->startswith, *suf->endswith,
*mid*->contains, exact->eq); the 2023 R2 metadata-server parses filters as OData.
Replaces the earlier RE probe helpers with the shipped browse path. Adds golden-byte
(BuildQueryTagRequest) + 8 glob-translation unit tests + gated live browse test.
226 unit tests pass; 5/5 live gRPC tests pass (read, probe, system-param, metadata, browse).
Milestone 0 (full gRPC parity) is complete.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B6mcaT2PjRFKcogzp9UkfC
This commit is contained in:
@@ -87,7 +87,7 @@ public sealed class HistorianGrpcIntegrationTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task StartTagQuery_OverGrpc_AcceptsODataFilter()
|
||||
public async Task BrowseTagNamesAsync_OverGrpc_ReturnsSystemTags()
|
||||
{
|
||||
string? host = Environment.GetEnvironmentVariable("HISTORIAN_GRPC_HOST");
|
||||
if (string.IsNullOrWhiteSpace(host) || string.IsNullOrEmpty(Environment.GetEnvironmentVariable("HISTORIAN_USER")))
|
||||
@@ -95,19 +95,16 @@ public sealed class HistorianGrpcIntegrationTests
|
||||
return;
|
||||
}
|
||||
|
||||
// R0.1 finding (2026-06-21): on the 2023 R2 gRPC front door the metadata-server pipe IS
|
||||
// reachable (unlike 2020 WCF) and StartActiveTagnamesQuery parses the filter as OData —
|
||||
// startswith/contains/eq succeed; SQL-LIKE "%"/glob "*" fail with "ODataFilter: bad token".
|
||||
// StartTagQuery returns the 8-byte (queryHandle, tagCount) response. The follow-on QueryTag
|
||||
// paging request format is not yet captured (see roadmap R0.1), so browse is not yet wired.
|
||||
HistorianClientOptions options = BuildOptions(host);
|
||||
var result = await Task.Run(() =>
|
||||
AVEVA.Historian.Client.Grpc.HistorianGrpcTagClient.ProbeStartTagQuery(options, "startswith(TagName,'Sys')", CancellationToken.None));
|
||||
// Full R0.1 browse over gRPC: StartTagQuery(OData) -> paged QueryTag(0x6752) -> EndTagQuery.
|
||||
HistorianClient client = new(BuildOptions(host));
|
||||
List<string> names = [];
|
||||
await foreach (string name in client.BrowseTagNamesAsync("Sys*", CancellationToken.None))
|
||||
{
|
||||
names.Add(name);
|
||||
}
|
||||
|
||||
Assert.True(result.Success,
|
||||
$"StartTagQuery(OData) should succeed; errLen={result.ErrorLength} " +
|
||||
$"err=\"{System.Text.Encoding.ASCII.GetString(result.Error).Replace('\0', '.')}\"");
|
||||
Assert.Equal(8, result.ResponseLength); // (queryHandle, tagCount)
|
||||
Assert.NotEmpty(names);
|
||||
Assert.All(names, n => Assert.StartsWith("Sys", n, StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
private static HistorianClientOptions BuildOptions(string host)
|
||||
|
||||
Reference in New Issue
Block a user