using System.Runtime.Versioning; using System.ServiceModel; using System.ServiceModel.Channels; using AVEVA.Historian.Client.Models; using AVEVA.Historian.Client.Wcf.Contracts; namespace AVEVA.Historian.Client.Wcf; internal static class HistorianWcfStatusClient { public static Task GetSystemParameterAsync( HistorianClientOptions options, string parameterName, CancellationToken cancellationToken) { ArgumentException.ThrowIfNullOrWhiteSpace(parameterName); return Task.Run(() => GetSystemParameter(options, parameterName), cancellationToken); } /// Diagnostic: the GETRP return code / error description from the last /// call (set only when the server rejects it). public static string? LastRuntimeParameterError { get; private set; } public static Task GetRuntimeParameterAsync( HistorianClientOptions options, string parameterName, CancellationToken cancellationToken) { ArgumentException.ThrowIfNullOrWhiteSpace(parameterName); return Task.Run(() => GetRuntimeParameter(options, parameterName), cancellationToken); } public static Task GetConnectionStatusAsync( HistorianClientOptions options, CancellationToken cancellationToken) { return Task.Run(() => SynthesizeConnectionStatus(options), cancellationToken); } public static Task GetStoreForwardStatusAsync( HistorianClientOptions options, CancellationToken cancellationToken) { return Task.Run(() => SynthesizeStoreForwardStatus(options), cancellationToken); } private static string? GetSystemParameter(HistorianClientOptions options, string parameterName) { Guid contextKey = Guid.NewGuid(); var (histBinding, histEndpoint, _, _) = HistorianWcfBindingFactory.CreateBindingPair(options); Binding statusBinding = HistorianWcfBindingFactory.CreateAuxiliaryBinding(options); EndpointAddress statusEndpoint = HistorianWcfBindingFactory.CreateAuxiliaryEndpointAddress(options, HistorianWcfServiceNames.Status); string? value = null; HistorianWcfAuthChainHelper.OpenAuthenticatedConnection( options, histBinding, histEndpoint, contextKey, CancellationToken.None, additionalSetup: (_, context) => value = QuerySystemParameter(statusBinding, statusEndpoint, context.ClientHandle, parameterName)); return value; } private static string? GetRuntimeParameter(HistorianClientOptions options, string parameterName) { Guid contextKey = Guid.NewGuid(); var (histBinding, histEndpoint, _, _) = HistorianWcfBindingFactory.CreateBindingPair(options); Binding statusBinding = HistorianWcfBindingFactory.CreateAuxiliaryBinding(options); EndpointAddress statusEndpoint = HistorianWcfBindingFactory.CreateAuxiliaryEndpointAddress(options, HistorianWcfServiceNames.Status); string? value = null; LastRuntimeParameterError = null; HistorianWcfAuthChainHelper.OpenAuthenticatedConnection( options, histBinding, histEndpoint, contextKey, CancellationToken.None, additionalSetup: (_, context) => value = QueryRuntimeParameter(statusBinding, statusEndpoint, context.StorageSessionId, parameterName)); return value; } private static string? QueryRuntimeParameter(Binding statusBinding, EndpointAddress statusEndpoint, Guid storageSessionId, string parameterName) { // GETRP takes the storage-session GUID as a string handle, formatted exactly as the // native client sends it: uppercase, dash-separated, no braces. string handle = storageSessionId.ToString("D").ToUpperInvariant(); byte[] requestBuffer = HistorianRuntimeParameterProtocol.SerializeRequest(parameterName); ChannelFactory factory = new(statusBinding, statusEndpoint); IStatusServiceContract2 channel = factory.CreateChannel(); ICommunicationObject co = (ICommunicationObject)channel; try { bool ok = channel.GetRuntimeParameter(handle, requestBuffer, out byte[] responseBuffer, out byte[] errorBuffer); if (!ok) { LastRuntimeParameterError = $"GETRP returned false (responseLen={responseBuffer?.Length ?? 0}, errorLen={errorBuffer?.Length ?? 0})."; return null; } return HistorianRuntimeParameterProtocol.ParseSingleStringResult(responseBuffer ?? []); } finally { try { if (co.State == CommunicationState.Faulted) co.Abort(); else co.Close(); } catch { try { co.Abort(); } catch { } } try { if (factory.State == CommunicationState.Faulted) factory.Abort(); else factory.Close(); } catch { try { factory.Abort(); } catch { } } } } private static string? QuerySystemParameter(Binding statusBinding, EndpointAddress statusEndpoint, uint clientHandle, string parameterName) { ChannelFactory factory = new(statusBinding, statusEndpoint); IStatusServiceContract2 channel = factory.CreateChannel(); ICommunicationObject co = (ICommunicationObject)channel; try { bool ok = channel.GetSystemParameter(clientHandle, parameterName, out string parameterValue, out _, out _); return ok ? parameterValue : null; } finally { try { if (co.State == CommunicationState.Faulted) co.Abort(); else co.Close(); } catch { try { co.Abort(); } catch { } } try { if (factory.State == CommunicationState.Faulted) factory.Abort(); else factory.Close(); } catch { try { factory.Abort(); } catch { } } } } /// /// AVEVA's native HistorianAccess.GetConnectionStatus reads local C++ /// HistorianClient state (no WCF op exists for it). We synthesize an equivalent /// by attempting an authenticated session open: a successful auth+open implies /// ConnectedToServer = true. Store-forward and partner-connection state are not /// observable from a single client probe and remain false. /// private static HistorianConnectionStatus SynthesizeConnectionStatus(HistorianClientOptions options) { bool connected; string? error = null; try { Guid contextKey = Guid.NewGuid(); var (histBinding, histEndpoint, _, _) = HistorianWcfBindingFactory.CreateBindingPair(options); HistorianWcfAuthChainHelper.OpenAuthenticatedConnection( options, histBinding, histEndpoint, contextKey, CancellationToken.None); connected = true; } catch (Exception ex) { connected = false; error = $"{ex.GetType().Name}: {ex.Message}"; } return new HistorianConnectionStatus( ServerName: options.Host, Pending: false, ErrorOccurred: !connected, Error: error, ConnectedToServer: connected, ConnectedToServerStorage: connected, ConnectedToStoreForward: false, ConnectionKind: HistorianConnectionKind.Process); } /// /// Native HistorianAccess.GetStoreForwardStatus is also client-side state. /// Without a local store-forward sidecar to probe, we report defaults: not pending, /// no error, no data stored, not actively storing. Connection kind is Process by /// convention (event-only sessions are uncommon for this status helper). /// private static HistorianStoreForwardStatus SynthesizeStoreForwardStatus(HistorianClientOptions options) { return new HistorianStoreForwardStatus( ServerName: options.Host, Pending: false, ErrorOccurred: false, Error: null, DataStored: false, Storing: false, ConnectionKind: HistorianConnectionKind.Process); } }