namespace AVEVA.Historian.Client;
///
/// Identifies a versioned native Historian service interface whose reported interface
/// version is validated at connect time by .
///
internal enum HistorianServiceInterface
{
History,
Retrieval,
Status,
Transaction
}
///
/// Fail-closed check (roadmap item R0.6) that a Historian server reports the native
/// interface version this SDK's byte serializers were built against.
///
/// The opaque native buffers carried inside the WCF/MDAS message body — and, on 2023 R2,
/// inside the gRPC bytes fields — are framed per native interface version. Parsing
/// them against an unexpected version risks silent misinterpretation, so per the
/// "version-pin, fail closed" principle this throws
/// rather than best-effort parsing.
///
/// Supported versions are evidence-based, discovered from a live AVEVA Historian 2020
/// server (product 20.0.000) via the reverse-engineering wcf-probe command:
///
/// - History (Hist) interface version = 11
/// - Retrieval (Retr) interface version = 4
/// - Transaction (Trx) interface version = 2
///
/// The Status (Stat) service's GetInterfaceVersion returns 0 (not a real
/// version), so the Status interface is validated for reachability only, never value.
///
/// A 2023 R2 gRPC server may report different integers even though it carries the same
/// proven 2020 native buffers; until those integers are captured, point such a server at
/// this gate with set to
/// .
///
internal static class HistorianServerVersionGate
{
public const uint HistoryInterfaceVersion = 11;
public const uint RetrievalInterfaceVersion = 4;
public const uint TransactionInterfaceVersion = 2;
///
/// True when the service interface reports a meaningful version that should be matched.
/// Status is reachability-only (its GetInterfaceVersion returns 0).
///
public static bool IsValueGated(HistorianServiceInterface service) => service switch
{
HistorianServiceInterface.History => true,
HistorianServiceInterface.Retrieval => true,
HistorianServiceInterface.Transaction => true,
HistorianServiceInterface.Status => false,
_ => false
};
/// The interface version this SDK's serializers target for a value-gated service.
public static uint ExpectedVersion(HistorianServiceInterface service) => service switch
{
HistorianServiceInterface.History => HistoryInterfaceVersion,
HistorianServiceInterface.Retrieval => RetrievalInterfaceVersion,
HistorianServiceInterface.Transaction => TransactionInterfaceVersion,
_ => throw new ArgumentOutOfRangeException(nameof(service), service, "Service interface is not value-gated.")
};
///
/// Throws when version verification is enabled
/// and the server's reported interface version differs from the version this SDK targets.
/// No-op when is
/// , when the service is not value-gated (Status), or on a match.
///
public static void Validate(HistorianServiceInterface service, uint reportedVersion, HistorianClientOptions options)
{
ArgumentNullException.ThrowIfNull(options);
if (!options.VerifyServerInterfaceVersion || !IsValueGated(service))
{
return;
}
uint expected = ExpectedVersion(service);
if (reportedVersion == expected)
{
return;
}
throw new ProtocolEvidenceMissingException(
$"{service} interface version {reportedVersion} (this SDK's serializers target version {expected}); " +
$"set {nameof(HistorianClientOptions)}.{nameof(HistorianClientOptions.VerifyServerInterfaceVersion)}=false to bypass at your own risk");
}
}