4da5287d01
Execute HCAL roadmap R1.2 (GetRuntimeParameterAsync) end-to-end, and in doing so
discover that the "string-handle wall" blocking R1.1/R1.4/R1.5/R1.6 was a handle
FORMAT bug, not a missing native session/filter registration.
R1.2 (shipped, live-verified):
- Captured native GetRuntimeParameter -> WCF op aa/Stat/GETRP (string-handle op,
GETHI's shape), via scripts/Capture-RuntimeParam.ps1 + instrument-wcf-{write,read}message.
- HistorianRuntimeParameterProtocol serializes pRequestBuff (54 67 01 00 + uint
nameCount + per-name uint charCount + UTF-16) and parses pResponseBuff (version +
uint resultCount + CRetVariant 0x43 VT_BSTR + uint16 len + uint16 charCount + UTF-16).
- IStatusServiceContract2.GetRuntimeParameter (GETRP) op; HistorianWcfStatusClient
passes the Open2 storage-session GUID as the string handle, UPPERCASE.
- Public HistorianClient.GetRuntimeParameterAsync(name) via the dialect.
- Golden WcfRuntimeParameterProtocolTests + gated live test; returns HistorianVersion.
String-handle wall RESOLVED (proven, public APIs deferred):
- The Open2 storage GUID works as the string handle when sent UPPERCASE
(ToString("D").ToUpperInvariant()); earlier "blocked" probes used lowercase.
- Live-probed GETHI (R1.4) -> returns data; ExeC (R1.1) -> Retr.GetV prime -> ExeC ->
GetR returns a BinaryFormatter-serialized .NET DataTable. Gated
StringHandleProbeDiagnosticTests + scripts/Capture-ExecSql.ps1 + exec-sql harness scenario.
- Docs flipped: wcf-string-handle-wall.md RESOLVED banner; roadmap R1.1/R1.4 reachable,
R1.5/R1.6 likely; wcf-status-localhost.md GETRP section.
- R1.1/R1.4 public APIs NOT shipped: ExeC needs a GetR paging loop + a BinaryFormatter-
stream parser (BinaryFormatter is removed from .NET 10); GETHI full-info struct needs
its own capture.
223 unit tests pass; gated live tests green against the local 2020 Historian.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B6mcaT2PjRFKcogzp9UkfC
59 lines
2.4 KiB
C#
59 lines
2.4 KiB
C#
using AVEVA.Historian.Client.Protocol;
|
|
using AVEVA.Historian.Client.Wcf;
|
|
|
|
namespace AVEVA.Historian.Client.Tests;
|
|
|
|
public sealed class WcfRuntimeParameterProtocolTests
|
|
{
|
|
// GETRP pRequestBuff captured from the native client for GetRuntimeParameter("HistorianVersion")
|
|
// via scripts/Capture-RuntimeParam.ps1 + instrument-wcf-writemessage:
|
|
// 54 67 01 00 signature(0x6754) + version(1)
|
|
// 01 00 00 00 name count = 1
|
|
// 10 00 00 00 char count = 16
|
|
// UTF-16LE "HistorianVersion"
|
|
private const string CaptureRequestHex =
|
|
"54670100010000001000000048006900730074006F007200690061006E00560065007200730069006F006E00";
|
|
|
|
// GETRP pResponseBuff captured from the paired GETRPResponse (instrument-wcf-readmessage):
|
|
// 01 00 version = 1
|
|
// 01 00 00 00 result count = 1
|
|
// 43 CRetVariant type 0x43 (VT_BSTR)
|
|
// 1A 00 payload length = 26 (= charCount field + string bytes)
|
|
// 0C 00 char count = 12
|
|
// UTF-16LE "20,0,000,000"
|
|
private const string CaptureResponseHex =
|
|
"010001000000431A000C00320030002C0030002C003000300030002C00300030003000";
|
|
|
|
[Fact]
|
|
public void SerializeRequestMatchesInstrumentedNativeRequestBuffer()
|
|
{
|
|
byte[] actual = HistorianRuntimeParameterProtocol.SerializeRequest("HistorianVersion");
|
|
Assert.Equal(Convert.FromHexString(CaptureRequestHex), actual);
|
|
}
|
|
|
|
[Fact]
|
|
public void ParseSingleStringResultReadsTheCapturedResponseValue()
|
|
{
|
|
byte[] response = Convert.FromHexString(CaptureResponseHex);
|
|
string? value = HistorianRuntimeParameterProtocol.ParseSingleStringResult(response);
|
|
Assert.Equal("20,0,000,000", value);
|
|
}
|
|
|
|
[Fact]
|
|
public void ParseSingleStringResultReturnsNullForZeroResultCount()
|
|
{
|
|
// version(1) + result count(0)
|
|
byte[] empty = [0x01, 0x00, 0x00, 0x00, 0x00, 0x00];
|
|
Assert.Null(HistorianRuntimeParameterProtocol.ParseSingleStringResult(empty));
|
|
}
|
|
|
|
[Fact]
|
|
public void ParseSingleStringResultThrowsForUncapturedVariantType()
|
|
{
|
|
// version(1) + count(1) + a non-string variant marker (0x03, VT_I4 — not captured).
|
|
byte[] buffer = [0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x04, 0x00, 0x00, 0x00, 0x00];
|
|
Assert.Throws<ProtocolEvidenceMissingException>(
|
|
() => HistorianRuntimeParameterProtocol.ParseSingleStringResult(buffer));
|
|
}
|
|
}
|