using System.ComponentModel.DataAnnotations;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
namespace ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient;
///
/// OPC UA Client (gateway) driver configuration. Bound from DriverConfig JSON at
/// driver-host registration time. Models the settings documented in
/// docs/v2/driver-specs.md §8.
///
///
/// This driver connects to a REMOTE OPC UA server and re-exposes its address space
/// through the local OtOpcUa server — the opposite direction from the usual "server
/// exposes PLC data" flow. Tier A (pure managed, OPC Foundation reference SDK); universal
/// protections cover it.
///
// A record (not a plain class) so G-2b's OpcUaClientSecretResolution can produce a
// credential-resolved copy with a `with` expression — every property keeps its init setter.
public sealed record OpcUaClientDriverOptions
{
///
/// Remote OPC UA endpoint URL, e.g. opc.tcp://plc.internal:4840. Convenience
/// shortcut for a single-endpoint deployment — equivalent to setting
/// to a list with this one URL. When both are provided,
/// the list wins and is ignored.
///
public string EndpointUrl { get; init; } = "opc.tcp://localhost:4840";
///
/// v3 authored raw tags for this driver instance — the deploy artifact (Wave C) populates this
/// with the cluster's raw OPC UA Client tags. Each carries the tag's
/// RawPath identity plus a driver TagConfig blob whose nodeId key holds the
/// upstream OPC UA node id string the driver dials. The driver builds a RawPath → nodeId
/// resolution table (via NamespaceMap, in the sibling .Session project) from this
/// list at connect time; a
/// read/write/subscribe/history reference is always a RawPath resolved through that table.
///
public IReadOnlyList RawTags { get; init; } = [];
///
/// Ordered list of candidate endpoint URLs for failover. The driver tries each in
/// order at OpcUaClientDriver.InitializeAsync and on session drop;
/// the first URL that successfully connects wins. Typical use-case: an OPC UA server
/// pair running in hot-standby (primary 4840 + backup 4841) where either can serve
/// the same address space. Leave unset (or empty) to use
/// as a single-URL shortcut.
///
public IReadOnlyList EndpointUrls { get; init; } = [];
///
/// Per-endpoint connect-attempt timeout during the failover sweep. Short enough that
/// cycling through several dead servers doesn't blow the overall init budget, long
/// enough to tolerate a slow TLS handshake on a healthy server. Applied independently
/// of which governs steady-state operations.
///
public TimeSpan PerEndpointConnectTimeout { get; init; } = TimeSpan.FromSeconds(3);
///
/// Security policy to require when selecting an endpoint. Either a
/// enum constant or a free-form string (for
/// forward-compatibility with future OPC UA policies not yet in the enum).
/// Matched against EndpointDescription.SecurityPolicyUri suffix — the driver
/// connects to the first endpoint whose policy name matches AND whose mode matches
/// . When set to
/// the driver picks any unsecured endpoint regardless of policy string.
///
public OpcUaSecurityPolicy SecurityPolicy { get; init; } = OpcUaSecurityPolicy.None;
/// Security mode.
public OpcUaSecurityMode SecurityMode { get; init; } = OpcUaSecurityMode.None;
/// Authentication type.
public OpcUaAuthType AuthType { get; init; } = OpcUaAuthType.Anonymous;
/// User name (required only for ).
public string? Username { get; init; }
/// Password (required only for ).
public string? Password { get; init; }
///
/// Filesystem path to the user-identity certificate (PFX/PEM). Required when
/// is . The driver
/// loads the cert + private key, which the remote server validates against its
/// TrustedUserCertificates store to authenticate the session's user token.
/// Leave unset to use the driver's application-instance certificate as the user
/// token (not typical — most deployments have a separate user cert).
///
public string? UserCertificatePath { get; init; }
///
/// Optional password that unlocks when the PFX is
/// protected. PEM files generally have their password on the adjacent key file; this
/// knob only applies to password-locked PFX.
///
public string? UserCertificatePassword { get; init; }
/// Server-negotiated session timeout. Default 120s per driver-specs.md §8.
public TimeSpan SessionTimeout { get; init; } = TimeSpan.FromSeconds(120);
/// Client-side keep-alive interval.
public TimeSpan KeepAliveInterval { get; init; } = TimeSpan.FromSeconds(5);
///
/// When true, the driver accepts any self-signed / untrusted server certificate.
/// Dev-only — must be false in production so MITM attacks against the opc.tcp
/// channel fail closed.
///
public bool AutoAcceptCertificates { get; init; } = false;
///
/// Application URI the driver reports during session creation. Must match the
/// subject-alt-name on the client certificate if one is used, which is why it's a
/// config knob rather than hard-coded.
///
public string ApplicationUri { get; init; } = "urn:localhost:OtOpcUa:GatewayClient";
///
/// Friendly name sent to the remote server for diagnostics. Shows up in the remote
/// server's session-list so operators can identify which gateway instance is calling.
///
public string SessionName { get; init; } = "OtOpcUa-Gateway";
/// Connect + per-operation timeout.
public TimeSpan Timeout { get; init; } = TimeSpan.FromSeconds(10);
///
/// Root NodeId to mirror. Default null = ObjectsFolder (i=85). Set to
/// a scoped root to restrict the address space the driver exposes locally — useful
/// when the remote server has tens of thousands of nodes and only a subset is
/// needed downstream.
///
public string? BrowseRoot { get; init; }
///
/// Cap on total nodes discovered during DiscoverAsync. Default 10_000 —
/// bounds memory on runaway remote servers without being so low that normal
/// deployments hit it. When the cap is reached discovery stops and a warning is
/// written to the driver health surface; the partially-discovered tree is still
/// projected into the local address space.
///
[Range(1, int.MaxValue)]
public int MaxDiscoveredNodes { get; init; } = 10_000;
///
/// Max hierarchical depth of the browse. Default 10 — deep enough for realistic
/// OPC UA information models, shallow enough that cyclic graphs can't spin the
/// browse forever.
///
[Range(1, int.MaxValue)]
public int MaxBrowseDepth { get; init; } = 10;
///
/// Timeout for the AdminUI Test Connect probe, in seconds. The AdminUI clamps to a
/// 60s server-side maximum; this default is what the form pre-fills for new instances.
///
[Range(1, 60)]
public int ProbeTimeoutSeconds { get; init; } = 15;
// ---- #273 per-subscription tuning (defaults are the values that used to be hardcoded) ----
///
/// Fixed publishing interval for every data-change subscription this driver creates. null
/// (default) keeps the historical behaviour: the interval the runtime asks for per
/// SubscribeAsync call, floored at 50 ms. When set it overrides the per-call value (the
/// 50 ms floor still applies); monitored-item sampling intervals follow it.
///
public TimeSpan? SubscriptionPublishingInterval { get; init; }
///
/// Publish cycles with no data before the server sends a keep-alive (default 10). Must be >= 1.
///
public uint SubscriptionKeepAliveCount { get; init; } = 10;
///
/// Publish cycles with no Publish request before the server drops the subscription (default 1000).
/// Must be at least 3x (OPC UA Part 4 §5.13.2).
///
public uint SubscriptionLifetimeCount { get; init; } = 1000;
/// Max notifications per Publish response; 0 (default) = server decides.
public uint SubscriptionMaxNotificationsPerPublish { get; init; }
/// Relative subscription priority 0-255 (default 0).
public byte SubscriptionPriority { get; init; }
// ---- #277 ----
/// Certificate-revocation / issuer-store settings. Defaults preserve the SDK behaviour.
public OpcUaCertificateValidationOptions CertificateValidation { get; init; } = new();
// ---- #278 ----
///
/// Optional discovery endpoint (a Local Discovery Server or the server itself), e.g.
/// opc.tcp://lds.internal:4840. When set, InitializeAsync calls FindServers and
/// GetEndpoints on it and tries the discovered URLs that match +
/// after the statically configured /
/// (pin the server with ). A discovery failure is non-fatal: the static list is used. Discovery
/// itself always runs over an unsecured channel, as the spec requires. null (default) = off.
///
public string? DiscoveryUrl { get; init; }
///
/// ApplicationUri of the one server discovery may resolve to. An LDS usually registers several servers;
/// with this set only that server's endpoints are used. When unset and discovery finds more than one
/// server, connect fails with an error listing their ApplicationUris. Discovered endpoints are always
/// tried AFTER the configured .
///
public string? DiscoveryServerUri { get; init; }
}
///
/// Server-certificate revocation handling for the OPC UA Client driver (#277). The SDK reads CRLs from
/// the crl sub-folder of the trusted-peer / trusted-issuer stores; these settings only decide how
/// a certificate whose revocation status cannot be determined is treated and where the issuer store lives.
///
public sealed record OpcUaCertificateValidationOptions
{
///
/// When true, a server certificate whose revocation status is unknown (no CRL for its issuer, or a
/// stale one) is rejected. Default false keeps brownfield deployments without CRL infrastructure
/// connecting. A certificate that IS revoked is always rejected — even with
/// set.
///
public bool RejectUnknownRevocationStatus { get; init; }
///
/// Directory used as the trusted-issuer certificate store. Its crl/ sub-folder is where the SDK
/// looks for issuer CRLs (.crl files), so this is the knob for pointing the driver at an
/// operator-managed CRL set. null/empty (default) = %LocalAppData%/OtOpcUa/pki/issuers.
///
public string? IssuerStorePath { get; init; }
}
/// OPC UA message security mode.
public enum OpcUaSecurityMode
{
None,
Sign,
SignAndEncrypt,
}
///
/// OPC UA security policies recognized by the driver. Maps to the standard
/// http://opcfoundation.org/UA/SecurityPolicy# URI suffixes the SDK uses for
/// endpoint matching.
///
///
/// and are deprecated per OPC UA
/// spec v1.04 — they remain in the enum only for brownfield interop with older servers.
/// Prefer , , or
/// for new deployments.
///
public enum OpcUaSecurityPolicy
{
/// No security. Unsigned, unencrypted wire.
None,
/// Deprecated (OPC UA 1.04). Retained for legacy server interop.
Basic128Rsa15,
/// Deprecated (OPC UA 1.04). Retained for legacy server interop.
Basic256,
/// Recommended baseline for current deployments.
Basic256Sha256,
/// Current OPC UA policy; AES-128 + SHA-256 + RSA-OAEP.
Aes128_Sha256_RsaOaep,
/// Current OPC UA policy; AES-256 + SHA-256 + RSA-PSS.
Aes256_Sha256_RsaPss,
}
/// User authentication type sent to the remote server.
public enum OpcUaAuthType
{
Anonymous,
Username,
Certificate,
}