gRPC M0 R0.2: tag metadata over gRPC (GetTagInfosFromName, live-verified)
Routes HistorianClient.GetTagMetadataAsync over gRPC when Transport==RemoteGrpc,
via the new Grpc/HistorianGrpcTagClient calling RetrievalService.GetTagInfosFromName
(the plural string-handle metadata op).
- String handle = the Open2 storage-session GUID formatted uppercase (the format
that resolves the native string-handle path); threaded out of the shared handshake
via a new HistorianGrpcHandshake.Session { ClientHandle, StorageSessionId, StringHandle }.
- Request btTagNames = uint count + per-name(uint charCount + UTF-16LE) — golden-byte
unit-tested (BuildTagNamesBuffer).
- Response btTagInfos = uint count + CTagMetadata records — decoded by the existing
HistorianTagQueryProtocol.ParseGetTagInfoResponse; data type via the shared MapDataType.
The 2020 WCF string-handle wall does NOT apply on the gRPC front door, as the
string-handle-wall RE note predicted. LIVE-VERIFIED against a real 2023 R2 server:
GetTagMetadataAsync returns the requested tag with a valid decoded data type.
216 unit tests pass. Captured framing confirmed live then discarded; no tag names
or identities committed.
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:
@@ -66,6 +66,26 @@ public sealed class HistorianGrpcIntegrationTests
|
||||
Assert.False(string.IsNullOrWhiteSpace(version));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetTagMetadataAsync_OverGrpc_ReturnsRequestedTag()
|
||||
{
|
||||
string? host = Environment.GetEnvironmentVariable("HISTORIAN_GRPC_HOST");
|
||||
string? tag = Environment.GetEnvironmentVariable("HISTORIAN_TEST_TAG");
|
||||
if (string.IsNullOrWhiteSpace(host) || string.IsNullOrWhiteSpace(tag)
|
||||
|| string.IsNullOrEmpty(Environment.GetEnvironmentVariable("HISTORIAN_USER")))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
HistorianClient client = new(BuildOptions(host));
|
||||
HistorianTagMetadata? metadata = await client.GetTagMetadataAsync(tag, CancellationToken.None);
|
||||
|
||||
Assert.NotNull(metadata);
|
||||
Assert.Equal(tag, metadata!.Name);
|
||||
// A real metadata record decodes to a known data type (descriptor passed MapDataType).
|
||||
Assert.True(Enum.IsDefined(metadata.DataType));
|
||||
}
|
||||
|
||||
private static HistorianClientOptions BuildOptions(string host)
|
||||
{
|
||||
string? user = Environment.GetEnvironmentVariable("HISTORIAN_USER");
|
||||
|
||||
@@ -135,6 +135,23 @@ public sealed class HistorianGrpcTransportTests
|
||||
Assert.Equal("20.0.000", response.StrParameterValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BuildTagNamesBuffer_EncodesCountThenLengthPrefixedUtf16Names()
|
||||
{
|
||||
// R0.2 request framing: uint count + per-name(uint charCount + UTF-16LE). Golden bytes.
|
||||
byte[] buffer = AVEVA.Historian.Client.Grpc.HistorianGrpcTagClient.BuildTagNamesBuffer(["AB", "C"]);
|
||||
|
||||
byte[] expected =
|
||||
[
|
||||
0x02, 0x00, 0x00, 0x00, // count = 2
|
||||
0x02, 0x00, 0x00, 0x00, // "AB" char count = 2
|
||||
0x41, 0x00, 0x42, 0x00, // 'A','B' UTF-16LE
|
||||
0x01, 0x00, 0x00, 0x00, // "C" char count = 1
|
||||
0x43, 0x00 // 'C' UTF-16LE
|
||||
];
|
||||
Assert.Equal(expected, buffer);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OpenConnectionRequest_CarriesNativeOpen2BufferUnchanged()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user