Files
natsdotnet/src/NATS.Server/NatsOptions.cs
Joseph Doherty 88a82ee860 docs: add XML doc comments to server types and fix flaky test timings
Add XML doc comments to public properties across EventTypes, Connz, Varz,
NatsOptions, StreamConfig, IStreamStore, FileStore, MqttListener,
MqttSessionStore, MessageTraceContext, and JetStreamApiResponse. Fix flaky
tests by increasing timing margins (ResponseTracker expiry 1ms→50ms,
sleep 50ms→200ms) and document known flaky test patterns in tests.md.
2026-03-13 18:47:48 -04:00

825 lines
26 KiB
C#

using System.Security.Cryptography;
using System.Security.Authentication;
using System.Text;
using System.Text.Json;
using NATS.Server.Auth;
using NATS.Server.Configuration;
using NATS.Server.Protocol;
using NATS.Server.Tls;
namespace NATS.Server;
/// <summary>
/// Represents the complete runtime configuration for a NATS server instance,
/// including client connectivity, auth, clustering, transport, and observability settings.
/// </summary>
public sealed class NatsOptions
{
private static bool _allowUnknownTopLevelFields;
private string _configDigest = string.Empty;
/// <summary>
/// Gets or sets the listener host for client connections.
/// </summary>
public string Host { get; set; } = NatsProtocol.DefaultHost;
/// <summary>
/// Gets or sets the TCP port for client connections.
/// </summary>
public int Port { get; set; } = NatsProtocol.DefaultPort;
/// <summary>
/// Gets or sets a logical server name advertised to clients and peers.
/// </summary>
public string? ServerName { get; set; }
/// <summary>
/// Gets or sets the maximum accepted payload size in bytes for client publishes.
/// </summary>
public int MaxPayload { get; set; } = 1024 * 1024;
/// <summary>
/// Gets or sets the maximum protocol control line length in bytes.
/// </summary>
public int MaxControlLine { get; set; } = 4096;
/// <summary>
/// Gets or sets the maximum number of concurrent client connections.
/// </summary>
public int MaxConnections { get; set; } = NatsProtocol.DefaultMaxConnections;
/// <summary>
/// Gets or sets the maximum buffered outbound data per client before the connection is considered slow.
/// </summary>
public long MaxPending { get; set; } = 64 * 1024 * 1024; // 64MB, matching Go MAX_PENDING_SIZE
/// <summary>
/// Gets or sets the write deadline used for flushing outbound protocol frames.
/// </summary>
public TimeSpan WriteDeadline { get; set; } = NatsProtocol.DefaultFlushDeadline;
/// <summary>
/// Gets or sets the server heartbeat interval used to detect stale clients.
/// </summary>
public TimeSpan PingInterval { get; set; } = NatsProtocol.DefaultPingInterval;
/// <summary>
/// Gets or sets the maximum number of unanswered pings before disconnect.
/// </summary>
public int MaxPingsOut { get; set; } = NatsProtocol.DefaultPingMaxOut;
/// <summary>
/// Gets or sets a value indicating whether to disable the short-first-ping grace behavior.
/// </summary>
public bool DisableShortFirstPing { get; set; }
/// <summary>
/// Gets or sets the maximum subscriptions allowed per connection. A value of <c>0</c> means unlimited.
/// </summary>
public int MaxSubs { get; set; }
/// <summary>
/// Gets or sets the maximum number of subject tokens allowed in a subscription. A value of <c>0</c> means unlimited.
/// </summary>
public int MaxSubTokens { get; set; }
/// <summary>
/// Gets or sets user-defined server tags exposed via monitoring endpoints.
/// </summary>
public Dictionary<string, string>? Tags { get; set; }
/// <summary>
/// Gets or sets account definitions used to isolate tenants and permissions.
/// </summary>
public Dictionary<string, AccountConfig>? Accounts { get; set; }
/// <summary>
/// Gets or sets the global username for single-user authentication mode.
/// </summary>
public string? Username { get; set; }
/// <summary>
/// Gets or sets the global password for single-user authentication mode.
/// </summary>
public string? Password { get; set; }
/// <summary>
/// Gets or sets the global token for token-based authentication mode.
/// </summary>
public string? Authorization { get; set; }
/// <summary>
/// Gets or sets the configured list of explicit users for multi-user auth.
/// </summary>
public IReadOnlyList<User>? Users { get; set; }
/// <summary>
/// Gets or sets the configured list of NKey users for signature-based auth.
/// </summary>
public IReadOnlyList<NKeyUser>? NKeys { get; set; }
/// <summary>
/// Gets or sets the fallback account user identity when no credentials are provided.
/// </summary>
public string? NoAuthUser { get; set; }
/// <summary>
/// Gets or sets external authorization callout settings.
/// </summary>
public Auth.ExternalAuthOptions? ExternalAuth { get; set; }
/// <summary>
/// Gets or sets proxy-based authentication settings.
/// </summary>
public Auth.ProxyAuthOptions? ProxyAuth { get; set; }
/// <summary>
/// Gets or sets the timeout for completing client authentication.
/// </summary>
public TimeSpan AuthTimeout { get; set; } = NatsProtocol.AuthTimeout;
/// <summary>
/// Gets or sets the HTTP monitoring port. A value of <c>0</c> disables monitoring.
/// </summary>
public int MonitorPort { get; set; }
/// <summary>
/// Gets or sets the bind host for monitoring endpoints.
/// </summary>
public string MonitorHost { get; set; } = "0.0.0.0";
/// <summary>
/// Gets or sets an optional URL base path prefix for monitoring endpoints.
/// </summary>
public string? MonitorBasePath { get; set; }
/// <summary>
/// Gets or sets the HTTPS monitoring port. A value of <c>0</c> disables HTTPS monitoring.
/// </summary>
public int MonitorHttpsPort { get; set; }
/// <summary>
/// Gets or sets the duration of lame duck mode before final shutdown.
/// </summary>
public TimeSpan LameDuckDuration { get; set; } = NatsProtocol.DefaultLameDuckDuration;
/// <summary>
/// Gets or sets the grace period before starting client eviction during lame duck mode.
/// </summary>
public TimeSpan LameDuckGracePeriod { get; set; } = NatsProtocol.DefaultLameDuckGracePeriod;
/// <summary>
/// Gets or sets the optional PID file path written at startup.
/// </summary>
public string? PidFile { get; set; }
/// <summary>
/// Gets or sets the directory where dynamic listener port files are written.
/// </summary>
public string? PortsFileDir { get; set; }
/// <summary>
/// Gets or sets the primary configuration file path.
/// </summary>
public string? ConfigFile { get; set; }
/// <summary>
/// Gets or sets the output file path for server logs.
/// </summary>
public string? LogFile { get; set; }
/// <summary>
/// Gets or sets the maximum log file size before rotation.
/// </summary>
public long LogSizeLimit { get; set; }
/// <summary>
/// Gets or sets the number of rotated log files to retain.
/// </summary>
public int LogMaxFiles { get; set; }
/// <summary>
/// Gets or sets a value indicating whether debug-level logging is enabled.
/// </summary>
public bool Debug { get; set; }
/// <summary>
/// Gets or sets a value indicating whether protocol trace logging is enabled.
/// </summary>
public bool Trace { get; set; }
/// <summary>
/// Gets or sets a value indicating whether timestamps are included in log entries.
/// </summary>
public bool Logtime { get; set; } = true;
/// <summary>
/// Gets or sets a value indicating whether log timestamps use UTC instead of local time.
/// </summary>
public bool LogtimeUTC { get; set; }
/// <summary>
/// Gets or sets a value indicating whether logs are emitted to syslog.
/// </summary>
public bool Syslog { get; set; }
/// <summary>
/// Gets or sets the remote syslog endpoint, when remote syslog forwarding is enabled.
/// </summary>
public string? RemoteSyslog { get; set; }
/// <summary>
/// Gets or sets the profiling HTTP port. A value of <c>0</c> disables profiling endpoints.
/// </summary>
public int ProfPort { get; set; }
/// <summary>
/// Gets or sets the client advertise address provided to peers and clients.
/// </summary>
public string? ClientAdvertise { get; set; }
/// <summary>
/// Gets or sets a value indicating whether verbose protocol tracing is enabled.
/// </summary>
public bool TraceVerbose { get; set; }
/// <summary>
/// Gets or sets the maximum payload length included in trace log messages.
/// </summary>
public int MaxTracedMsgLen { get; set; }
/// <summary>
/// Gets or sets a value indicating whether sublist result caching is disabled.
/// </summary>
public bool DisableSublistCache { get; set; }
/// <summary>
/// Gets or sets how many connection errors are logged before log suppression starts.
/// </summary>
public int ConnectErrorReports { get; set; } = NatsProtocol.DefaultConnectErrorReports;
/// <summary>
/// Gets or sets how many reconnect errors are logged before log suppression starts.
/// </summary>
public int ReconnectErrorReports { get; set; } = NatsProtocol.DefaultReconnectErrorReports;
/// <summary>
/// Gets or sets a value indicating whether protocol headers are disabled.
/// </summary>
public bool NoHeaderSupport { get; set; }
/// <summary>
/// Gets or sets the number of closed-client records retained for monitoring.
/// </summary>
public int MaxClosedClients { get; set; } = NatsProtocol.DefaultMaxClosedClients;
/// <summary>
/// Gets or sets a value indicating whether system account setup is disabled.
/// </summary>
public bool NoSystemAccount { get; set; }
/// <summary>
/// Gets or sets the configured system account name used for server-level subjects.
/// </summary>
public string? SystemAccount { get; set; }
/// <summary>
/// Gets the set of fields explicitly provided on the command line to preserve override precedence.
/// </summary>
public HashSet<string> InCmdLine { get; } = [];
/// <summary>
/// Gets or sets the server TLS certificate file path.
/// </summary>
public string? TlsCert { get; set; }
/// <summary>
/// Gets or sets the server TLS private key file path.
/// </summary>
public string? TlsKey { get; set; }
/// <summary>
/// Gets or sets the trusted CA certificate file used to validate peers.
/// </summary>
public string? TlsCaCert { get; set; }
/// <summary>
/// Gets or sets a value indicating whether client certificates are required and verified.
/// </summary>
public bool TlsVerify { get; set; }
/// <summary>
/// Gets or sets a value indicating whether certificate subject mapping to users is enabled.
/// </summary>
public bool TlsMap { get; set; }
/// <summary>
/// Gets or sets the timeout for TLS handshake and verification.
/// </summary>
public TimeSpan TlsTimeout { get; set; } = NatsProtocol.TlsTimeout;
/// <summary>
/// Gets or sets a value indicating whether clients must start with TLS handshake bytes.
/// </summary>
public bool TlsHandshakeFirst { get; set; }
/// <summary>
/// Gets or sets the fallback delay before trying plaintext parsing when handshake-first is enabled.
/// </summary>
public TimeSpan TlsHandshakeFirstFallback { get; set; } = NatsProtocol.DefaultTlsHandshakeFirstFallbackDelay;
/// <summary>
/// Gets or sets a value indicating whether non-TLS client connections are allowed.
/// </summary>
public bool AllowNonTls { get; set; }
/// <summary>
/// Gets or sets an optional TLS handshake rate limit.
/// </summary>
public long TlsRateLimit { get; set; }
/// <summary>
/// Gets or sets the pinned server certificate fingerprints accepted for peers.
/// </summary>
public HashSet<string>? TlsPinnedCerts { get; set; }
/// <summary>
/// Gets or sets the minimum TLS protocol version allowed for client connections.
/// </summary>
public SslProtocols TlsMinVersion { get; set; } = SslProtocols.Tls12;
/// <summary>
/// Gets or sets OCSP stapling behavior for presented server certificates.
/// </summary>
public OcspConfig? OcspConfig { get; set; }
/// <summary>
/// Gets or sets a value indicating whether OCSP status on peer certificates is enforced.
/// </summary>
public bool OcspPeerVerify { get; set; }
/// <summary>
/// Gets or sets the trusted operator/public keys used to validate account JWTs.
/// </summary>
public string[]? TrustedKeys { get; set; }
/// <summary>
/// Gets or sets the account resolver used to fetch and cache account JWT metadata.
/// </summary>
public Auth.Jwt.IAccountResolver? AccountResolver { get; set; }
/// <summary>
/// Gets or sets per-subsystem log level overrides.
/// </summary>
public Dictionary<string, string>? LogOverrides { get; set; }
/// <summary>
/// Gets or sets configured subject mapping transforms.
/// </summary>
public Dictionary<string, string>? SubjectMappings { get; set; }
/// <summary>
/// Gets or sets MQTT bridge options.
/// </summary>
public MqttOptions? Mqtt { get; set; }
/// <summary>
/// Gets or sets cluster route listener and dial settings.
/// </summary>
public ClusterOptions? Cluster { get; set; }
/// <summary>
/// Gets or sets gateway federation settings.
/// </summary>
public GatewayOptions? Gateway { get; set; }
/// <summary>
/// Gets or sets leaf node listener and remote settings.
/// </summary>
public LeafNodeOptions? LeafNode { get; set; }
/// <summary>
/// Gets or sets JetStream persistence and API options.
/// </summary>
public JetStreamOptions? JetStream { get; set; }
/// <summary>
/// Gets a value indicating whether TLS server certificate and key paths are both configured.
/// </summary>
public bool HasTls => TlsCert != null && TlsKey != null;
/// <summary>
/// Gets or sets websocket listener and authentication options.
/// </summary>
public WebSocketOptions WebSocket { get; set; } = new();
/// <summary>
/// Enables or disables tolerance for unknown top-level configuration fields.
/// </summary>
/// <param name="noError">If <see langword="true"/>, unknown fields are accepted without scan failure.</param>
public static void NoErrOnUnknownFields(bool noError)
{
_allowUnknownTopLevelFields = noError;
}
/// <summary>
/// Gets a value indicating whether unknown top-level config keys are currently accepted.
/// </summary>
internal static bool AllowUnknownTopLevelFields => _allowUnknownTopLevelFields;
/// <summary>
/// Parses a comma-delimited route URL string into absolute route URIs.
/// </summary>
/// <param name="routesStr">The route list from CLI or config.</param>
/// <returns>A list of valid absolute route endpoints.</returns>
public static List<Uri> RoutesFromStr(string routesStr)
{
if (string.IsNullOrWhiteSpace(routesStr))
return [];
var routes = new List<Uri>();
foreach (var route in routesStr.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries))
{
if (Uri.TryCreate(route, UriKind.Absolute, out var uri))
routes.Add(uri);
}
return routes;
}
/// <summary>
/// Creates a deep copy of the options object for reload and runtime mutation workflows.
/// </summary>
/// <returns>A cloned options instance preserving effective values and command-line precedence flags.</returns>
public NatsOptions Clone()
{
try
{
var json = JsonSerializer.Serialize(this);
var clone = JsonSerializer.Deserialize<NatsOptions>(json) ?? new NatsOptions();
clone.InCmdLine.Clear();
foreach (var flag in InCmdLine)
clone.InCmdLine.Add(flag);
if (TlsPinnedCerts != null)
clone.TlsPinnedCerts = [.. TlsPinnedCerts];
return clone;
}
catch
{
var clone = new NatsOptions();
CopyFrom(clone, this);
clone.InCmdLine.Clear();
foreach (var flag in InCmdLine)
clone.InCmdLine.Add(flag);
if (Tags != null)
clone.Tags = new Dictionary<string, string>(Tags);
if (SubjectMappings != null)
clone.SubjectMappings = new Dictionary<string, string>(SubjectMappings);
if (TlsPinnedCerts != null)
clone.TlsPinnedCerts = [.. TlsPinnedCerts];
return clone;
}
}
/// <summary>
/// Applies configuration parsed from raw config text to this options instance.
/// </summary>
/// <param name="data">Configuration file contents in NATS config format.</param>
public void ProcessConfigString(string data)
{
var parsed = ConfigProcessor.ProcessConfig(data);
CopyFrom(this, parsed);
_configDigest = ComputeDigest(data);
}
/// <summary>
/// Returns the SHA-256 digest of the last processed config text.
/// </summary>
/// <returns>A lowercase hex digest for reload/change detection.</returns>
public string ConfigDigest() => _configDigest;
private static void CopyFrom(NatsOptions destination, NatsOptions source)
{
foreach (var prop in typeof(NatsOptions).GetProperties())
{
if (!prop.CanRead || !prop.CanWrite)
continue;
prop.SetValue(destination, prop.GetValue(source));
}
}
private static string ComputeDigest(string text)
{
var hash = SHA256.HashData(Encoding.UTF8.GetBytes(text));
return Convert.ToHexString(hash).ToLowerInvariant();
}
}
/// <summary>
/// Defines operational limits for JetStream API request handling.
/// </summary>
public sealed class JSLimitOpts
{
/// <summary>
/// Gets or sets the maximum number of messages requested in a single batch pull.
/// </summary>
public int MaxRequestBatch { get; set; }
/// <summary>
/// Gets or sets the maximum number of pending acknowledgements per consumer.
/// </summary>
public int MaxAckPending { get; set; }
/// <summary>
/// Gets or sets the maximum number of high-availability JetStream assets.
/// </summary>
public int MaxHAAssets { get; set; }
/// <summary>
/// Gets or sets the duplicate window used for message de-duplication.
/// </summary>
public TimeSpan Duplicates { get; set; }
/// <summary>
/// Gets or sets the per-stream cap for in-flight batched pull requests.
/// </summary>
public int MaxBatchInflightPerStream { get; set; }
/// <summary>
/// Gets or sets the global cap for in-flight batched pull requests.
/// </summary>
public int MaxBatchInflightTotal { get; set; }
/// <summary>
/// Gets or sets the maximum payload size permitted for batch responses.
/// </summary>
public int MaxBatchSize { get; set; }
/// <summary>
/// Gets or sets the maximum wait time for a pull batch request.
/// </summary>
public TimeSpan MaxBatchTimeout { get; set; }
}
/// <summary>
/// Configures operator-issued external auth callout claims and delegation.
/// </summary>
public sealed class AuthCallout
{
/// <summary>
/// Gets or sets the issuer public key for validating callout-issued JWTs.
/// </summary>
public string Issuer { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the account used for auth callout request subjects.
/// </summary>
public string Account { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the users permitted to receive callout requests.
/// </summary>
public List<string> AuthUsers { get; set; } = [];
/// <summary>
/// Gets or sets the XKey used for encrypting auth callout payloads.
/// </summary>
public string XKey { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the accounts that can be authenticated via this callout.
/// </summary>
public List<string> AllowedAccounts { get; set; } = [];
}
/// <summary>
/// Contains trusted proxy connection settings for delegated client identity.
/// </summary>
public sealed class ProxiesConfig
{
/// <summary>
/// Gets or sets the list of trusted proxy identities.
/// </summary>
public List<ProxyConfig> Trusted { get; set; } = [];
}
/// <summary>
/// Describes a trusted proxy identity key.
/// </summary>
public sealed class ProxyConfig
{
/// <summary>
/// Gets or sets the trusted proxy public key.
/// </summary>
public string Key { get; set; } = string.Empty;
}
/// <summary>
/// Captures dynamically assigned listener ports for process integration and discovery.
/// </summary>
public sealed class Ports
{
/// <summary>
/// Gets or sets exposed client listener endpoints.
/// </summary>
public List<string> Nats { get; set; } = [];
/// <summary>
/// Gets or sets exposed monitoring listener endpoints.
/// </summary>
public List<string> Monitoring { get; set; } = [];
/// <summary>
/// Gets or sets exposed route listener endpoints.
/// </summary>
public List<string> Cluster { get; set; } = [];
/// <summary>
/// Gets or sets exposed profiler listener endpoints.
/// </summary>
public List<string> Profile { get; set; } = [];
/// <summary>
/// Gets or sets exposed websocket listener endpoints.
/// </summary>
public List<string> WebSocket { get; set; } = [];
/// <summary>
/// Gets or sets exposed leaf node listener endpoints.
/// </summary>
public List<string> LeafNodes { get; set; } = [];
}
/// <summary>
/// Enumerates supported wire-compression mode names shared with config parsing.
/// </summary>
public static class CompressionModes
{
/// <summary>
/// Disables transport compression.
/// </summary>
public const string Off = "off";
/// <summary>
/// Accepts compression when the peer requests it.
/// </summary>
public const string Accept = "accept";
/// <summary>
/// Uses fast S2 compression.
/// </summary>
public const string S2Fast = "s2_fast";
/// <summary>
/// Uses balanced S2 compression.
/// </summary>
public const string S2Better = "s2_better";
/// <summary>
/// Uses highest-ratio S2 compression.
/// </summary>
public const string S2Best = "s2_best";
/// <summary>
/// Uses S2 framing without payload compression.
/// </summary>
public const string S2Uncompressed = "s2_uncompressed";
/// <summary>
/// Chooses an S2 mode automatically from latency thresholds.
/// </summary>
public const string S2Auto = "s2_auto";
}
/// <summary>
/// Configures connection compression policy.
/// </summary>
public sealed class CompressionOpts
{
/// <summary>
/// Gets or sets the selected compression mode.
/// </summary>
public string Mode { get; set; } = CompressionModes.Off;
/// <summary>
/// Gets or sets RTT thresholds used by automatic compression mode selection.
/// </summary>
public List<int> RTTThresholds { get; set; } = [10, 50, 100, 250];
}
/// <summary>
/// Represents websocket listener and auth configuration for browser and websocket clients.
/// </summary>
public sealed class WebSocketOptions
{
/// <summary>
/// Gets or sets the websocket bind host.
/// </summary>
public string Host { get; set; } = "0.0.0.0";
/// <summary>
/// Gets or sets the websocket bind port.
/// </summary>
public int Port { get; set; } = -1;
/// <summary>
/// Gets or sets the advertised websocket URL authority.
/// </summary>
public string? Advertise { get; set; }
/// <summary>
/// Gets or sets the fallback no-auth user for websocket clients.
/// </summary>
public string? NoAuthUser { get; set; }
/// <summary>
/// Gets or sets the cookie name used to read JWT credentials.
/// </summary>
public string? JwtCookie { get; set; }
/// <summary>
/// Gets or sets the cookie name used to read username credentials.
/// </summary>
public string? UsernameCookie { get; set; }
/// <summary>
/// Gets or sets the cookie name used to read password credentials.
/// </summary>
public string? PasswordCookie { get; set; }
/// <summary>
/// Gets or sets the cookie name used to read token credentials.
/// </summary>
public string? TokenCookie { get; set; }
/// <summary>
/// Gets or sets a static websocket username override.
/// </summary>
public string? Username { get; set; }
/// <summary>
/// Gets or sets a static websocket password override.
/// </summary>
public string? Password { get; set; }
/// <summary>
/// Gets or sets a static websocket token override.
/// </summary>
public string? Token { get; set; }
/// <summary>
/// Gets or sets the websocket authentication timeout.
/// </summary>
public TimeSpan AuthTimeout { get; set; } = TimeSpan.FromSeconds(2);
/// <summary>
/// Gets or sets a value indicating whether websocket TLS is disabled.
/// </summary>
public bool NoTls { get; set; }
/// <summary>
/// Gets or sets the websocket TLS certificate path.
/// </summary>
public string? TlsCert { get; set; }
/// <summary>
/// Gets or sets the websocket TLS key path.
/// </summary>
public string? TlsKey { get; set; }
/// <summary>
/// Gets or sets a value indicating whether same-origin checks are enforced.
/// </summary>
public bool SameOrigin { get; set; }
/// <summary>
/// Gets or sets explicit allowed origins for cross-origin websocket upgrades.
/// </summary>
public List<string>? AllowedOrigins { get; set; }
/// <summary>
/// Gets or sets a value indicating whether per-message websocket compression is enabled.
/// </summary>
public bool Compression { get; set; }
/// <summary>
/// Gets or sets the websocket handshake timeout.
/// </summary>
public TimeSpan HandshakeTimeout { get; set; } = TimeSpan.FromSeconds(2);
/// <summary>
/// Gets or sets an optional server ping interval for websocket connections.
/// </summary>
public TimeSpan? PingInterval { get; set; }
/// <summary>
/// Gets or sets additional HTTP headers included on websocket upgrade responses.
/// </summary>
public Dictionary<string, string>? Headers { get; set; }
/// <summary>
/// Gets a value indicating whether websocket auth settings override top-level auth configuration.
/// </summary>
public bool AuthOverride { get; internal set; }
}