feat: port session 03 — Configuration & Options types, Clone, MergeOptions, SetBaseline
- ServerOptionTypes.cs: all supporting types — ClusterOpts, GatewayOpts, LeafNodeOpts, WebsocketOpts, MqttOpts, RemoteLeafOpts, RemoteGatewayOpts, CompressionOpts, TlsConfigOpts, JsLimitOpts, JsTpmOpts, AuthCalloutOpts, ProxiesConfig, IAuthentication, IAccountResolver, enums (WriteTimeoutPolicy, StoreCipher, OcspMode) - ServerOptions.cs: full Options struct with ~100 properties across 10 subsystems (general, logging, networking, TLS, cluster, gateway, leafnode, websocket, MQTT, JetStream) - ServerOptions.Methods.cs: Clone (deep copy), MergeOptions, SetBaselineOptions, RoutesFromStr, NormalizeBasePath, OverrideTls, OverrideCluster, ExpandPath, HomeDir, MaybeReadPidFile, GetDefaultAuthTimeout, ConfigFlags.NoErrOnUnknownFields - 17 tests covering defaults, random port, merge, clone, expand path, auth timeout, routes parsing, normalize path, cluster override, config flags - Config file parsing (processConfigFileLine 765-line function) deferred to follow-up - All 130 tests pass (129 unit + 1 integration) - DB: features 344/3673 complete, tests 148/3257 complete (9.1% overall)
This commit is contained in:
477
dotnet/src/ZB.MOM.NatsNet.Server/ServerOptionTypes.cs
Normal file
477
dotnet/src/ZB.MOM.NatsNet.Server/ServerOptionTypes.cs
Normal file
@@ -0,0 +1,477 @@
|
|||||||
|
// Copyright 2012-2025 The NATS Authors
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
//
|
||||||
|
// Adapted from server/opts.go in the NATS server Go source.
|
||||||
|
|
||||||
|
using System.Net.Security;
|
||||||
|
using System.Security.Authentication;
|
||||||
|
using System.Security.Cryptography.X509Certificates;
|
||||||
|
|
||||||
|
namespace ZB.MOM.NatsNet.Server;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Write timeout behavior policy.
|
||||||
|
/// Mirrors <c>WriteTimeoutPolicy</c> in opts.go.
|
||||||
|
/// </summary>
|
||||||
|
public enum WriteTimeoutPolicy : byte
|
||||||
|
{
|
||||||
|
Default = 0,
|
||||||
|
Close = 1,
|
||||||
|
Retry = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Store encryption cipher selection.
|
||||||
|
/// Mirrors <c>StoreCipher</c> in opts.go.
|
||||||
|
/// </summary>
|
||||||
|
public enum StoreCipher
|
||||||
|
{
|
||||||
|
ChaCha = 0,
|
||||||
|
Aes = 1,
|
||||||
|
NoCipher = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// OCSP stapling mode.
|
||||||
|
/// Mirrors <c>OCSPMode</c> in opts.go.
|
||||||
|
/// </summary>
|
||||||
|
public enum OcspMode : byte
|
||||||
|
{
|
||||||
|
Auto = 0,
|
||||||
|
Always = 1,
|
||||||
|
Never = 2,
|
||||||
|
Must = 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set of pinned certificate SHA256 hashes (lowercase hex-encoded DER SubjectPublicKeyInfo).
|
||||||
|
/// Mirrors <c>PinnedCertSet</c> in opts.go.
|
||||||
|
/// </summary>
|
||||||
|
public class PinnedCertSet : HashSet<string>
|
||||||
|
{
|
||||||
|
public PinnedCertSet() : base(StringComparer.OrdinalIgnoreCase) { }
|
||||||
|
public PinnedCertSet(IEnumerable<string> collection) : base(collection, StringComparer.OrdinalIgnoreCase) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Compression options for route/leaf connections.
|
||||||
|
/// Mirrors <c>CompressionOpts</c> in opts.go.
|
||||||
|
/// </summary>
|
||||||
|
public class CompressionOpts
|
||||||
|
{
|
||||||
|
public string Mode { get; set; } = string.Empty;
|
||||||
|
public List<TimeSpan> RttThresholds { get; set; } = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Compression mode string constants.
|
||||||
|
/// </summary>
|
||||||
|
public static class CompressionModes
|
||||||
|
{
|
||||||
|
public const string Off = "off";
|
||||||
|
public const string Accept = "accept";
|
||||||
|
public const string S2Fast = "s2_fast";
|
||||||
|
public const string S2Better = "s2_better";
|
||||||
|
public const string S2Best = "s2_best";
|
||||||
|
public const string S2Auto = "s2_auto";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// TLS configuration parsed from config file.
|
||||||
|
/// Mirrors <c>TLSConfigOpts</c> in opts.go.
|
||||||
|
/// </summary>
|
||||||
|
public class TlsConfigOpts
|
||||||
|
{
|
||||||
|
public string CertFile { get; set; } = string.Empty;
|
||||||
|
public string KeyFile { get; set; } = string.Empty;
|
||||||
|
public string CaFile { get; set; } = string.Empty;
|
||||||
|
public bool Verify { get; set; }
|
||||||
|
public bool Insecure { get; set; }
|
||||||
|
public bool Map { get; set; }
|
||||||
|
public bool TlsCheckKnownUrls { get; set; }
|
||||||
|
public bool HandshakeFirst { get; set; }
|
||||||
|
public TimeSpan FallbackDelay { get; set; }
|
||||||
|
public double Timeout { get; set; }
|
||||||
|
public long RateLimit { get; set; }
|
||||||
|
public bool AllowInsecureCiphers { get; set; }
|
||||||
|
public List<SslProtocols> CurvePreferences { get; set; } = [];
|
||||||
|
public PinnedCertSet? PinnedCerts { get; set; }
|
||||||
|
public string CertMatch { get; set; } = string.Empty;
|
||||||
|
public bool CertMatchSkipInvalid { get; set; }
|
||||||
|
public List<string> CaCertsMatch { get; set; } = [];
|
||||||
|
public List<TlsCertPairOpt> Certificates { get; set; } = [];
|
||||||
|
public SslProtocols MinVersion { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Certificate and key file pair.
|
||||||
|
/// Mirrors <c>TLSCertPairOpt</c> in opts.go.
|
||||||
|
/// </summary>
|
||||||
|
public class TlsCertPairOpt
|
||||||
|
{
|
||||||
|
public string CertFile { get; set; } = string.Empty;
|
||||||
|
public string KeyFile { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// OCSP stapling configuration.
|
||||||
|
/// Mirrors <c>OCSPConfig</c> in opts.go.
|
||||||
|
/// </summary>
|
||||||
|
public class OcspConfig
|
||||||
|
{
|
||||||
|
public OcspMode Mode { get; set; }
|
||||||
|
public List<string> OverrideUrls { get; set; } = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// OCSP response cache configuration.
|
||||||
|
/// Mirrors <c>OCSPResponseCacheConfig</c> in opts.go.
|
||||||
|
/// </summary>
|
||||||
|
public class OcspResponseCacheConfig
|
||||||
|
{
|
||||||
|
public string Type { get; set; } = string.Empty;
|
||||||
|
public string LocalStore { get; set; } = string.Empty;
|
||||||
|
public bool PreserveRevoked { get; set; }
|
||||||
|
public double SaveInterval { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Cluster configuration options.
|
||||||
|
/// Mirrors <c>ClusterOpts</c> in opts.go.
|
||||||
|
/// </summary>
|
||||||
|
public class ClusterOpts
|
||||||
|
{
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
public string Host { get; set; } = string.Empty;
|
||||||
|
public int Port { get; set; }
|
||||||
|
public string Username { get; set; } = string.Empty;
|
||||||
|
public string Password { get; set; } = string.Empty;
|
||||||
|
public double AuthTimeout { get; set; }
|
||||||
|
public double TlsTimeout { get; set; }
|
||||||
|
public SslServerAuthenticationOptions? TlsConfig { get; set; }
|
||||||
|
public bool TlsMap { get; set; }
|
||||||
|
public bool TlsCheckKnownUrls { get; set; }
|
||||||
|
public PinnedCertSet? TlsPinnedCerts { get; set; }
|
||||||
|
public bool TlsHandshakeFirst { get; set; }
|
||||||
|
public TimeSpan TlsHandshakeFirstFallback { get; set; }
|
||||||
|
public string ListenStr { get; set; } = string.Empty;
|
||||||
|
public string Advertise { get; set; } = string.Empty;
|
||||||
|
public bool NoAdvertise { get; set; }
|
||||||
|
public int ConnectRetries { get; set; }
|
||||||
|
public bool ConnectBackoff { get; set; }
|
||||||
|
public int PoolSize { get; set; }
|
||||||
|
public List<string> PinnedAccounts { get; set; } = [];
|
||||||
|
public CompressionOpts Compression { get; set; } = new();
|
||||||
|
public TimeSpan PingInterval { get; set; }
|
||||||
|
public int MaxPingsOut { get; set; }
|
||||||
|
public TimeSpan WriteDeadline { get; set; }
|
||||||
|
public WriteTimeoutPolicy WriteTimeout { get; set; }
|
||||||
|
internal TlsConfigOpts? TlsConfigOpts { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gateway configuration options.
|
||||||
|
/// Mirrors <c>GatewayOpts</c> in opts.go.
|
||||||
|
/// </summary>
|
||||||
|
public class GatewayOpts
|
||||||
|
{
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
public string Host { get; set; } = string.Empty;
|
||||||
|
public int Port { get; set; }
|
||||||
|
public string Username { get; set; } = string.Empty;
|
||||||
|
public string Password { get; set; } = string.Empty;
|
||||||
|
public double AuthTimeout { get; set; }
|
||||||
|
public SslServerAuthenticationOptions? TlsConfig { get; set; }
|
||||||
|
public double TlsTimeout { get; set; }
|
||||||
|
public bool TlsMap { get; set; }
|
||||||
|
public bool TlsCheckKnownUrls { get; set; }
|
||||||
|
public PinnedCertSet? TlsPinnedCerts { get; set; }
|
||||||
|
public string Advertise { get; set; } = string.Empty;
|
||||||
|
public int ConnectRetries { get; set; }
|
||||||
|
public bool ConnectBackoff { get; set; }
|
||||||
|
public List<RemoteGatewayOpts> Gateways { get; set; } = [];
|
||||||
|
public bool RejectUnknown { get; set; }
|
||||||
|
public TimeSpan WriteDeadline { get; set; }
|
||||||
|
public WriteTimeoutPolicy WriteTimeout { get; set; }
|
||||||
|
internal TlsConfigOpts? TlsConfigOpts { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Remote gateway connection options.
|
||||||
|
/// Mirrors <c>RemoteGatewayOpts</c> in opts.go.
|
||||||
|
/// </summary>
|
||||||
|
public class RemoteGatewayOpts
|
||||||
|
{
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
public SslServerAuthenticationOptions? TlsConfig { get; set; }
|
||||||
|
public double TlsTimeout { get; set; }
|
||||||
|
public List<Uri> Urls { get; set; } = [];
|
||||||
|
internal TlsConfigOpts? TlsConfigOpts { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Leaf node configuration options.
|
||||||
|
/// Mirrors <c>LeafNodeOpts</c> in opts.go.
|
||||||
|
/// </summary>
|
||||||
|
public class LeafNodeOpts
|
||||||
|
{
|
||||||
|
public string Host { get; set; } = string.Empty;
|
||||||
|
public int Port { get; set; }
|
||||||
|
public string Username { get; set; } = string.Empty;
|
||||||
|
public string Password { get; set; } = string.Empty;
|
||||||
|
public bool ProxyRequired { get; set; }
|
||||||
|
public string Nkey { get; set; } = string.Empty;
|
||||||
|
public string Account { get; set; } = string.Empty;
|
||||||
|
public double AuthTimeout { get; set; }
|
||||||
|
public SslServerAuthenticationOptions? TlsConfig { get; set; }
|
||||||
|
public double TlsTimeout { get; set; }
|
||||||
|
public bool TlsMap { get; set; }
|
||||||
|
public PinnedCertSet? TlsPinnedCerts { get; set; }
|
||||||
|
public bool TlsHandshakeFirst { get; set; }
|
||||||
|
public TimeSpan TlsHandshakeFirstFallback { get; set; }
|
||||||
|
public string Advertise { get; set; } = string.Empty;
|
||||||
|
public bool NoAdvertise { get; set; }
|
||||||
|
public TimeSpan ReconnectInterval { get; set; }
|
||||||
|
public TimeSpan WriteDeadline { get; set; }
|
||||||
|
public WriteTimeoutPolicy WriteTimeout { get; set; }
|
||||||
|
public CompressionOpts Compression { get; set; } = new();
|
||||||
|
public List<RemoteLeafOpts> Remotes { get; set; } = [];
|
||||||
|
public string MinVersion { get; set; } = string.Empty;
|
||||||
|
public bool IsolateLeafnodeInterest { get; set; }
|
||||||
|
internal TlsConfigOpts? TlsConfigOpts { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Signature handler delegate for NKey authentication.
|
||||||
|
/// Mirrors <c>SignatureHandler</c> in opts.go.
|
||||||
|
/// </summary>
|
||||||
|
public delegate (string jwt, byte[] signature, Exception? err) SignatureHandler(byte[] nonce);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Options for connecting to a remote leaf node.
|
||||||
|
/// Mirrors <c>RemoteLeafOpts</c> in opts.go.
|
||||||
|
/// </summary>
|
||||||
|
public class RemoteLeafOpts
|
||||||
|
{
|
||||||
|
public string LocalAccount { get; set; } = string.Empty;
|
||||||
|
public bool NoRandomize { get; set; }
|
||||||
|
public List<Uri> Urls { get; set; } = [];
|
||||||
|
public string Credentials { get; set; } = string.Empty;
|
||||||
|
public string Nkey { get; set; } = string.Empty;
|
||||||
|
public SignatureHandler? SignatureCb { get; set; }
|
||||||
|
public bool Tls { get; set; }
|
||||||
|
public SslServerAuthenticationOptions? TlsConfig { get; set; }
|
||||||
|
public double TlsTimeout { get; set; }
|
||||||
|
public bool TlsHandshakeFirst { get; set; }
|
||||||
|
public bool Hub { get; set; }
|
||||||
|
public List<string> DenyImports { get; set; } = [];
|
||||||
|
public List<string> DenyExports { get; set; } = [];
|
||||||
|
public TimeSpan FirstInfoTimeout { get; set; }
|
||||||
|
public CompressionOpts Compression { get; set; } = new();
|
||||||
|
public RemoteLeafWebsocketOpts Websocket { get; set; } = new();
|
||||||
|
public RemoteLeafProxyOpts Proxy { get; set; } = new();
|
||||||
|
public bool JetStreamClusterMigrate { get; set; }
|
||||||
|
public TimeSpan JetStreamClusterMigrateDelay { get; set; }
|
||||||
|
public bool LocalIsolation { get; set; }
|
||||||
|
public bool RequestIsolation { get; set; }
|
||||||
|
public bool Disabled { get; set; }
|
||||||
|
internal TlsConfigOpts? TlsConfigOpts { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>WebSocket sub-options for a remote leaf connection.</summary>
|
||||||
|
public class RemoteLeafWebsocketOpts
|
||||||
|
{
|
||||||
|
public bool Compression { get; set; }
|
||||||
|
public bool NoMasking { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>HTTP proxy sub-options for a remote leaf connection.</summary>
|
||||||
|
public class RemoteLeafProxyOpts
|
||||||
|
{
|
||||||
|
public string Url { get; set; } = string.Empty;
|
||||||
|
public string Username { get; set; } = string.Empty;
|
||||||
|
public string Password { get; set; } = string.Empty;
|
||||||
|
public TimeSpan Timeout { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// WebSocket configuration options.
|
||||||
|
/// Mirrors <c>WebsocketOpts</c> in opts.go.
|
||||||
|
/// </summary>
|
||||||
|
public class WebsocketOpts
|
||||||
|
{
|
||||||
|
public string Host { get; set; } = string.Empty;
|
||||||
|
public int Port { get; set; }
|
||||||
|
public string Advertise { get; set; } = string.Empty;
|
||||||
|
public string NoAuthUser { get; set; } = string.Empty;
|
||||||
|
public string JwtCookie { get; set; } = string.Empty;
|
||||||
|
public string UsernameCookie { get; set; } = string.Empty;
|
||||||
|
public string PasswordCookie { get; set; } = string.Empty;
|
||||||
|
public string TokenCookie { get; set; } = string.Empty;
|
||||||
|
public string Username { get; set; } = string.Empty;
|
||||||
|
public string Password { get; set; } = string.Empty;
|
||||||
|
public string Token { get; set; } = string.Empty;
|
||||||
|
public double AuthTimeout { get; set; }
|
||||||
|
public bool NoTls { get; set; }
|
||||||
|
public SslServerAuthenticationOptions? TlsConfig { get; set; }
|
||||||
|
public bool TlsMap { get; set; }
|
||||||
|
public PinnedCertSet? TlsPinnedCerts { get; set; }
|
||||||
|
public bool SameOrigin { get; set; }
|
||||||
|
public List<string> AllowedOrigins { get; set; } = [];
|
||||||
|
public bool Compression { get; set; }
|
||||||
|
public TimeSpan HandshakeTimeout { get; set; }
|
||||||
|
public TimeSpan PingInterval { get; set; }
|
||||||
|
public Dictionary<string, string> Headers { get; set; } = new();
|
||||||
|
internal TlsConfigOpts? TlsConfigOpts { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// MQTT configuration options.
|
||||||
|
/// Mirrors <c>MQTTOpts</c> in opts.go.
|
||||||
|
/// </summary>
|
||||||
|
public class MqttOpts
|
||||||
|
{
|
||||||
|
public string Host { get; set; } = string.Empty;
|
||||||
|
public int Port { get; set; }
|
||||||
|
public string NoAuthUser { get; set; } = string.Empty;
|
||||||
|
public string Username { get; set; } = string.Empty;
|
||||||
|
public string Password { get; set; } = string.Empty;
|
||||||
|
public string Token { get; set; } = string.Empty;
|
||||||
|
public string JsDomain { get; set; } = string.Empty;
|
||||||
|
public int StreamReplicas { get; set; }
|
||||||
|
public int ConsumerReplicas { get; set; }
|
||||||
|
public bool ConsumerMemoryStorage { get; set; }
|
||||||
|
public TimeSpan ConsumerInactiveThreshold { get; set; }
|
||||||
|
public double AuthTimeout { get; set; }
|
||||||
|
public SslServerAuthenticationOptions? TlsConfig { get; set; }
|
||||||
|
public bool TlsMap { get; set; }
|
||||||
|
public double TlsTimeout { get; set; }
|
||||||
|
public PinnedCertSet? TlsPinnedCerts { get; set; }
|
||||||
|
public TimeSpan AckWait { get; set; }
|
||||||
|
public TimeSpan JsApiTimeout { get; set; }
|
||||||
|
public ushort MaxAckPending { get; set; }
|
||||||
|
internal TlsConfigOpts? TlsConfigOpts { get; set; }
|
||||||
|
internal bool RejectQoS2Pub { get; set; }
|
||||||
|
internal bool DowngradeQoS2Sub { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// JetStream server-level limits.
|
||||||
|
/// Mirrors <c>JSLimitOpts</c> in opts.go.
|
||||||
|
/// </summary>
|
||||||
|
public class JsLimitOpts
|
||||||
|
{
|
||||||
|
public int MaxRequestBatch { get; set; }
|
||||||
|
public int MaxAckPending { get; set; }
|
||||||
|
public int MaxHaAssets { get; set; }
|
||||||
|
public TimeSpan Duplicates { get; set; }
|
||||||
|
public int MaxBatchInflightPerStream { get; set; }
|
||||||
|
public int MaxBatchInflightTotal { get; set; }
|
||||||
|
public int MaxBatchSize { get; set; }
|
||||||
|
public TimeSpan MaxBatchTimeout { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// TPM configuration for JetStream encryption.
|
||||||
|
/// Mirrors <c>JSTpmOpts</c> in opts.go.
|
||||||
|
/// </summary>
|
||||||
|
public class JsTpmOpts
|
||||||
|
{
|
||||||
|
public string KeysFile { get; set; } = string.Empty;
|
||||||
|
public string KeyPassword { get; set; } = string.Empty;
|
||||||
|
public string SrkPassword { get; set; } = string.Empty;
|
||||||
|
public int Pcr { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Auth callout options for external authentication.
|
||||||
|
/// Mirrors <c>AuthCallout</c> in opts.go.
|
||||||
|
/// </summary>
|
||||||
|
public class AuthCalloutOpts
|
||||||
|
{
|
||||||
|
public string Issuer { get; set; } = string.Empty;
|
||||||
|
public string Account { get; set; } = string.Empty;
|
||||||
|
public List<string> AuthUsers { get; set; } = [];
|
||||||
|
public string XKey { get; set; } = string.Empty;
|
||||||
|
public List<string> AllowedAccounts { get; set; } = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Proxy configuration for trusted proxies.
|
||||||
|
/// Mirrors <c>ProxiesConfig</c> in opts.go.
|
||||||
|
/// </summary>
|
||||||
|
public class ProxiesConfig
|
||||||
|
{
|
||||||
|
public List<ProxyConfig> Trusted { get; set; } = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A single trusted proxy identified by public key.
|
||||||
|
/// Mirrors <c>ProxyConfig</c> in opts.go.
|
||||||
|
/// </summary>
|
||||||
|
public class ProxyConfig
|
||||||
|
{
|
||||||
|
public string Key { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parsed authorization section from config file.
|
||||||
|
/// Mirrors the unexported <c>authorization</c> struct in opts.go.
|
||||||
|
/// </summary>
|
||||||
|
internal class AuthorizationConfig
|
||||||
|
{
|
||||||
|
public string User { get; set; } = string.Empty;
|
||||||
|
public string Pass { get; set; } = string.Empty;
|
||||||
|
public string Token { get; set; } = string.Empty;
|
||||||
|
public string Nkey { get; set; } = string.Empty;
|
||||||
|
public string Acc { get; set; } = string.Empty;
|
||||||
|
public bool ProxyRequired { get; set; }
|
||||||
|
public double Timeout { get; set; }
|
||||||
|
public AuthCalloutOpts? Callout { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Custom authentication interface.
|
||||||
|
/// Mirrors the <c>Authentication</c> interface in auth.go.
|
||||||
|
/// </summary>
|
||||||
|
public interface IAuthentication
|
||||||
|
{
|
||||||
|
bool Check(IClientAuthentication client);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Client-side of authentication check.
|
||||||
|
/// Mirrors <c>ClientAuthentication</c> in auth.go.
|
||||||
|
/// </summary>
|
||||||
|
public interface IClientAuthentication
|
||||||
|
{
|
||||||
|
string? GetOpts();
|
||||||
|
bool IsTls();
|
||||||
|
string? GetTlsConnectionState();
|
||||||
|
string RemoteAddress();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Account resolver interface for dynamic account loading.
|
||||||
|
/// Mirrors <c>AccountResolver</c> in accounts.go.
|
||||||
|
/// </summary>
|
||||||
|
public interface IAccountResolver
|
||||||
|
{
|
||||||
|
(string jwt, Exception? err) Fetch(string name);
|
||||||
|
Exception? Store(string name, string jwt);
|
||||||
|
bool IsReadOnly();
|
||||||
|
Exception? Start(object server);
|
||||||
|
bool IsTrackingUpdate();
|
||||||
|
Exception? Reload();
|
||||||
|
void Close();
|
||||||
|
}
|
||||||
569
dotnet/src/ZB.MOM.NatsNet.Server/ServerOptions.Methods.cs
Normal file
569
dotnet/src/ZB.MOM.NatsNet.Server/ServerOptions.Methods.cs
Normal file
@@ -0,0 +1,569 @@
|
|||||||
|
// Copyright 2012-2025 The NATS Authors
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
//
|
||||||
|
// Adapted from server/opts.go in the NATS server Go source.
|
||||||
|
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
namespace ZB.MOM.NatsNet.Server;
|
||||||
|
|
||||||
|
// Global flag for unknown field handling.
|
||||||
|
internal static class ConfigFlags
|
||||||
|
{
|
||||||
|
private static int _allowUnknownTopLevelField;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets whether unknown top-level config fields should be allowed.
|
||||||
|
/// Mirrors <c>NoErrOnUnknownFields</c> in opts.go.
|
||||||
|
/// </summary>
|
||||||
|
public static void NoErrOnUnknownFields(bool noError) =>
|
||||||
|
Interlocked.Exchange(ref _allowUnknownTopLevelField, noError ? 1 : 0);
|
||||||
|
|
||||||
|
public static bool AllowUnknownTopLevelField =>
|
||||||
|
Interlocked.CompareExchange(ref _allowUnknownTopLevelField, 0, 0) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed partial class ServerOptions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Snapshot of command-line flags, populated during <see cref="ConfigureOptions"/>.
|
||||||
|
/// Mirrors <c>FlagSnapshot</c> in opts.go.
|
||||||
|
/// </summary>
|
||||||
|
public static ServerOptions? FlagSnapshot { get; internal set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Deep-copies this <see cref="ServerOptions"/> instance.
|
||||||
|
/// Mirrors <c>Options.Clone()</c> in opts.go.
|
||||||
|
/// </summary>
|
||||||
|
public ServerOptions Clone()
|
||||||
|
{
|
||||||
|
// Start with a shallow memberwise clone.
|
||||||
|
var clone = (ServerOptions)MemberwiseClone();
|
||||||
|
|
||||||
|
// Deep-copy reference types that need isolation.
|
||||||
|
if (Routes.Count > 0)
|
||||||
|
clone.Routes = Routes.Select(u => new Uri(u.ToString())).ToList();
|
||||||
|
|
||||||
|
clone.Cluster = CloneClusterOpts(Cluster);
|
||||||
|
clone.Gateway = CloneGatewayOpts(Gateway);
|
||||||
|
clone.LeafNode = CloneLeafNodeOpts(LeafNode);
|
||||||
|
clone.Websocket = CloneWebsocketOpts(Websocket);
|
||||||
|
clone.Mqtt = CloneMqttOpts(Mqtt);
|
||||||
|
|
||||||
|
clone.Tags = [.. Tags];
|
||||||
|
clone.Metadata = new Dictionary<string, string>(Metadata);
|
||||||
|
clone.TrustedKeys = [.. TrustedKeys];
|
||||||
|
clone.JsAccDefaultDomain = new Dictionary<string, string>(JsAccDefaultDomain);
|
||||||
|
clone.InConfig = new Dictionary<string, bool>(InConfig);
|
||||||
|
clone.InCmdLine = new Dictionary<string, bool>(InCmdLine);
|
||||||
|
clone.OperatorJwt = [.. OperatorJwt];
|
||||||
|
clone.ResolverPreloads = new Dictionary<string, string>(ResolverPreloads);
|
||||||
|
clone.ResolverPinnedAccounts = [.. ResolverPinnedAccounts];
|
||||||
|
|
||||||
|
return clone;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the SHA-256 digest of the configuration.
|
||||||
|
/// Mirrors <c>Options.ConfigDigest()</c> in opts.go.
|
||||||
|
/// </summary>
|
||||||
|
public string ConfigDigest() => ConfigDigestValue;
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Merge / Baseline
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Merges file-based options with command-line flag options.
|
||||||
|
/// Flag options override file options where set.
|
||||||
|
/// Mirrors <c>MergeOptions</c> in opts.go.
|
||||||
|
/// </summary>
|
||||||
|
public static ServerOptions MergeOptions(ServerOptions? fileOpts, ServerOptions? flagOpts)
|
||||||
|
{
|
||||||
|
if (fileOpts == null) return flagOpts ?? new ServerOptions();
|
||||||
|
if (flagOpts == null) return fileOpts;
|
||||||
|
|
||||||
|
var opts = fileOpts.Clone();
|
||||||
|
|
||||||
|
if (flagOpts.Port != 0) opts.Port = flagOpts.Port;
|
||||||
|
if (!string.IsNullOrEmpty(flagOpts.Host)) opts.Host = flagOpts.Host;
|
||||||
|
if (flagOpts.DontListen) opts.DontListen = true;
|
||||||
|
if (!string.IsNullOrEmpty(flagOpts.ClientAdvertise)) opts.ClientAdvertise = flagOpts.ClientAdvertise;
|
||||||
|
if (!string.IsNullOrEmpty(flagOpts.Username)) opts.Username = flagOpts.Username;
|
||||||
|
if (!string.IsNullOrEmpty(flagOpts.Password)) opts.Password = flagOpts.Password;
|
||||||
|
if (!string.IsNullOrEmpty(flagOpts.Authorization)) opts.Authorization = flagOpts.Authorization;
|
||||||
|
if (flagOpts.HttpPort != 0) opts.HttpPort = flagOpts.HttpPort;
|
||||||
|
if (!string.IsNullOrEmpty(flagOpts.HttpBasePath)) opts.HttpBasePath = flagOpts.HttpBasePath;
|
||||||
|
if (flagOpts.Debug) opts.Debug = true;
|
||||||
|
if (flagOpts.Trace) opts.Trace = true;
|
||||||
|
if (flagOpts.Logtime) opts.Logtime = true;
|
||||||
|
if (!string.IsNullOrEmpty(flagOpts.LogFile)) opts.LogFile = flagOpts.LogFile;
|
||||||
|
if (!string.IsNullOrEmpty(flagOpts.PidFile)) opts.PidFile = flagOpts.PidFile;
|
||||||
|
if (!string.IsNullOrEmpty(flagOpts.PortsFileDir)) opts.PortsFileDir = flagOpts.PortsFileDir;
|
||||||
|
if (flagOpts.ProfPort != 0) opts.ProfPort = flagOpts.ProfPort;
|
||||||
|
if (!string.IsNullOrEmpty(flagOpts.Cluster.ListenStr)) opts.Cluster.ListenStr = flagOpts.Cluster.ListenStr;
|
||||||
|
if (flagOpts.Cluster.NoAdvertise) opts.Cluster.NoAdvertise = true;
|
||||||
|
if (flagOpts.Cluster.ConnectRetries != 0) opts.Cluster.ConnectRetries = flagOpts.Cluster.ConnectRetries;
|
||||||
|
if (!string.IsNullOrEmpty(flagOpts.Cluster.Advertise)) opts.Cluster.Advertise = flagOpts.Cluster.Advertise;
|
||||||
|
if (!string.IsNullOrEmpty(flagOpts.RoutesStr)) MergeRoutes(opts, flagOpts);
|
||||||
|
if (flagOpts.JetStream) opts.JetStream = true;
|
||||||
|
if (!string.IsNullOrEmpty(flagOpts.StoreDir)) opts.StoreDir = flagOpts.StoreDir;
|
||||||
|
|
||||||
|
return opts;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses route URLs from a comma-separated string.
|
||||||
|
/// Mirrors <c>RoutesFromStr</c> in opts.go.
|
||||||
|
/// </summary>
|
||||||
|
public static List<Uri> RoutesFromStr(string routesStr)
|
||||||
|
{
|
||||||
|
var parts = routesStr.Split(',');
|
||||||
|
if (parts.Length == 0) return [];
|
||||||
|
|
||||||
|
var urls = new List<Uri>();
|
||||||
|
foreach (var r in parts)
|
||||||
|
{
|
||||||
|
var trimmed = r.Trim();
|
||||||
|
if (Uri.TryCreate(trimmed, UriKind.Absolute, out var u))
|
||||||
|
urls.Add(u);
|
||||||
|
}
|
||||||
|
return urls;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Applies system-wide defaults to any unset options.
|
||||||
|
/// Mirrors <c>setBaselineOptions</c> in opts.go.
|
||||||
|
/// </summary>
|
||||||
|
public void SetBaselineOptions()
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(Host))
|
||||||
|
Host = ServerConstants.DefaultHost;
|
||||||
|
if (string.IsNullOrEmpty(HttpHost))
|
||||||
|
HttpHost = Host;
|
||||||
|
if (Port == 0)
|
||||||
|
Port = ServerConstants.DefaultPort;
|
||||||
|
else if (Port == ServerConstants.RandomPort)
|
||||||
|
Port = 0;
|
||||||
|
if (MaxConn == 0)
|
||||||
|
MaxConn = ServerConstants.DefaultMaxConnections;
|
||||||
|
if (PingInterval == TimeSpan.Zero)
|
||||||
|
PingInterval = ServerConstants.DefaultPingInterval;
|
||||||
|
if (MaxPingsOut == 0)
|
||||||
|
MaxPingsOut = ServerConstants.DefaultPingMaxOut;
|
||||||
|
if (TlsTimeout == 0)
|
||||||
|
TlsTimeout = ServerConstants.TlsTimeout.TotalSeconds;
|
||||||
|
if (AuthTimeout == 0)
|
||||||
|
AuthTimeout = GetDefaultAuthTimeout(TlsConfig, TlsTimeout);
|
||||||
|
|
||||||
|
// Cluster defaults
|
||||||
|
if (Cluster.Port != 0 || !string.IsNullOrEmpty(Cluster.ListenStr))
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(Cluster.Host))
|
||||||
|
Cluster.Host = ServerConstants.DefaultHost;
|
||||||
|
if (Cluster.TlsTimeout == 0)
|
||||||
|
Cluster.TlsTimeout = ServerConstants.TlsTimeout.TotalSeconds;
|
||||||
|
if (Cluster.AuthTimeout == 0)
|
||||||
|
Cluster.AuthTimeout = GetDefaultAuthTimeout(Cluster.TlsConfig, Cluster.TlsTimeout);
|
||||||
|
if (Cluster.PoolSize == 0)
|
||||||
|
Cluster.PoolSize = ServerConstants.DefaultRoutePoolSize;
|
||||||
|
|
||||||
|
// Add system account to pinned accounts if pool is enabled.
|
||||||
|
if (Cluster.PoolSize > 0)
|
||||||
|
{
|
||||||
|
var sysAccName = SystemAccount;
|
||||||
|
if (string.IsNullOrEmpty(sysAccName) && !NoSystemAccount)
|
||||||
|
sysAccName = ServerConstants.DefaultSystemAccount;
|
||||||
|
if (!string.IsNullOrEmpty(sysAccName) && !Cluster.PinnedAccounts.Contains(sysAccName))
|
||||||
|
Cluster.PinnedAccounts.Add(sysAccName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default compression to "accept".
|
||||||
|
if (string.IsNullOrEmpty(Cluster.Compression.Mode))
|
||||||
|
Cluster.Compression.Mode = CompressionModes.Accept;
|
||||||
|
}
|
||||||
|
|
||||||
|
// LeafNode defaults
|
||||||
|
if (LeafNode.Port != 0)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(LeafNode.Host))
|
||||||
|
LeafNode.Host = ServerConstants.DefaultHost;
|
||||||
|
if (LeafNode.TlsTimeout == 0)
|
||||||
|
LeafNode.TlsTimeout = ServerConstants.TlsTimeout.TotalSeconds;
|
||||||
|
if (LeafNode.AuthTimeout == 0)
|
||||||
|
LeafNode.AuthTimeout = GetDefaultAuthTimeout(LeafNode.TlsConfig, LeafNode.TlsTimeout);
|
||||||
|
if (string.IsNullOrEmpty(LeafNode.Compression.Mode))
|
||||||
|
LeafNode.Compression.Mode = CompressionModes.S2Auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remote leafnode defaults
|
||||||
|
foreach (var r in LeafNode.Remotes)
|
||||||
|
{
|
||||||
|
foreach (var u in r.Urls)
|
||||||
|
{
|
||||||
|
if (u.IsDefaultPort || string.IsNullOrEmpty(u.GetComponents(UriComponents.Port, UriFormat.Unescaped)))
|
||||||
|
{
|
||||||
|
var builder = new UriBuilder(u) { Port = ServerConstants.DefaultLeafNodePort };
|
||||||
|
r.Urls[r.Urls.IndexOf(u)] = builder.Uri;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (string.IsNullOrEmpty(r.Compression.Mode))
|
||||||
|
r.Compression.Mode = CompressionModes.S2Auto;
|
||||||
|
if (r.FirstInfoTimeout <= TimeSpan.Zero)
|
||||||
|
r.FirstInfoTimeout = ServerConstants.DefaultLeafNodeInfoWait;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (LeafNode.ReconnectInterval == TimeSpan.Zero)
|
||||||
|
LeafNode.ReconnectInterval = ServerConstants.DefaultLeafNodeReconnect;
|
||||||
|
|
||||||
|
// Protocol limits
|
||||||
|
if (MaxControlLine == 0)
|
||||||
|
MaxControlLine = ServerConstants.MaxControlLineSize;
|
||||||
|
if (MaxPayload == 0)
|
||||||
|
MaxPayload = ServerConstants.MaxPayloadSize;
|
||||||
|
if (MaxPending == 0)
|
||||||
|
MaxPending = ServerConstants.MaxPendingSize;
|
||||||
|
if (WriteDeadline == TimeSpan.Zero)
|
||||||
|
WriteDeadline = ServerConstants.DefaultFlushDeadline;
|
||||||
|
if (MaxClosedClients == 0)
|
||||||
|
MaxClosedClients = ServerConstants.DefaultMaxClosedClients;
|
||||||
|
if (LameDuckDuration == TimeSpan.Zero)
|
||||||
|
LameDuckDuration = ServerConstants.DefaultLameDuckDuration;
|
||||||
|
if (LameDuckGracePeriod == TimeSpan.Zero)
|
||||||
|
LameDuckGracePeriod = ServerConstants.DefaultLameDuckGracePeriod;
|
||||||
|
|
||||||
|
// Gateway defaults
|
||||||
|
if (Gateway.Port != 0)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(Gateway.Host))
|
||||||
|
Gateway.Host = ServerConstants.DefaultHost;
|
||||||
|
if (Gateway.TlsTimeout == 0)
|
||||||
|
Gateway.TlsTimeout = ServerConstants.TlsTimeout.TotalSeconds;
|
||||||
|
if (Gateway.AuthTimeout == 0)
|
||||||
|
Gateway.AuthTimeout = GetDefaultAuthTimeout(Gateway.TlsConfig, Gateway.TlsTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error reporting
|
||||||
|
if (ConnectErrorReports == 0)
|
||||||
|
ConnectErrorReports = ServerConstants.DefaultConnectErrorReports;
|
||||||
|
if (ReconnectErrorReports == 0)
|
||||||
|
ReconnectErrorReports = ServerConstants.DefaultReconnectErrorReports;
|
||||||
|
|
||||||
|
// WebSocket defaults
|
||||||
|
if (Websocket.Port != 0)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(Websocket.Host))
|
||||||
|
Websocket.Host = ServerConstants.DefaultHost;
|
||||||
|
}
|
||||||
|
|
||||||
|
// MQTT defaults
|
||||||
|
if (Mqtt.Port != 0)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(Mqtt.Host))
|
||||||
|
Mqtt.Host = ServerConstants.DefaultHost;
|
||||||
|
if (Mqtt.TlsTimeout == 0)
|
||||||
|
Mqtt.TlsTimeout = ServerConstants.TlsTimeout.TotalSeconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
// JetStream defaults
|
||||||
|
if (JetStreamMaxMemory == 0 && !MaxMemSet)
|
||||||
|
JetStreamMaxMemory = -1;
|
||||||
|
if (JetStreamMaxStore == 0 && !MaxStoreSet)
|
||||||
|
JetStreamMaxStore = -1;
|
||||||
|
if (SyncInterval == TimeSpan.Zero && !SyncSet)
|
||||||
|
SyncInterval = TimeSpan.FromMinutes(2); // defaultSyncInterval
|
||||||
|
if (JetStreamRequestQueueLimit <= 0)
|
||||||
|
JetStreamRequestQueueLimit = 4096; // JSDefaultRequestQueueLimit
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Normalizes an HTTP base path (ensure leading slash, clean redundant separators).
|
||||||
|
/// Mirrors <c>normalizeBasePath</c> in opts.go.
|
||||||
|
/// </summary>
|
||||||
|
public static string NormalizeBasePath(string p)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(p)) return "/";
|
||||||
|
if (p[0] != '/') p = "/" + p;
|
||||||
|
// Simple path clean: collapse repeated slashes and remove trailing slash.
|
||||||
|
while (p.Contains("//")) p = p.Replace("//", "/");
|
||||||
|
return p.Length > 1 && p.EndsWith('/') ? p[..^1] : p;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Computes the default auth timeout based on TLS config presence.
|
||||||
|
/// Mirrors <c>getDefaultAuthTimeout</c> in opts.go.
|
||||||
|
/// </summary>
|
||||||
|
public static double GetDefaultAuthTimeout(object? tlsConfig, double tlsTimeout)
|
||||||
|
{
|
||||||
|
if (tlsConfig != null)
|
||||||
|
return tlsTimeout + 1.0;
|
||||||
|
return ServerConstants.AuthTimeout.TotalSeconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the user's home directory.
|
||||||
|
/// Mirrors <c>homeDir</c> in opts.go.
|
||||||
|
/// </summary>
|
||||||
|
public static string HomeDir() => Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Expands environment variables and ~/ prefix in a path.
|
||||||
|
/// Mirrors <c>expandPath</c> in opts.go.
|
||||||
|
/// </summary>
|
||||||
|
public static string ExpandPath(string p)
|
||||||
|
{
|
||||||
|
p = Environment.ExpandEnvironmentVariables(p);
|
||||||
|
if (!p.StartsWith('~')) return p;
|
||||||
|
return Path.Combine(HomeDir(), p[1..].TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads a PID from a file path if possible, otherwise returns the string as-is.
|
||||||
|
/// Mirrors <c>maybeReadPidFile</c> in opts.go.
|
||||||
|
/// </summary>
|
||||||
|
public static string MaybeReadPidFile(string pidStr)
|
||||||
|
{
|
||||||
|
try { return File.ReadAllText(pidStr).Trim(); }
|
||||||
|
catch { return pidStr; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Applies TLS overrides from command-line options.
|
||||||
|
/// Mirrors <c>overrideTLS</c> in opts.go.
|
||||||
|
/// </summary>
|
||||||
|
public Exception? OverrideTls()
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(TlsCert))
|
||||||
|
return new InvalidOperationException("TLS Server certificate must be present and valid");
|
||||||
|
if (string.IsNullOrEmpty(TlsKey))
|
||||||
|
return new InvalidOperationException("TLS Server private key must be present and valid");
|
||||||
|
|
||||||
|
// TLS config generation is deferred to GenTlsConfig (session 06+).
|
||||||
|
// For now, mark that TLS is enabled.
|
||||||
|
Tls = true;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Overrides cluster options from the --cluster flag.
|
||||||
|
/// Mirrors <c>overrideCluster</c> in opts.go.
|
||||||
|
/// </summary>
|
||||||
|
public Exception? OverrideCluster()
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(Cluster.ListenStr))
|
||||||
|
{
|
||||||
|
Cluster.Port = 0;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var listenStr = Cluster.ListenStr;
|
||||||
|
var wantsRandom = false;
|
||||||
|
if (listenStr.EndsWith(":-1"))
|
||||||
|
{
|
||||||
|
wantsRandom = true;
|
||||||
|
listenStr = listenStr[..^3] + ":0";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Uri.TryCreate(listenStr, UriKind.Absolute, out var clusterUri))
|
||||||
|
return new InvalidOperationException($"could not parse cluster URL: {Cluster.ListenStr}");
|
||||||
|
|
||||||
|
Cluster.Host = clusterUri.Host;
|
||||||
|
Cluster.Port = wantsRandom ? -1 : clusterUri.Port;
|
||||||
|
|
||||||
|
var userInfo = clusterUri.UserInfo;
|
||||||
|
if (!string.IsNullOrEmpty(userInfo))
|
||||||
|
{
|
||||||
|
var parts = userInfo.Split(':', 2);
|
||||||
|
if (parts.Length != 2)
|
||||||
|
return new InvalidOperationException("expected cluster password to be set");
|
||||||
|
Cluster.Username = parts[0];
|
||||||
|
Cluster.Password = parts[1];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Cluster.Username = string.Empty;
|
||||||
|
Cluster.Password = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Private helpers
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
private static void MergeRoutes(ServerOptions opts, ServerOptions flagOpts)
|
||||||
|
{
|
||||||
|
var routeUrls = RoutesFromStr(flagOpts.RoutesStr);
|
||||||
|
if (routeUrls.Count == 0) return;
|
||||||
|
opts.Routes = routeUrls;
|
||||||
|
opts.RoutesStr = flagOpts.RoutesStr;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void TrackExplicitVal(Dictionary<string, bool> pm, string name, bool val) =>
|
||||||
|
pm[name] = val;
|
||||||
|
|
||||||
|
private static ClusterOpts CloneClusterOpts(ClusterOpts src) => new()
|
||||||
|
{
|
||||||
|
Name = src.Name,
|
||||||
|
Host = src.Host,
|
||||||
|
Port = src.Port,
|
||||||
|
Username = src.Username,
|
||||||
|
Password = src.Password,
|
||||||
|
AuthTimeout = src.AuthTimeout,
|
||||||
|
TlsTimeout = src.TlsTimeout,
|
||||||
|
TlsConfig = src.TlsConfig,
|
||||||
|
TlsMap = src.TlsMap,
|
||||||
|
TlsCheckKnownUrls = src.TlsCheckKnownUrls,
|
||||||
|
TlsPinnedCerts = src.TlsPinnedCerts != null ? new PinnedCertSet(src.TlsPinnedCerts) : null,
|
||||||
|
TlsHandshakeFirst = src.TlsHandshakeFirst,
|
||||||
|
TlsHandshakeFirstFallback = src.TlsHandshakeFirstFallback,
|
||||||
|
ListenStr = src.ListenStr,
|
||||||
|
Advertise = src.Advertise,
|
||||||
|
NoAdvertise = src.NoAdvertise,
|
||||||
|
ConnectRetries = src.ConnectRetries,
|
||||||
|
ConnectBackoff = src.ConnectBackoff,
|
||||||
|
PoolSize = src.PoolSize,
|
||||||
|
PinnedAccounts = [.. src.PinnedAccounts],
|
||||||
|
Compression = new CompressionOpts { Mode = src.Compression.Mode, RttThresholds = [.. src.Compression.RttThresholds] },
|
||||||
|
PingInterval = src.PingInterval,
|
||||||
|
MaxPingsOut = src.MaxPingsOut,
|
||||||
|
WriteDeadline = src.WriteDeadline,
|
||||||
|
WriteTimeout = src.WriteTimeout,
|
||||||
|
};
|
||||||
|
|
||||||
|
private static GatewayOpts CloneGatewayOpts(GatewayOpts src) => new()
|
||||||
|
{
|
||||||
|
Name = src.Name,
|
||||||
|
Host = src.Host,
|
||||||
|
Port = src.Port,
|
||||||
|
Username = src.Username,
|
||||||
|
Password = src.Password,
|
||||||
|
AuthTimeout = src.AuthTimeout,
|
||||||
|
TlsConfig = src.TlsConfig,
|
||||||
|
TlsTimeout = src.TlsTimeout,
|
||||||
|
TlsMap = src.TlsMap,
|
||||||
|
TlsCheckKnownUrls = src.TlsCheckKnownUrls,
|
||||||
|
TlsPinnedCerts = src.TlsPinnedCerts != null ? new PinnedCertSet(src.TlsPinnedCerts) : null,
|
||||||
|
Advertise = src.Advertise,
|
||||||
|
ConnectRetries = src.ConnectRetries,
|
||||||
|
ConnectBackoff = src.ConnectBackoff,
|
||||||
|
Gateways = src.Gateways.Select(g => new RemoteGatewayOpts
|
||||||
|
{
|
||||||
|
Name = g.Name,
|
||||||
|
TlsConfig = g.TlsConfig,
|
||||||
|
TlsTimeout = g.TlsTimeout,
|
||||||
|
Urls = g.Urls.Select(u => new Uri(u.ToString())).ToList(),
|
||||||
|
}).ToList(),
|
||||||
|
RejectUnknown = src.RejectUnknown,
|
||||||
|
WriteDeadline = src.WriteDeadline,
|
||||||
|
WriteTimeout = src.WriteTimeout,
|
||||||
|
};
|
||||||
|
|
||||||
|
private static LeafNodeOpts CloneLeafNodeOpts(LeafNodeOpts src) => new()
|
||||||
|
{
|
||||||
|
Host = src.Host,
|
||||||
|
Port = src.Port,
|
||||||
|
Username = src.Username,
|
||||||
|
Password = src.Password,
|
||||||
|
ProxyRequired = src.ProxyRequired,
|
||||||
|
Nkey = src.Nkey,
|
||||||
|
Account = src.Account,
|
||||||
|
AuthTimeout = src.AuthTimeout,
|
||||||
|
TlsConfig = src.TlsConfig,
|
||||||
|
TlsTimeout = src.TlsTimeout,
|
||||||
|
TlsMap = src.TlsMap,
|
||||||
|
TlsPinnedCerts = src.TlsPinnedCerts != null ? new PinnedCertSet(src.TlsPinnedCerts) : null,
|
||||||
|
TlsHandshakeFirst = src.TlsHandshakeFirst,
|
||||||
|
TlsHandshakeFirstFallback = src.TlsHandshakeFirstFallback,
|
||||||
|
Advertise = src.Advertise,
|
||||||
|
NoAdvertise = src.NoAdvertise,
|
||||||
|
ReconnectInterval = src.ReconnectInterval,
|
||||||
|
WriteDeadline = src.WriteDeadline,
|
||||||
|
WriteTimeout = src.WriteTimeout,
|
||||||
|
Compression = new CompressionOpts { Mode = src.Compression.Mode, RttThresholds = [.. src.Compression.RttThresholds] },
|
||||||
|
Remotes = src.Remotes.Select(r => new RemoteLeafOpts
|
||||||
|
{
|
||||||
|
LocalAccount = r.LocalAccount,
|
||||||
|
NoRandomize = r.NoRandomize,
|
||||||
|
Urls = r.Urls.Select(u => new Uri(u.ToString())).ToList(),
|
||||||
|
Credentials = r.Credentials,
|
||||||
|
Nkey = r.Nkey,
|
||||||
|
Tls = r.Tls,
|
||||||
|
TlsTimeout = r.TlsTimeout,
|
||||||
|
TlsHandshakeFirst = r.TlsHandshakeFirst,
|
||||||
|
Hub = r.Hub,
|
||||||
|
DenyImports = [.. r.DenyImports],
|
||||||
|
DenyExports = [.. r.DenyExports],
|
||||||
|
FirstInfoTimeout = r.FirstInfoTimeout,
|
||||||
|
Compression = new CompressionOpts { Mode = r.Compression.Mode, RttThresholds = [.. r.Compression.RttThresholds] },
|
||||||
|
JetStreamClusterMigrate = r.JetStreamClusterMigrate,
|
||||||
|
JetStreamClusterMigrateDelay = r.JetStreamClusterMigrateDelay,
|
||||||
|
LocalIsolation = r.LocalIsolation,
|
||||||
|
RequestIsolation = r.RequestIsolation,
|
||||||
|
Disabled = r.Disabled,
|
||||||
|
}).ToList(),
|
||||||
|
MinVersion = src.MinVersion,
|
||||||
|
IsolateLeafnodeInterest = src.IsolateLeafnodeInterest,
|
||||||
|
};
|
||||||
|
|
||||||
|
private static WebsocketOpts CloneWebsocketOpts(WebsocketOpts src) => new()
|
||||||
|
{
|
||||||
|
Host = src.Host,
|
||||||
|
Port = src.Port,
|
||||||
|
Advertise = src.Advertise,
|
||||||
|
NoAuthUser = src.NoAuthUser,
|
||||||
|
JwtCookie = src.JwtCookie,
|
||||||
|
UsernameCookie = src.UsernameCookie,
|
||||||
|
PasswordCookie = src.PasswordCookie,
|
||||||
|
TokenCookie = src.TokenCookie,
|
||||||
|
Username = src.Username,
|
||||||
|
Password = src.Password,
|
||||||
|
Token = src.Token,
|
||||||
|
AuthTimeout = src.AuthTimeout,
|
||||||
|
NoTls = src.NoTls,
|
||||||
|
TlsConfig = src.TlsConfig,
|
||||||
|
TlsMap = src.TlsMap,
|
||||||
|
TlsPinnedCerts = src.TlsPinnedCerts != null ? new PinnedCertSet(src.TlsPinnedCerts) : null,
|
||||||
|
SameOrigin = src.SameOrigin,
|
||||||
|
AllowedOrigins = [.. src.AllowedOrigins],
|
||||||
|
Compression = src.Compression,
|
||||||
|
HandshakeTimeout = src.HandshakeTimeout,
|
||||||
|
PingInterval = src.PingInterval,
|
||||||
|
Headers = new Dictionary<string, string>(src.Headers),
|
||||||
|
};
|
||||||
|
|
||||||
|
private static MqttOpts CloneMqttOpts(MqttOpts src) => new()
|
||||||
|
{
|
||||||
|
Host = src.Host,
|
||||||
|
Port = src.Port,
|
||||||
|
NoAuthUser = src.NoAuthUser,
|
||||||
|
Username = src.Username,
|
||||||
|
Password = src.Password,
|
||||||
|
Token = src.Token,
|
||||||
|
JsDomain = src.JsDomain,
|
||||||
|
StreamReplicas = src.StreamReplicas,
|
||||||
|
ConsumerReplicas = src.ConsumerReplicas,
|
||||||
|
ConsumerMemoryStorage = src.ConsumerMemoryStorage,
|
||||||
|
ConsumerInactiveThreshold = src.ConsumerInactiveThreshold,
|
||||||
|
AuthTimeout = src.AuthTimeout,
|
||||||
|
TlsConfig = src.TlsConfig,
|
||||||
|
TlsMap = src.TlsMap,
|
||||||
|
TlsTimeout = src.TlsTimeout,
|
||||||
|
TlsPinnedCerts = src.TlsPinnedCerts != null ? new PinnedCertSet(src.TlsPinnedCerts) : null,
|
||||||
|
AckWait = src.AckWait,
|
||||||
|
JsApiTimeout = src.JsApiTimeout,
|
||||||
|
MaxAckPending = src.MaxAckPending,
|
||||||
|
};
|
||||||
|
}
|
||||||
234
dotnet/src/ZB.MOM.NatsNet.Server/ServerOptions.cs
Normal file
234
dotnet/src/ZB.MOM.NatsNet.Server/ServerOptions.cs
Normal file
@@ -0,0 +1,234 @@
|
|||||||
|
// Copyright 2012-2025 The NATS Authors
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
//
|
||||||
|
// Adapted from server/opts.go in the NATS server Go source.
|
||||||
|
|
||||||
|
using System.Net.Security;
|
||||||
|
using System.Security.Authentication;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
namespace ZB.MOM.NatsNet.Server;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Server configuration options block.
|
||||||
|
/// Mirrors <c>Options</c> struct in opts.go.
|
||||||
|
/// </summary>
|
||||||
|
public sealed partial class ServerOptions
|
||||||
|
{
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// General / Startup
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
public string ConfigFile { get; set; } = string.Empty;
|
||||||
|
public string ServerName { get; set; } = string.Empty;
|
||||||
|
public string Host { get; set; } = string.Empty;
|
||||||
|
public int Port { get; set; }
|
||||||
|
public bool DontListen { get; set; }
|
||||||
|
public string ClientAdvertise { get; set; } = string.Empty;
|
||||||
|
public bool CheckConfig { get; set; }
|
||||||
|
public string PidFile { get; set; } = string.Empty;
|
||||||
|
public string PortsFileDir { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Logging & Debugging
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
public bool Trace { get; set; }
|
||||||
|
public bool Debug { get; set; }
|
||||||
|
public bool TraceVerbose { get; set; }
|
||||||
|
public bool TraceHeaders { get; set; }
|
||||||
|
public bool NoLog { get; set; }
|
||||||
|
public bool NoSigs { get; set; }
|
||||||
|
public bool Logtime { get; set; }
|
||||||
|
public bool LogtimeUtc { get; set; }
|
||||||
|
public string LogFile { get; set; } = string.Empty;
|
||||||
|
public long LogSizeLimit { get; set; }
|
||||||
|
public long LogMaxFiles { get; set; }
|
||||||
|
public bool Syslog { get; set; }
|
||||||
|
public string RemoteSyslog { get; set; } = string.Empty;
|
||||||
|
public int ProfPort { get; set; }
|
||||||
|
public int ProfBlockRate { get; set; }
|
||||||
|
public int MaxTracedMsgLen { get; set; }
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Networking & Limits
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
public int MaxConn { get; set; }
|
||||||
|
public int MaxSubs { get; set; }
|
||||||
|
public byte MaxSubTokens { get; set; }
|
||||||
|
public int MaxControlLine { get; set; }
|
||||||
|
public int MaxPayload { get; set; }
|
||||||
|
public long MaxPending { get; set; }
|
||||||
|
public bool NoFastProducerStall { get; set; }
|
||||||
|
public bool ProxyRequired { get; set; }
|
||||||
|
public bool ProxyProtocol { get; set; }
|
||||||
|
public int MaxClosedClients { get; set; }
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Connectivity
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
public TimeSpan PingInterval { get; set; }
|
||||||
|
public int MaxPingsOut { get; set; }
|
||||||
|
public TimeSpan WriteDeadline { get; set; }
|
||||||
|
public WriteTimeoutPolicy WriteTimeout { get; set; }
|
||||||
|
public TimeSpan LameDuckDuration { get; set; }
|
||||||
|
public TimeSpan LameDuckGracePeriod { get; set; }
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// HTTP / Monitoring
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
public string HttpHost { get; set; } = string.Empty;
|
||||||
|
public int HttpPort { get; set; }
|
||||||
|
public string HttpBasePath { get; set; } = string.Empty;
|
||||||
|
public int HttpsPort { get; set; }
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Authentication & Authorization
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
public string Username { get; set; } = string.Empty;
|
||||||
|
public string Password { get; set; } = string.Empty;
|
||||||
|
public string Authorization { get; set; } = string.Empty;
|
||||||
|
public double AuthTimeout { get; set; }
|
||||||
|
public string NoAuthUser { get; set; } = string.Empty;
|
||||||
|
public string DefaultSentinel { get; set; } = string.Empty;
|
||||||
|
public string SystemAccount { get; set; } = string.Empty;
|
||||||
|
public bool NoSystemAccount { get; set; }
|
||||||
|
public AuthCalloutOpts? AuthCallout { get; set; }
|
||||||
|
public bool AlwaysEnableNonce { get; set; }
|
||||||
|
public IAuthentication? CustomClientAuthentication { get; set; }
|
||||||
|
public IAuthentication? CustomRouterAuthentication { get; set; }
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Sublist
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
public bool NoSublistCache { get; set; }
|
||||||
|
public bool NoHeaderSupport { get; set; }
|
||||||
|
public bool DisableShortFirstPing { get; set; }
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// TLS (Client)
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
public double TlsTimeout { get; set; }
|
||||||
|
public bool Tls { get; set; }
|
||||||
|
public bool TlsVerify { get; set; }
|
||||||
|
public bool TlsMap { get; set; }
|
||||||
|
public string TlsCert { get; set; } = string.Empty;
|
||||||
|
public string TlsKey { get; set; } = string.Empty;
|
||||||
|
public string TlsCaCert { get; set; } = string.Empty;
|
||||||
|
public SslServerAuthenticationOptions? TlsConfig { get; set; }
|
||||||
|
public PinnedCertSet? TlsPinnedCerts { get; set; }
|
||||||
|
public long TlsRateLimit { get; set; }
|
||||||
|
public bool TlsHandshakeFirst { get; set; }
|
||||||
|
public TimeSpan TlsHandshakeFirstFallback { get; set; }
|
||||||
|
public bool AllowNonTls { get; set; }
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Cluster / Gateway / Leaf / WebSocket / MQTT
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
public ClusterOpts Cluster { get; set; } = new();
|
||||||
|
public GatewayOpts Gateway { get; set; } = new();
|
||||||
|
public LeafNodeOpts LeafNode { get; set; } = new();
|
||||||
|
public WebsocketOpts Websocket { get; set; } = new();
|
||||||
|
public MqttOpts Mqtt { get; set; } = new();
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Routing
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
public List<Uri> Routes { get; set; } = [];
|
||||||
|
public string RoutesStr { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// JetStream
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
public bool JetStream { get; set; }
|
||||||
|
public bool NoJetStreamStrict { get; set; }
|
||||||
|
public long JetStreamMaxMemory { get; set; }
|
||||||
|
public long JetStreamMaxStore { get; set; }
|
||||||
|
public string JetStreamDomain { get; set; } = string.Empty;
|
||||||
|
public string JetStreamExtHint { get; set; } = string.Empty;
|
||||||
|
public string JetStreamKey { get; set; } = string.Empty;
|
||||||
|
public string JetStreamOldKey { get; set; } = string.Empty;
|
||||||
|
public StoreCipher JetStreamCipher { get; set; }
|
||||||
|
public string JetStreamUniqueTag { get; set; } = string.Empty;
|
||||||
|
public JsLimitOpts JetStreamLimits { get; set; } = new();
|
||||||
|
public JsTpmOpts JetStreamTpm { get; set; } = new();
|
||||||
|
public long JetStreamMaxCatchup { get; set; }
|
||||||
|
public long JetStreamRequestQueueLimit { get; set; }
|
||||||
|
public ulong JetStreamMetaCompact { get; set; }
|
||||||
|
public ulong JetStreamMetaCompactSize { get; set; }
|
||||||
|
public bool JetStreamMetaCompactSync { get; set; }
|
||||||
|
public int StreamMaxBufferedMsgs { get; set; }
|
||||||
|
public long StreamMaxBufferedSize { get; set; }
|
||||||
|
public string StoreDir { get; set; } = string.Empty;
|
||||||
|
public TimeSpan SyncInterval { get; set; }
|
||||||
|
public bool SyncAlways { get; set; }
|
||||||
|
public Dictionary<string, string> JsAccDefaultDomain { get; set; } = new();
|
||||||
|
public bool DisableJetStreamBanner { get; set; }
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Security & Trust
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
public List<string> TrustedKeys { get; set; } = [];
|
||||||
|
public SslServerAuthenticationOptions? AccountResolverTlsConfig { get; set; }
|
||||||
|
public IAccountResolver? AccountResolver { get; set; }
|
||||||
|
public OcspConfig? OcspConfig { get; set; }
|
||||||
|
public OcspResponseCacheConfig? OcspCacheConfig { get; set; }
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Tagging & Metadata
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
public List<string> Tags { get; set; } = [];
|
||||||
|
public Dictionary<string, string> Metadata { get; set; } = new();
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Proxies
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
public ProxiesConfig? Proxies { get; set; }
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Connectivity error reporting
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
public int ConnectErrorReports { get; set; }
|
||||||
|
public int ReconnectErrorReports { get; set; }
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Internal / Private fields
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
internal Dictionary<string, bool> InConfig { get; set; } = new();
|
||||||
|
internal Dictionary<string, bool> InCmdLine { get; set; } = new();
|
||||||
|
internal List<string> OperatorJwt { get; set; } = [];
|
||||||
|
internal Dictionary<string, string> ResolverPreloads { get; set; } = new();
|
||||||
|
internal HashSet<string> ResolverPinnedAccounts { get; set; } = [];
|
||||||
|
internal TimeSpan GatewaysSolicitDelay { get; set; }
|
||||||
|
internal int OverrideProto { get; set; }
|
||||||
|
internal bool MaxMemSet { get; set; }
|
||||||
|
internal bool MaxStoreSet { get; set; }
|
||||||
|
internal bool SyncSet { get; set; }
|
||||||
|
internal bool AuthBlockDefined { get; set; }
|
||||||
|
internal string ConfigDigestValue { get; set; } = string.Empty;
|
||||||
|
internal TlsConfigOpts? TlsConfigOpts { get; set; }
|
||||||
|
}
|
||||||
333
dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ServerOptionsTests.cs
Normal file
333
dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ServerOptionsTests.cs
Normal file
@@ -0,0 +1,333 @@
|
|||||||
|
// Copyright 2012-2025 The NATS Authors
|
||||||
|
// Licensed under the Apache License, Version 2.0
|
||||||
|
|
||||||
|
using Shouldly;
|
||||||
|
using ZB.MOM.NatsNet.Server;
|
||||||
|
|
||||||
|
namespace ZB.MOM.NatsNet.Server.Tests;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests for ServerOptions — mirrors tests from server/opts_test.go.
|
||||||
|
/// </summary>
|
||||||
|
public class ServerOptionsTests
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Mirrors TestDefaultOptions — verifies baseline defaults are applied.
|
||||||
|
/// </summary>
|
||||||
|
[Fact] // T:2512
|
||||||
|
public void DefaultOptions_ShouldSetBaselineDefaults()
|
||||||
|
{
|
||||||
|
var opts = new ServerOptions();
|
||||||
|
opts.SetBaselineOptions();
|
||||||
|
|
||||||
|
opts.Host.ShouldBe(ServerConstants.DefaultHost);
|
||||||
|
opts.Port.ShouldBe(ServerConstants.DefaultPort);
|
||||||
|
opts.MaxConn.ShouldBe(ServerConstants.DefaultMaxConnections);
|
||||||
|
opts.HttpHost.ShouldBe(ServerConstants.DefaultHost);
|
||||||
|
opts.PingInterval.ShouldBe(ServerConstants.DefaultPingInterval);
|
||||||
|
opts.MaxPingsOut.ShouldBe(ServerConstants.DefaultPingMaxOut);
|
||||||
|
opts.TlsTimeout.ShouldBe(ServerConstants.TlsTimeout.TotalSeconds);
|
||||||
|
opts.AuthTimeout.ShouldBe(ServerConstants.AuthTimeout.TotalSeconds);
|
||||||
|
opts.MaxControlLine.ShouldBe(ServerConstants.MaxControlLineSize);
|
||||||
|
opts.MaxPayload.ShouldBe(ServerConstants.MaxPayloadSize);
|
||||||
|
opts.MaxPending.ShouldBe(ServerConstants.MaxPendingSize);
|
||||||
|
opts.WriteDeadline.ShouldBe(ServerConstants.DefaultFlushDeadline);
|
||||||
|
opts.MaxClosedClients.ShouldBe(ServerConstants.DefaultMaxClosedClients);
|
||||||
|
opts.LameDuckDuration.ShouldBe(ServerConstants.DefaultLameDuckDuration);
|
||||||
|
opts.LameDuckGracePeriod.ShouldBe(ServerConstants.DefaultLameDuckGracePeriod);
|
||||||
|
opts.LeafNode.ReconnectInterval.ShouldBe(ServerConstants.DefaultLeafNodeReconnect);
|
||||||
|
opts.ConnectErrorReports.ShouldBe(ServerConstants.DefaultConnectErrorReports);
|
||||||
|
opts.ReconnectErrorReports.ShouldBe(ServerConstants.DefaultReconnectErrorReports);
|
||||||
|
opts.JetStreamMaxMemory.ShouldBe(-1);
|
||||||
|
opts.JetStreamMaxStore.ShouldBe(-1);
|
||||||
|
opts.SyncInterval.ShouldBe(TimeSpan.FromMinutes(2));
|
||||||
|
opts.JetStreamRequestQueueLimit.ShouldBe(4096);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mirrors TestOptions_RandomPort — RANDOM_PORT should become 0 after baseline.
|
||||||
|
/// </summary>
|
||||||
|
[Fact] // T:2513
|
||||||
|
public void RandomPort_ShouldResolveToZero()
|
||||||
|
{
|
||||||
|
var opts = new ServerOptions { Port = ServerConstants.RandomPort };
|
||||||
|
opts.SetBaselineOptions();
|
||||||
|
opts.Port.ShouldBe(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mirrors TestMergeOverrides — flag options override file options.
|
||||||
|
/// </summary>
|
||||||
|
[Fact] // T:2516
|
||||||
|
public void MergeOverrides_ShouldLetFlagsWin()
|
||||||
|
{
|
||||||
|
var fileOpts = new ServerOptions
|
||||||
|
{
|
||||||
|
Host = "0.0.0.0",
|
||||||
|
Port = 4222,
|
||||||
|
Username = "fileuser",
|
||||||
|
Password = "filepass",
|
||||||
|
};
|
||||||
|
|
||||||
|
var flagOpts = new ServerOptions
|
||||||
|
{
|
||||||
|
Port = 9999,
|
||||||
|
Username = "flaguser",
|
||||||
|
};
|
||||||
|
|
||||||
|
var merged = ServerOptions.MergeOptions(fileOpts, flagOpts);
|
||||||
|
|
||||||
|
merged.Port.ShouldBe(9999);
|
||||||
|
merged.Username.ShouldBe("flaguser");
|
||||||
|
// Host not overridden (empty in flags)
|
||||||
|
merged.Host.ShouldBe("0.0.0.0");
|
||||||
|
// Password not overridden (empty in flags)
|
||||||
|
merged.Password.ShouldBe("filepass");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mirrors TestOptionsClone — deep copy should produce independent objects.
|
||||||
|
/// </summary>
|
||||||
|
[Fact] // T:2537
|
||||||
|
public void Clone_ShouldDeepCopyAllFields()
|
||||||
|
{
|
||||||
|
var opts = new ServerOptions
|
||||||
|
{
|
||||||
|
ConfigFile = "./configs/test.conf",
|
||||||
|
Host = "127.0.0.1",
|
||||||
|
Port = 2222,
|
||||||
|
Username = "derek",
|
||||||
|
Password = "porkchop",
|
||||||
|
AuthTimeout = 1.0,
|
||||||
|
Debug = true,
|
||||||
|
Trace = true,
|
||||||
|
Logtime = false,
|
||||||
|
HttpPort = ServerConstants.DefaultHttpPort,
|
||||||
|
HttpBasePath = ServerConstants.DefaultHttpBasePath,
|
||||||
|
PidFile = "/tmp/nats-server/nats-server.pid",
|
||||||
|
ProfPort = 6789,
|
||||||
|
Syslog = true,
|
||||||
|
RemoteSyslog = "udp://foo.com:33",
|
||||||
|
MaxControlLine = 2048,
|
||||||
|
MaxPayload = 65536,
|
||||||
|
MaxConn = 100,
|
||||||
|
PingInterval = TimeSpan.FromSeconds(60),
|
||||||
|
MaxPingsOut = 3,
|
||||||
|
Cluster = new ClusterOpts
|
||||||
|
{
|
||||||
|
NoAdvertise = true,
|
||||||
|
ConnectRetries = 2,
|
||||||
|
WriteDeadline = TimeSpan.FromSeconds(3),
|
||||||
|
},
|
||||||
|
Gateway = new GatewayOpts
|
||||||
|
{
|
||||||
|
Name = "A",
|
||||||
|
Gateways =
|
||||||
|
[
|
||||||
|
new RemoteGatewayOpts { Name = "B", Urls = [new Uri("nats://host:5222")] },
|
||||||
|
new RemoteGatewayOpts { Name = "C" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
WriteDeadline = TimeSpan.FromSeconds(3),
|
||||||
|
Routes = [new Uri("nats://localhost:4222")],
|
||||||
|
};
|
||||||
|
|
||||||
|
var clone = opts.Clone();
|
||||||
|
|
||||||
|
// Values should match.
|
||||||
|
clone.ConfigFile.ShouldBe(opts.ConfigFile);
|
||||||
|
clone.Host.ShouldBe(opts.Host);
|
||||||
|
clone.Port.ShouldBe(opts.Port);
|
||||||
|
clone.Username.ShouldBe(opts.Username);
|
||||||
|
clone.Gateway.Name.ShouldBe("A");
|
||||||
|
clone.Gateway.Gateways.Count.ShouldBe(2);
|
||||||
|
clone.Gateway.Gateways[0].Urls[0].Authority.ShouldBe("host:5222");
|
||||||
|
|
||||||
|
// Mutating clone should not affect original.
|
||||||
|
clone.Username = "changed";
|
||||||
|
opts.Username.ShouldBe("derek");
|
||||||
|
|
||||||
|
// Mutating original gateway URLs should not affect clone.
|
||||||
|
opts.Gateway.Gateways[0].Urls[0] = new Uri("nats://other:9999");
|
||||||
|
clone.Gateway.Gateways[0].Urls[0].Authority.ShouldBe("host:5222");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mirrors TestOptionsCloneNilLists — clone of empty lists should produce empty lists.
|
||||||
|
/// </summary>
|
||||||
|
[Fact] // T:2538
|
||||||
|
public void CloneNilLists_ShouldProduceEmptyLists()
|
||||||
|
{
|
||||||
|
var opts = new ServerOptions();
|
||||||
|
var clone = opts.Clone();
|
||||||
|
clone.Routes.ShouldNotBeNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mirrors TestExpandPath — tilde and env var expansion.
|
||||||
|
/// </summary>
|
||||||
|
[Fact] // T:2563
|
||||||
|
public void ExpandPath_ShouldExpandTildeAndEnvVars()
|
||||||
|
{
|
||||||
|
// Absolute path should not change.
|
||||||
|
var result = ServerOptions.ExpandPath("/foo/bar");
|
||||||
|
result.ShouldBe("/foo/bar");
|
||||||
|
|
||||||
|
// Tilde expansion.
|
||||||
|
var home = ServerOptions.HomeDir();
|
||||||
|
var expanded = ServerOptions.ExpandPath("~/test");
|
||||||
|
expanded.ShouldBe(Path.Combine(home, "test"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mirrors TestDefaultAuthTimeout — verifies auth timeout logic.
|
||||||
|
/// </summary>
|
||||||
|
[Fact] // T:2572
|
||||||
|
public void DefaultAuthTimeout_ShouldBe2sWithoutTls()
|
||||||
|
{
|
||||||
|
var timeout = ServerOptions.GetDefaultAuthTimeout(null, 0);
|
||||||
|
timeout.ShouldBe(ServerConstants.AuthTimeout.TotalSeconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void DefaultAuthTimeout_ShouldBeTlsTimeoutPlusOneWithTls()
|
||||||
|
{
|
||||||
|
// When TLS is configured, auth timeout = tls_timeout + 1
|
||||||
|
var timeout = ServerOptions.GetDefaultAuthTimeout(new object(), 4.0);
|
||||||
|
timeout.ShouldBe(5.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests RoutesFromStr parsing.
|
||||||
|
/// </summary>
|
||||||
|
[Fact] // T:2576 (partial)
|
||||||
|
public void RoutesFromStr_ShouldParseCommaSeparatedUrls()
|
||||||
|
{
|
||||||
|
var routes = ServerOptions.RoutesFromStr("nats://localhost:4222,nats://localhost:4223");
|
||||||
|
routes.Count.ShouldBe(2);
|
||||||
|
routes[0].Authority.ShouldBe("localhost:4222");
|
||||||
|
routes[1].Authority.ShouldBe("localhost:4223");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests NormalizeBasePath.
|
||||||
|
/// </summary>
|
||||||
|
[Fact] // T:2581
|
||||||
|
public void NormalizeBasePath_ShouldCleanPaths()
|
||||||
|
{
|
||||||
|
ServerOptions.NormalizeBasePath("").ShouldBe("/");
|
||||||
|
ServerOptions.NormalizeBasePath("/").ShouldBe("/");
|
||||||
|
ServerOptions.NormalizeBasePath("foo").ShouldBe("/foo");
|
||||||
|
ServerOptions.NormalizeBasePath("/foo/").ShouldBe("/foo");
|
||||||
|
ServerOptions.NormalizeBasePath("//foo//bar//").ShouldBe("/foo/bar");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests ConfigFlags.NoErrOnUnknownFields.
|
||||||
|
/// </summary>
|
||||||
|
[Fact] // T:2502
|
||||||
|
public void NoErrOnUnknownFields_ShouldToggleFlag()
|
||||||
|
{
|
||||||
|
ConfigFlags.NoErrOnUnknownFields(true);
|
||||||
|
ConfigFlags.AllowUnknownTopLevelField.ShouldBeTrue();
|
||||||
|
|
||||||
|
ConfigFlags.NoErrOnUnknownFields(false);
|
||||||
|
ConfigFlags.AllowUnknownTopLevelField.ShouldBeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests SetBaseline with cluster port set.
|
||||||
|
/// </summary>
|
||||||
|
[Fact]
|
||||||
|
public void SetBaseline_WithClusterPort_ShouldApplyClusterDefaults()
|
||||||
|
{
|
||||||
|
var opts = new ServerOptions
|
||||||
|
{
|
||||||
|
Cluster = new ClusterOpts { Port = 6222 },
|
||||||
|
};
|
||||||
|
opts.SetBaselineOptions();
|
||||||
|
|
||||||
|
opts.Cluster.Host.ShouldBe(ServerConstants.DefaultHost);
|
||||||
|
opts.Cluster.TlsTimeout.ShouldBe(ServerConstants.TlsTimeout.TotalSeconds);
|
||||||
|
opts.Cluster.PoolSize.ShouldBe(ServerConstants.DefaultRoutePoolSize);
|
||||||
|
opts.Cluster.Compression.Mode.ShouldBe(CompressionModes.Accept);
|
||||||
|
// System account should be auto-pinned.
|
||||||
|
opts.Cluster.PinnedAccounts.ShouldContain(ServerConstants.DefaultSystemAccount);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests SetBaseline with leaf node port set.
|
||||||
|
/// </summary>
|
||||||
|
[Fact]
|
||||||
|
public void SetBaseline_WithLeafNodePort_ShouldApplyLeafDefaults()
|
||||||
|
{
|
||||||
|
var opts = new ServerOptions
|
||||||
|
{
|
||||||
|
LeafNode = new LeafNodeOpts { Port = 7422 },
|
||||||
|
};
|
||||||
|
opts.SetBaselineOptions();
|
||||||
|
|
||||||
|
opts.LeafNode.Host.ShouldBe(ServerConstants.DefaultHost);
|
||||||
|
opts.LeafNode.Compression.Mode.ShouldBe(CompressionModes.S2Auto);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests OverrideCluster with valid URL.
|
||||||
|
/// </summary>
|
||||||
|
[Fact] // T:2518
|
||||||
|
public void OverrideCluster_ShouldParseUrlAndSetHostPort()
|
||||||
|
{
|
||||||
|
var opts = new ServerOptions
|
||||||
|
{
|
||||||
|
Cluster = new ClusterOpts { ListenStr = "nats://user:pass@127.0.0.1:6222" },
|
||||||
|
};
|
||||||
|
var err = opts.OverrideCluster();
|
||||||
|
err.ShouldBeNull();
|
||||||
|
opts.Cluster.Host.ShouldBe("127.0.0.1");
|
||||||
|
opts.Cluster.Port.ShouldBe(6222);
|
||||||
|
opts.Cluster.Username.ShouldBe("user");
|
||||||
|
opts.Cluster.Password.ShouldBe("pass");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests OverrideCluster with empty string disables clustering.
|
||||||
|
/// </summary>
|
||||||
|
[Fact]
|
||||||
|
public void OverrideCluster_EmptyString_ShouldDisableClustering()
|
||||||
|
{
|
||||||
|
var opts = new ServerOptions
|
||||||
|
{
|
||||||
|
Cluster = new ClusterOpts { Port = 6222, ListenStr = "" },
|
||||||
|
};
|
||||||
|
var err = opts.OverrideCluster();
|
||||||
|
err.ShouldBeNull();
|
||||||
|
opts.Cluster.Port.ShouldBe(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests MaybeReadPidFile returns original string when file doesn't exist.
|
||||||
|
/// </summary>
|
||||||
|
[Fact] // T:2585
|
||||||
|
public void MaybeReadPidFile_NonExistent_ShouldReturnOriginalString()
|
||||||
|
{
|
||||||
|
ServerOptions.MaybeReadPidFile("12345").ShouldBe("12345");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests that MergeOptions handles null inputs.
|
||||||
|
/// </summary>
|
||||||
|
[Fact]
|
||||||
|
public void MergeOptions_NullInputs_ShouldReturnNonNull()
|
||||||
|
{
|
||||||
|
var result = ServerOptions.MergeOptions(null, null);
|
||||||
|
result.ShouldNotBeNull();
|
||||||
|
|
||||||
|
var fileOpts = new ServerOptions { Port = 1234 };
|
||||||
|
var r1 = ServerOptions.MergeOptions(fileOpts, null);
|
||||||
|
r1.Port.ShouldBe(1234);
|
||||||
|
|
||||||
|
var flagOpts = new ServerOptions { Port = 5678 };
|
||||||
|
var r2 = ServerOptions.MergeOptions(null, flagOpts);
|
||||||
|
r2.Port.ShouldBe(5678);
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
porting.db
BIN
porting.db
Binary file not shown.
@@ -1,6 +1,6 @@
|
|||||||
# NATS .NET Porting Status Report
|
# NATS .NET Porting Status Report
|
||||||
|
|
||||||
Generated: 2026-02-26 14:39:36 UTC
|
Generated: 2026-02-26 16:51:01 UTC
|
||||||
|
|
||||||
## Modules (12 total)
|
## Modules (12 total)
|
||||||
|
|
||||||
@@ -13,17 +13,19 @@ Generated: 2026-02-26 14:39:36 UTC
|
|||||||
|
|
||||||
| Status | Count |
|
| Status | Count |
|
||||||
|--------|-------|
|
|--------|-------|
|
||||||
| complete | 328 |
|
| complete | 344 |
|
||||||
| n_a | 79 |
|
| n_a | 82 |
|
||||||
| not_started | 3266 |
|
| not_started | 3180 |
|
||||||
|
| stub | 67 |
|
||||||
|
|
||||||
## Unit Tests (3257 total)
|
## Unit Tests (3257 total)
|
||||||
|
|
||||||
| Status | Count |
|
| Status | Count |
|
||||||
|--------|-------|
|
|--------|-------|
|
||||||
| complete | 139 |
|
| complete | 148 |
|
||||||
| n_a | 49 |
|
| n_a | 49 |
|
||||||
| not_started | 3069 |
|
| not_started | 2980 |
|
||||||
|
| stub | 80 |
|
||||||
|
|
||||||
## Library Mappings (36 total)
|
## Library Mappings (36 total)
|
||||||
|
|
||||||
@@ -34,4 +36,4 @@ Generated: 2026-02-26 14:39:36 UTC
|
|||||||
|
|
||||||
## Overall Progress
|
## Overall Progress
|
||||||
|
|
||||||
**606/6942 items complete (8.7%)**
|
**634/6942 items complete (9.1%)**
|
||||||
|
|||||||
39
reports/report_11c0b92.md
Normal file
39
reports/report_11c0b92.md
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
# NATS .NET Porting Status Report
|
||||||
|
|
||||||
|
Generated: 2026-02-26 16:51:01 UTC
|
||||||
|
|
||||||
|
## Modules (12 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| complete | 11 |
|
||||||
|
| not_started | 1 |
|
||||||
|
|
||||||
|
## Features (3673 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| complete | 344 |
|
||||||
|
| n_a | 82 |
|
||||||
|
| not_started | 3180 |
|
||||||
|
| stub | 67 |
|
||||||
|
|
||||||
|
## Unit Tests (3257 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| complete | 148 |
|
||||||
|
| n_a | 49 |
|
||||||
|
| not_started | 2980 |
|
||||||
|
| stub | 80 |
|
||||||
|
|
||||||
|
## Library Mappings (36 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| mapped | 36 |
|
||||||
|
|
||||||
|
|
||||||
|
## Overall Progress
|
||||||
|
|
||||||
|
**634/6942 items complete (9.1%)**
|
||||||
Reference in New Issue
Block a user