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"); } }