using ZB.MOM.WW.Configuration;
using ZB.MOM.WW.OtOpcUa.OpcUaServer;
namespace ZB.MOM.WW.OtOpcUa.Host.Configuration;
///
/// Fail-fast startup validator for , built on the
/// shared ZB.MOM.WW.Configuration . The C#
/// defaults are all valid, so a host with no explicit "OpcUa" section passes untouched;
/// the validator exists to reject explicit prod/env overrides before the OPC UA SDK boots.
/// Identity/transport essentials (ApplicationName, ApplicationUri,
/// PublicHostname, PkiStoreRoot, OpcUaPort) must be present/valid and at
/// least one security profile must be enabled. Optional fields — ApplicationConfigPath,
/// PeerApplicationUris, AutoAcceptUntrustedClientCertificates, and
/// ProductUri — are intentionally not validated. Failure messages carry the real
/// "OpcUa:" section prefix matching the bound configuration section.
///
public sealed class OpcUaApplicationHostOptionsValidator : OptionsValidatorBase
{
///
protected override void Validate(ValidationBuilder builder, OpcUaApplicationHostOptions o)
{
builder.Required(o.ApplicationName, "OpcUa:ApplicationName");
builder.Required(o.ApplicationUri, "OpcUa:ApplicationUri");
builder.Required(o.PublicHostname, "OpcUa:PublicHostname");
builder.Required(o.PkiStoreRoot, "OpcUa:PkiStoreRoot");
builder.Port(o.OpcUaPort, "OpcUa:OpcUaPort");
// EnabledSecurityProfiles is declared as IList — that interface does not derive from
// IReadOnlyCollection, so it can't bind to MinCount's IReadOnlyCollection parameter
// directly. ToList() bridges to the shared primitive while preserving the count (and message).
builder.MinCount(o.EnabledSecurityProfiles?.ToList(), 1, "OpcUa:EnabledSecurityProfiles");
// #833 review: an enabled credential lockout needs positive durations — a zero LockoutSeconds would lock
// nothing, a zero FailureWindowSeconds would never accumulate. MaxFailedAttempts <= 0 disables it.
var lockout = o.CredentialLockout;
builder.RequireThat(lockout is null || lockout.MaxFailedAttempts <= 0
|| (lockout.LockoutSeconds > 0 && lockout.FailureWindowSeconds > 0 && lockout.SlotWaitSeconds > 0),
"OpcUa:CredentialLockout:LockoutSeconds, OpcUa:CredentialLockout:FailureWindowSeconds and "
+ "OpcUa:CredentialLockout:SlotWaitSeconds must all be "
+ "greater than 0 while OpcUa:CredentialLockout:MaxFailedAttempts is greater than 0 (set "
+ "MaxFailedAttempts to 0 to disable the lockout)");
// #844: the per-client cap (which a success never resets) below the per-credential threshold would make every
// per-credential lockout a client-wide one; 0 means 2 x MaxFailedAttempts.
builder.RequireThat(lockout is null || lockout.MaxFailedAttempts <= 0 || lockout.MaxFailedAttemptsPerClient <= 0
|| lockout.MaxFailedAttemptsPerClient >= lockout.MaxFailedAttempts,
"OpcUa:CredentialLockout:MaxFailedAttemptsPerClient must be 0 (= 2 x MaxFailedAttempts) or at least "
+ "OpcUa:CredentialLockout:MaxFailedAttempts");
}
}