R1.4 GetHistorianInfo: bounded out on 2020 WCF (named-value-only, no struct)

Captured the native HistorianAccess.GetHistorianInfo(out HistorianInfo, out err)
and decoded the wire: over 2020 WCF, GETHI is a named-value query whose only
working key is "HistorianVersion" (response ~30 bytes = the version string).
Probed 7 storage-mode key names -> all ok=False/err. The 518-byte HISTORIAN_INFO
struct + EventStorageMode@514 is the 2023R2 HCAL-native/gRPC model (confirmed
from the decompiled 2023R2 source); on 2020 the native client derives the mode
outside the WCF wire.

Version is already exposed (ProbeAsync/GetRuntimeParameterAsync), so no hollow
GetHistorianInfoAsync is shipped (same disposition as R1.3 timezone). This
completes the reachable 2020-WCF M1 read surface; remaining M1 = config writes
(gated on explicit request) or gRPC/2023R2-only items.

RE aids kept: harness `historian-info` scenario, Capture-HistorianInfo.ps1,
decode-historian-info-capture.py, and StringHandleProbeDiagnosticTests
.GETHI_CandidateInfoNames (asserts the named-value-only finding; gated).
Docs: wcf-historian-info.md (new) + roadmap/matrix/wall-doc updates. 230 tests green.

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:
Joseph Doherty
2026-06-20 23:42:27 -04:00
parent 1a539882d0
commit fbd839077b
8 changed files with 441 additions and 7 deletions
@@ -246,6 +246,33 @@ internal static class Program
}));
return 0;
}
else if (openSuccess && status.ConnectedToServer && IsHistorianInfoScenario(scenario))
{
// R1.4 capture: drive HistorianAccess.GetHistorianInfo(out HistorianInfo, out error)
// so instrument-wcf-{write,read}message can observe the WCF GETHI pRequestBuff that
// returns the full 518-byte HISTORIAN_INFO struct (version@0 + EventStorageMode@514),
// distinct from the named-value "HistorianVersion" request. Pure status read.
MethodInfo getInfoMethod = accessType.GetMethods()
.First(m => m.Name == "GetHistorianInfo"
&& m.GetParameters().Length == 2
&& m.GetParameters()[0].ParameterType.IsByRef
&& m.GetParameters()[1].ParameterType.IsByRef);
object infoError = Activator.CreateInstance(errorType)!;
object?[] infoArgs = new object?[] { null, infoError };
WriteRuntimeMethodPointerSnapshot(assembly, runtimeMethodPointerOutput, runtimeMethodPointerFilters, repoRoot, scenario, "before-get-historian-info");
bool infoOk = (bool)getInfoMethod.Invoke(access, infoArgs)!;
Console.WriteLine(Serialize(new
{
Scenario = scenario,
GetHistorianInfoReturned = infoOk,
HistorianInfoType = getInfoMethod.GetParameters()[0].ParameterType.GetElementType()?.FullName,
HistorianInfo = infoArgs[0] is null ? null : SnapshotObject(infoArgs[0]!),
Error = SnapshotObject(infoArgs[1]!),
}));
return 0;
}
else if (openSuccess && status.ConnectedToServer && IsEventSendScenario(scenario))
{
// R2.1 capture: drive AddStreamedValue(HistorianEvent) and let instrument-wcf-*
@@ -1502,6 +1529,19 @@ internal static class Program
|| scenario.Equals("runtime-parameter", StringComparison.OrdinalIgnoreCase);
}
/// <summary>
/// Historian-info scenario (R1.4 capture): opens a normal authenticated process connection and
/// calls <c>GetHistorianInfo(out HistorianInfo, out error)</c> so instrument-wcf-{write,read}message
/// can observe the WCF GETHI <c>pRequestBuff</c>/<c>pResponseBuff</c> that returns the full
/// 518-byte HISTORIAN_INFO struct (version@0 + EventStorageMode@514). Pure status read.
/// </summary>
private static bool IsHistorianInfoScenario(string scenario)
{
return scenario.Equals("historian-info", StringComparison.OrdinalIgnoreCase)
|| scenario.Equals("hist-info", StringComparison.OrdinalIgnoreCase)
|| scenario.Equals("gethi", StringComparison.OrdinalIgnoreCase);
}
/// <summary>
/// SQL-command scenario (R1.1 capture): opens a normal authenticated process connection and
/// calls <c>ExecuteSqlCommand</c> (Retr.ExeC + Retr.GetR) so the string-handle SQL surface