diff --git a/dotnet/src/ZB.MOM.NatsNet.Server/Accounts/Account.cs b/dotnet/src/ZB.MOM.NatsNet.Server/Accounts/Account.cs index 4a9c55a..ab7eb64 100644 --- a/dotnet/src/ZB.MOM.NatsNet.Server/Accounts/Account.cs +++ b/dotnet/src/ZB.MOM.NatsNet.Server/Accounts/Account.cs @@ -19,38 +19,6 @@ using ZB.MOM.NatsNet.Server.Internal.DataStructures; namespace ZB.MOM.NatsNet.Server; -// ============================================================================ -// AccountNumConns — remote server connection/leafnode count message -// Mirrors Go `AccountNumConns` struct used in updateRemoteServer. -// ============================================================================ - -/// -/// Carries the number of client connections and leaf nodes that a remote server -/// has for a given account, along with the remote server's identity. -/// Mirrors Go AccountNumConns in server/accounts.go. -/// -internal sealed class AccountNumConns -{ - /// Remote server identity. Mirrors Go Server ServerInfo. - public ServerIdentity Server { get; set; } = new(); - - /// Number of client connections on the remote server. Mirrors Go Conns int. - public int Conns { get; set; } - - /// Number of leaf nodes on the remote server. Mirrors Go LeafNodes int. - public int LeafNodes { get; set; } -} - -/// -/// Minimal remote server identity stub used by . -/// Full implementation lives with the server cluster sessions. -/// -internal sealed class ServerIdentity -{ - /// Unique server ID. Mirrors Go ID string. - public string ID { get; set; } = string.Empty; -} - // ============================================================================ // Account — full implementation // Mirrors Go `Account` struct in server/accounts.go lines 52-119. @@ -666,8 +634,8 @@ public sealed class Account : INatsAccount { _strack ??= new Dictionary(); - _strack.TryGetValue(m.Server.ID, out var prev); - _strack[m.Server.ID] = new SConns + _strack.TryGetValue(m.Server.Id, out var prev); + _strack[m.Server.Id] = new SConns { Conns = m.Conns, Leafs = m.LeafNodes, diff --git a/dotnet/src/ZB.MOM.NatsNet.Server/Config/ReloadOptions.cs b/dotnet/src/ZB.MOM.NatsNet.Server/Config/ReloadOptions.cs new file mode 100644 index 0000000..32f16b1 --- /dev/null +++ b/dotnet/src/ZB.MOM.NatsNet.Server/Config/ReloadOptions.cs @@ -0,0 +1,996 @@ +// Copyright 2017-2026 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/reload.go in the NATS server Go source. + +namespace ZB.MOM.NatsNet.Server; + +// ============================================================================= +// IReloadOption — mirrors Go `option` interface in reload.go +// ============================================================================= + +/// +/// Represents a hot-swappable configuration setting that can be applied to a +/// running server. Mirrors Go option interface in server/reload.go. +/// +public interface IReloadOption +{ + /// Apply this option to the running server. + void Apply(NatsServer server); + + /// Returns true if this option requires reloading the logger. + bool IsLoggingChange(); + + /// + /// Returns true if this option requires reloading the cached trace level. + /// Clients store trace level separately. + /// + bool IsTraceLevelChange(); + + /// Returns true if this option requires reloading authorization. + bool IsAuthChange(); + + /// Returns true if this option requires reloading TLS. + bool IsTlsChange(); + + /// Returns true if this option requires reloading cluster permissions. + bool IsClusterPermsChange(); + + /// + /// Returns true if this option requires special handling for changes in + /// cluster pool size or accounts list. + /// + bool IsClusterPoolSizeOrAccountsChange(); + + /// + /// Returns true if this option indicates a change in the server's JetStream config. + /// Account changes are handled separately in reloadAuthorization. + /// + bool IsJetStreamChange(); + + /// Returns true if this change requires publishing the server's statz. + bool IsStatszChange(); +} + +// ============================================================================= +// NoopReloadOption — mirrors Go `noopOption` struct in reload.go +// ============================================================================= + +/// +/// Base class providing no-op implementations for all +/// methods. Concrete option types override only the methods relevant to them. +/// Mirrors Go noopOption struct in server/reload.go. +/// +public abstract class NoopReloadOption : IReloadOption +{ + /// + public virtual void Apply(NatsServer server) { } + + /// + public virtual bool IsLoggingChange() => false; + + /// + public virtual bool IsTraceLevelChange() => false; + + /// + public virtual bool IsAuthChange() => false; + + /// + public virtual bool IsTlsChange() => false; + + /// + public virtual bool IsClusterPermsChange() => false; + + /// + public virtual bool IsClusterPoolSizeOrAccountsChange() => false; + + /// + public virtual bool IsJetStreamChange() => false; + + /// + public virtual bool IsStatszChange() => false; +} + +// ============================================================================= +// Intermediate base classes (mirrors Go loggingOption / traceLevelOption) +// ============================================================================= + +/// +/// Base for all logging-related reload options. +/// Mirrors Go loggingOption struct. +/// +internal abstract class LoggingReloadOption : NoopReloadOption +{ + public override bool IsLoggingChange() => true; +} + +/// +/// Base for all trace-level reload options. +/// Mirrors Go traceLevelOption struct. +/// +internal abstract class TraceLevelReloadOption : LoggingReloadOption +{ + public override bool IsTraceLevelChange() => true; +} + +/// +/// Base for all authorization-related reload options. +/// Mirrors Go authOption struct. +/// +internal abstract class AuthReloadOption : NoopReloadOption +{ + public override bool IsAuthChange() => true; +} + +/// +/// Base for TLS reload options. +/// Mirrors Go tlsOption (as a base, not the concrete type). +/// +internal abstract class TlsBaseReloadOption : NoopReloadOption +{ + public override bool IsTlsChange() => true; +} + +// ============================================================================= +// Logging & Trace option types +// ============================================================================= + +/// +/// Reload option for the trace setting. +/// Mirrors Go traceOption struct in reload.go. +/// +internal sealed class TraceReloadOption : TraceLevelReloadOption +{ + private readonly bool _newValue; + public TraceReloadOption(bool newValue) => _newValue = newValue; + + public override void Apply(NatsServer server) + => server.Noticef("Reloaded: trace = {0}", _newValue); +} + +/// +/// Reload option for the trace_verbose setting. +/// Mirrors Go traceVerboseOption struct in reload.go. +/// +internal sealed class TraceVerboseReloadOption : TraceLevelReloadOption +{ + private readonly bool _newValue; + public TraceVerboseReloadOption(bool newValue) => _newValue = newValue; + + public override void Apply(NatsServer server) + => server.Noticef("Reloaded: trace_verbose = {0}", _newValue); +} + +/// +/// Reload option for the trace_headers setting. +/// Mirrors Go traceHeadersOption struct in reload.go. +/// +internal sealed class TraceHeadersReloadOption : TraceLevelReloadOption +{ + private readonly bool _newValue; + public TraceHeadersReloadOption(bool newValue) => _newValue = newValue; + + public override void Apply(NatsServer server) + => server.Noticef("Reloaded: trace_headers = {0}", _newValue); +} + +/// +/// Reload option for the debug setting. +/// Mirrors Go debugOption struct in reload.go. +/// +internal sealed class DebugReloadOption : LoggingReloadOption +{ + private readonly bool _newValue; + public DebugReloadOption(bool newValue) => _newValue = newValue; + + public override void Apply(NatsServer server) + { + server.Noticef("Reloaded: debug = {0}", _newValue); + // TODO: session 13 — call server.ReloadDebugRaftNodes(_newValue) + } +} + +/// +/// Reload option for the logtime setting. +/// Mirrors Go logtimeOption struct in reload.go. +/// +internal sealed class LogtimeReloadOption : LoggingReloadOption +{ + private readonly bool _newValue; + public LogtimeReloadOption(bool newValue) => _newValue = newValue; + + public override void Apply(NatsServer server) + => server.Noticef("Reloaded: logtime = {0}", _newValue); +} + +/// +/// Reload option for the logtime_utc setting. +/// Mirrors Go logtimeUTCOption struct in reload.go. +/// +internal sealed class LogtimeUtcReloadOption : LoggingReloadOption +{ + private readonly bool _newValue; + public LogtimeUtcReloadOption(bool newValue) => _newValue = newValue; + + public override void Apply(NatsServer server) + => server.Noticef("Reloaded: logtime_utc = {0}", _newValue); +} + +/// +/// Reload option for the log_file setting. +/// Mirrors Go logfileOption struct in reload.go. +/// +internal sealed class LogFileReloadOption : LoggingReloadOption +{ + private readonly string _newValue; + public LogFileReloadOption(string newValue) => _newValue = newValue; + + public override void Apply(NatsServer server) + => server.Noticef("Reloaded: log_file = {0}", _newValue); +} + +/// +/// Reload option for the syslog setting. +/// Mirrors Go syslogOption struct in reload.go. +/// +internal sealed class SyslogReloadOption : LoggingReloadOption +{ + private readonly bool _newValue; + public SyslogReloadOption(bool newValue) => _newValue = newValue; + + public override void Apply(NatsServer server) + => server.Noticef("Reloaded: syslog = {0}", _newValue); +} + +/// +/// Reload option for the remote_syslog setting. +/// Mirrors Go remoteSyslogOption struct in reload.go. +/// +internal sealed class RemoteSyslogReloadOption : LoggingReloadOption +{ + private readonly string _newValue; + public RemoteSyslogReloadOption(string newValue) => _newValue = newValue; + + public override void Apply(NatsServer server) + => server.Noticef("Reloaded: remote_syslog = {0}", _newValue); +} + +// ============================================================================= +// TLS option types +// ============================================================================= + +/// +/// Reload option for the tls setting. +/// Mirrors Go tlsOption struct in reload.go. +/// The TLS config is stored as object? because the full +/// TlsConfig type is not yet ported. +/// TODO: session 13 — replace object? with the ported TlsConfig type. +/// +internal sealed class TlsReloadOption : NoopReloadOption +{ + // TODO: session 13 — replace object? with ported TlsConfig type + private readonly object? _newValue; + public TlsReloadOption(object? newValue) => _newValue = newValue; + + public override bool IsTlsChange() => true; + + public override void Apply(NatsServer server) + { + var message = _newValue is null ? "disabled" : "enabled"; + server.Noticef("Reloaded: tls = {0}", message); + // TODO: session 13 — update server.Info.TLSRequired / TLSVerify + } +} + +/// +/// Reload option for the TLS timeout setting. +/// Mirrors Go tlsTimeoutOption struct in reload.go. +/// +internal sealed class TlsTimeoutReloadOption : NoopReloadOption +{ + private readonly double _newValue; + public TlsTimeoutReloadOption(double newValue) => _newValue = newValue; + + public override void Apply(NatsServer server) + => server.Noticef("Reloaded: tls timeout = {0}", _newValue); +} + +/// +/// Reload option for the TLS pinned_certs setting. +/// Mirrors Go tlsPinnedCertOption struct in reload.go. +/// The pinned cert set is stored as object? pending the port +/// of the PinnedCertSet type. +/// TODO: session 13 — replace object? with ported PinnedCertSet type. +/// +internal sealed class TlsPinnedCertReloadOption : NoopReloadOption +{ + // TODO: session 13 — replace object? with ported PinnedCertSet type + private readonly object? _newValue; + public TlsPinnedCertReloadOption(object? newValue) => _newValue = newValue; + + public override void Apply(NatsServer server) + => server.Noticef("Reloaded: pinned_certs"); +} + +/// +/// Reload option for the TLS handshake_first setting. +/// Mirrors Go tlsHandshakeFirst struct in reload.go. +/// +internal sealed class TlsHandshakeFirstReloadOption : NoopReloadOption +{ + private readonly bool _newValue; + public TlsHandshakeFirstReloadOption(bool newValue) => _newValue = newValue; + + public override void Apply(NatsServer server) + => server.Noticef("Reloaded: Client TLS handshake first: {0}", _newValue); +} + +/// +/// Reload option for the TLS handshake_first_fallback delay setting. +/// Mirrors Go tlsHandshakeFirstFallback struct in reload.go. +/// +internal sealed class TlsHandshakeFirstFallbackReloadOption : NoopReloadOption +{ + private readonly TimeSpan _newValue; + public TlsHandshakeFirstFallbackReloadOption(TimeSpan newValue) => _newValue = newValue; + + public override void Apply(NatsServer server) + => server.Noticef("Reloaded: Client TLS handshake first fallback delay: {0}", _newValue); +} + +// ============================================================================= +// Authorization option types +// ============================================================================= + +/// +/// Reload option for the username authorization setting. +/// Mirrors Go usernameOption struct in reload.go. +/// +internal sealed class UsernameReloadOption : AuthReloadOption +{ + public override void Apply(NatsServer server) + => server.Noticef("Reloaded: authorization username"); +} + +/// +/// Reload option for the password authorization setting. +/// Mirrors Go passwordOption struct in reload.go. +/// +internal sealed class PasswordReloadOption : AuthReloadOption +{ + public override void Apply(NatsServer server) + => server.Noticef("Reloaded: authorization password"); +} + +/// +/// Reload option for the token authorization setting. +/// Mirrors Go authorizationOption struct in reload.go. +/// +internal sealed class AuthorizationReloadOption : AuthReloadOption +{ + public override void Apply(NatsServer server) + => server.Noticef("Reloaded: authorization token"); +} + +/// +/// Reload option for the authorization timeout setting. +/// Note: this is a NoopReloadOption (not auth) because authorization +/// will be reloaded with options separately. +/// Mirrors Go authTimeoutOption struct in reload.go. +/// +internal sealed class AuthTimeoutReloadOption : NoopReloadOption +{ + private readonly double _newValue; + public AuthTimeoutReloadOption(double newValue) => _newValue = newValue; + + public override void Apply(NatsServer server) + => server.Noticef("Reloaded: authorization timeout = {0}", _newValue); +} + +/// +/// Reload option for the tags setting. +/// Mirrors Go tagsOption struct in reload.go. +/// +internal sealed class TagsReloadOption : NoopReloadOption +{ + public override void Apply(NatsServer server) + => server.Noticef("Reloaded: tags"); + + public override bool IsStatszChange() => true; +} + +/// +/// Reload option for the metadata setting. +/// Mirrors Go metadataOption struct in reload.go. +/// +internal sealed class MetadataReloadOption : NoopReloadOption +{ + public override void Apply(NatsServer server) + => server.Noticef("Reloaded: metadata"); + + public override bool IsStatszChange() => true; +} + +/// +/// Reload option for the authorization users setting. +/// Mirrors Go usersOption struct in reload.go. +/// +internal sealed class UsersReloadOption : AuthReloadOption +{ + public override void Apply(NatsServer server) + => server.Noticef("Reloaded: authorization users"); +} + +/// +/// Reload option for the authorization nkeys setting. +/// Mirrors Go nkeysOption struct in reload.go. +/// +internal sealed class NkeysReloadOption : AuthReloadOption +{ + public override void Apply(NatsServer server) + => server.Noticef("Reloaded: authorization nkey users"); +} + +/// +/// Reload option for the accounts setting. +/// Mirrors Go accountsOption struct in reload.go. +/// +internal sealed class AccountsReloadOption : AuthReloadOption +{ + public override void Apply(NatsServer server) + => server.Noticef("Reloaded: accounts"); +} + +// ============================================================================= +// Cluster option types +// ============================================================================= + +/// +/// Reload option for the cluster setting. +/// Stores cluster options as object? pending the port of ClusterOpts. +/// Mirrors Go clusterOption struct in reload.go. +/// TODO: session 13 — replace object? with ported ClusterOpts type. +/// +internal sealed class ClusterReloadOption : AuthReloadOption +{ + // TODO: session 13 — replace object? with ported ClusterOpts type + private readonly object? _newValue; + private readonly bool _permsChanged; + private readonly bool _poolSizeChanged; + private readonly bool _compressChanged; + private readonly string[] _accsAdded; + private readonly string[] _accsRemoved; + + public ClusterReloadOption( + object? newValue, + bool permsChanged, + bool poolSizeChanged, + bool compressChanged, + string[] accsAdded, + string[] accsRemoved) + { + _newValue = newValue; + _permsChanged = permsChanged; + _poolSizeChanged = poolSizeChanged; + _compressChanged = compressChanged; + _accsAdded = accsAdded; + _accsRemoved = accsRemoved; + } + + public override bool IsClusterPermsChange() + => _permsChanged; + + public override bool IsClusterPoolSizeOrAccountsChange() + => _poolSizeChanged || _accsAdded.Length > 0 || _accsRemoved.Length > 0; + + public override void Apply(NatsServer server) + { + // TODO: session 13 — full cluster apply logic (TLS, route info, compression) + server.Noticef("Reloaded: cluster"); + } +} + +/// +/// Reload option for the cluster routes setting. +/// Routes to add/remove are stored as object[] pending the port of URL handling. +/// Mirrors Go routesOption struct in reload.go. +/// TODO: session 13 — replace object[] with Uri[] when route types are ported. +/// +internal sealed class RoutesReloadOption : NoopReloadOption +{ + // TODO: session 13 — replace object[] with Uri[] when route URL types are ported + private readonly object[] _add; + private readonly object[] _remove; + + public RoutesReloadOption(object[] add, object[] remove) + { + _add = add; + _remove = remove; + } + + public override void Apply(NatsServer server) + { + // TODO: session 13 — add/remove routes, update varzUpdateRouteURLs + server.Noticef("Reloaded: cluster routes"); + } +} + +// ============================================================================= +// Connection limit & network option types +// ============================================================================= + +/// +/// Reload option for the max_connections setting. +/// Mirrors Go maxConnOption struct in reload.go. +/// +internal sealed class MaxConnReloadOption : NoopReloadOption +{ + private readonly int _newValue; + public MaxConnReloadOption(int newValue) => _newValue = newValue; + + public override void Apply(NatsServer server) + { + // TODO: session 13 — close random connections if over limit + server.Noticef("Reloaded: max_connections = {0}", _newValue); + } +} + +/// +/// Reload option for the pid_file setting. +/// Mirrors Go pidFileOption struct in reload.go. +/// +internal sealed class PidFileReloadOption : NoopReloadOption +{ + private readonly string _newValue; + public PidFileReloadOption(string newValue) => _newValue = newValue; + + public override void Apply(NatsServer server) + { + if (string.IsNullOrEmpty(_newValue)) + return; + // TODO: session 13 — call server.LogPid() + server.Noticef("Reloaded: pid_file = {0}", _newValue); + } +} + +/// +/// Reload option for the ports_file_dir setting. +/// Mirrors Go portsFileDirOption struct in reload.go. +/// +internal sealed class PortsFileDirReloadOption : NoopReloadOption +{ + private readonly string _oldValue; + private readonly string _newValue; + + public PortsFileDirReloadOption(string oldValue, string newValue) + { + _oldValue = oldValue; + _newValue = newValue; + } + + public override void Apply(NatsServer server) + { + // TODO: session 13 — call server.DeletePortsFile(_oldValue) and server.LogPorts() + server.Noticef("Reloaded: ports_file_dir = {0}", _newValue); + } +} + +/// +/// Reload option for the max_control_line setting. +/// Mirrors Go maxControlLineOption struct in reload.go. +/// +internal sealed class MaxControlLineReloadOption : NoopReloadOption +{ + private readonly int _newValue; + public MaxControlLineReloadOption(int newValue) => _newValue = newValue; + + public override void Apply(NatsServer server) + { + // TODO: session 13 — update mcl on each connected client + server.Noticef("Reloaded: max_control_line = {0}", _newValue); + } +} + +/// +/// Reload option for the max_payload setting. +/// Mirrors Go maxPayloadOption struct in reload.go. +/// +internal sealed class MaxPayloadReloadOption : NoopReloadOption +{ + private readonly int _newValue; + public MaxPayloadReloadOption(int newValue) => _newValue = newValue; + + public override void Apply(NatsServer server) + { + // TODO: session 13 — update server info and mpay on each client + server.Noticef("Reloaded: max_payload = {0}", _newValue); + } +} + +/// +/// Reload option for the ping_interval setting. +/// Mirrors Go pingIntervalOption struct in reload.go. +/// +internal sealed class PingIntervalReloadOption : NoopReloadOption +{ + private readonly TimeSpan _newValue; + public PingIntervalReloadOption(TimeSpan newValue) => _newValue = newValue; + + public override void Apply(NatsServer server) + => server.Noticef("Reloaded: ping_interval = {0}", _newValue); +} + +/// +/// Reload option for the ping_max setting. +/// Mirrors Go maxPingsOutOption struct in reload.go. +/// +internal sealed class MaxPingsOutReloadOption : NoopReloadOption +{ + private readonly int _newValue; + public MaxPingsOutReloadOption(int newValue) => _newValue = newValue; + + public override void Apply(NatsServer server) + => server.Noticef("Reloaded: ping_max = {0}", _newValue); +} + +/// +/// Reload option for the write_deadline setting. +/// Mirrors Go writeDeadlineOption struct in reload.go. +/// +internal sealed class WriteDeadlineReloadOption : NoopReloadOption +{ + private readonly TimeSpan _newValue; + public WriteDeadlineReloadOption(TimeSpan newValue) => _newValue = newValue; + + public override void Apply(NatsServer server) + => server.Noticef("Reloaded: write_deadline = {0}", _newValue); +} + +/// +/// Reload option for the client_advertise setting. +/// Mirrors Go clientAdvertiseOption struct in reload.go. +/// +internal sealed class ClientAdvertiseReloadOption : NoopReloadOption +{ + private readonly string _newValue; + public ClientAdvertiseReloadOption(string newValue) => _newValue = newValue; + + public override void Apply(NatsServer server) + { + // TODO: session 13 — call server.SetInfoHostPort() + server.Noticef("Reload: client_advertise = {0}", _newValue); + } +} + +// ============================================================================= +// JetStream option type +// ============================================================================= + +/// +/// Reload option for the jetstream setting. +/// Mirrors Go jetStreamOption struct in reload.go. +/// +internal sealed class JetStreamReloadOption : NoopReloadOption +{ + private readonly bool _newValue; + public JetStreamReloadOption(bool newValue) => _newValue = newValue; + + public override bool IsJetStreamChange() => true; + public override bool IsStatszChange() => true; + + public override void Apply(NatsServer server) + => server.Noticef("Reloaded: JetStream"); +} + +// ============================================================================= +// Miscellaneous option types +// ============================================================================= + +/// +/// Reload option for the default_sentinel setting. +/// Mirrors Go defaultSentinelOption struct in reload.go. +/// +internal sealed class DefaultSentinelReloadOption : NoopReloadOption +{ + private readonly string _newValue; + public DefaultSentinelReloadOption(string newValue) => _newValue = newValue; + + public override void Apply(NatsServer server) + => server.Noticef("Reloaded: default_sentinel = {0}", _newValue); +} + +/// +/// Reload option for the OCSP setting. +/// The new value is stored as object? pending the port of OCSPConfig. +/// Mirrors Go ocspOption struct in reload.go. +/// TODO: session 13 — replace object? with ported OcspConfig type. +/// +internal sealed class OcspReloadOption : TlsBaseReloadOption +{ + // TODO: session 13 — replace object? with ported OcspConfig type + private readonly object? _newValue; + public OcspReloadOption(object? newValue) => _newValue = newValue; + + public override void Apply(NatsServer server) + => server.Noticef("Reloaded: OCSP"); +} + +/// +/// Reload option for the OCSP response cache setting. +/// The new value is stored as object? pending the port of +/// OCSPResponseCacheConfig. +/// Mirrors Go ocspResponseCacheOption struct in reload.go. +/// TODO: session 13 — replace object? with ported OcspResponseCacheConfig type. +/// +internal sealed class OcspResponseCacheReloadOption : TlsBaseReloadOption +{ + // TODO: session 13 — replace object? with ported OcspResponseCacheConfig type + private readonly object? _newValue; + public OcspResponseCacheReloadOption(object? newValue) => _newValue = newValue; + + public override void Apply(NatsServer server) + => server.Noticef("Reloaded OCSP peer cache"); +} + +/// +/// Reload option for the connect_error_reports setting. +/// Mirrors Go connectErrorReports struct in reload.go. +/// +internal sealed class ConnectErrorReportsReloadOption : NoopReloadOption +{ + private readonly int _newValue; + public ConnectErrorReportsReloadOption(int newValue) => _newValue = newValue; + + public override void Apply(NatsServer server) + => server.Noticef("Reloaded: connect_error_reports = {0}", _newValue); +} + +/// +/// Reload option for the reconnect_error_reports setting. +/// Mirrors Go reconnectErrorReports struct in reload.go. +/// +internal sealed class ReconnectErrorReportsReloadOption : NoopReloadOption +{ + private readonly int _newValue; + public ReconnectErrorReportsReloadOption(int newValue) => _newValue = newValue; + + public override void Apply(NatsServer server) + => server.Noticef("Reloaded: reconnect_error_reports = {0}", _newValue); +} + +/// +/// Reload option for the max_traced_msg_len setting. +/// Mirrors Go maxTracedMsgLenOption struct in reload.go. +/// +internal sealed class MaxTracedMsgLenReloadOption : NoopReloadOption +{ + private readonly int _newValue; + public MaxTracedMsgLenReloadOption(int newValue) => _newValue = newValue; + + public override void Apply(NatsServer server) + { + // TODO: session 13 — update server.Opts.MaxTracedMsgLen under lock + server.Noticef("Reloaded: max_traced_msg_len = {0}", _newValue); + } +} + +// ============================================================================= +// MQTT option types +// ============================================================================= + +/// +/// Reload option for the MQTT ack_wait setting. +/// Mirrors Go mqttAckWaitReload struct in reload.go. +/// +internal sealed class MqttAckWaitReloadOption : NoopReloadOption +{ + private readonly TimeSpan _newValue; + public MqttAckWaitReloadOption(TimeSpan newValue) => _newValue = newValue; + + public override void Apply(NatsServer server) + => server.Noticef("Reloaded: MQTT ack_wait = {0}", _newValue); +} + +/// +/// Reload option for the MQTT max_ack_pending setting. +/// Mirrors Go mqttMaxAckPendingReload struct in reload.go. +/// +internal sealed class MqttMaxAckPendingReloadOption : NoopReloadOption +{ + private readonly ushort _newValue; + public MqttMaxAckPendingReloadOption(ushort newValue) => _newValue = newValue; + + public override void Apply(NatsServer server) + { + // TODO: session 13 — call server.MqttUpdateMaxAckPending(_newValue) + server.Noticef("Reloaded: MQTT max_ack_pending = {0}", _newValue); + } +} + +/// +/// Reload option for the MQTT stream_replicas setting. +/// Mirrors Go mqttStreamReplicasReload struct in reload.go. +/// +internal sealed class MqttStreamReplicasReloadOption : NoopReloadOption +{ + private readonly int _newValue; + public MqttStreamReplicasReloadOption(int newValue) => _newValue = newValue; + + public override void Apply(NatsServer server) + => server.Noticef("Reloaded: MQTT stream_replicas = {0}", _newValue); +} + +/// +/// Reload option for the MQTT consumer_replicas setting. +/// Mirrors Go mqttConsumerReplicasReload struct in reload.go. +/// +internal sealed class MqttConsumerReplicasReloadOption : NoopReloadOption +{ + private readonly int _newValue; + public MqttConsumerReplicasReloadOption(int newValue) => _newValue = newValue; + + public override void Apply(NatsServer server) + => server.Noticef("Reloaded: MQTT consumer_replicas = {0}", _newValue); +} + +/// +/// Reload option for the MQTT consumer_memory_storage setting. +/// Mirrors Go mqttConsumerMemoryStorageReload struct in reload.go. +/// +internal sealed class MqttConsumerMemoryStorageReloadOption : NoopReloadOption +{ + private readonly bool _newValue; + public MqttConsumerMemoryStorageReloadOption(bool newValue) => _newValue = newValue; + + public override void Apply(NatsServer server) + => server.Noticef("Reloaded: MQTT consumer_memory_storage = {0}", _newValue); +} + +/// +/// Reload option for the MQTT consumer_inactive_threshold setting. +/// Mirrors Go mqttInactiveThresholdReload struct in reload.go. +/// +internal sealed class MqttInactiveThresholdReloadOption : NoopReloadOption +{ + private readonly TimeSpan _newValue; + public MqttInactiveThresholdReloadOption(TimeSpan newValue) => _newValue = newValue; + + public override void Apply(NatsServer server) + => server.Noticef("Reloaded: MQTT consumer_inactive_threshold = {0}", _newValue); +} + +// ============================================================================= +// Profiling option type +// ============================================================================= + +/// +/// Reload option for the prof_block_rate setting. +/// Mirrors Go profBlockRateReload struct in reload.go. +/// +internal sealed class ProfBlockRateReloadOption : NoopReloadOption +{ + private readonly int _newValue; + public ProfBlockRateReloadOption(int newValue) => _newValue = newValue; + + public override void Apply(NatsServer server) + { + // TODO: session 13 — call server.SetBlockProfileRate(_newValue) + server.Noticef("Reloaded: prof_block_rate = {0}", _newValue); + } +} + +// ============================================================================= +// LeafNode option type +// ============================================================================= + +/// +/// Reload option for leaf-node settings (TLS handshake-first, compression, disabled). +/// Mirrors Go leafNodeOption struct in reload.go. +/// +internal sealed class LeafNodeReloadOption : NoopReloadOption +{ + private readonly bool _tlsFirstChanged; + private readonly bool _compressionChanged; + private readonly bool _disabledChanged; + + public LeafNodeReloadOption(bool tlsFirstChanged, bool compressionChanged, bool disabledChanged) + { + _tlsFirstChanged = tlsFirstChanged; + _compressionChanged = compressionChanged; + _disabledChanged = disabledChanged; + } + + public override void Apply(NatsServer server) + { + // TODO: session 13 — full leaf-node apply logic from Go leafNodeOption.Apply() + if (_tlsFirstChanged) + server.Noticef("Reloaded: LeafNode TLS HandshakeFirst settings"); + if (_compressionChanged) + server.Noticef("Reloaded: LeafNode compression settings"); + if (_disabledChanged) + server.Noticef("Reloaded: LeafNode disabled/enabled state"); + } +} + +// ============================================================================= +// NoFastProducerStall option type +// ============================================================================= + +/// +/// Reload option for the no_fast_producer_stall setting. +/// Mirrors Go noFastProdStallReload struct in reload.go. +/// +internal sealed class NoFastProducerStallReloadOption : NoopReloadOption +{ + private readonly bool _noStall; + public NoFastProducerStallReloadOption(bool noStall) => _noStall = noStall; + + public override void Apply(NatsServer server) + { + var not = _noStall ? "not " : string.Empty; + server.Noticef("Reloaded: fast producers will {0}be stalled", not); + } +} + +// ============================================================================= +// Proxies option type +// ============================================================================= + +/// +/// Reload option for the proxies trusted keys setting. +/// Mirrors Go proxiesReload struct in reload.go. +/// +internal sealed class ProxiesReloadOption : NoopReloadOption +{ + private readonly string[] _add; + private readonly string[] _del; + + public ProxiesReloadOption(string[] add, string[] del) + { + _add = add; + _del = del; + } + + public override void Apply(NatsServer server) + { + // TODO: session 13 — disconnect proxied clients for removed keys, + // call server.ProcessProxiesTrustedKeys() + if (_del.Length > 0) + server.Noticef("Reloaded: proxies trusted keys {0} were removed", string.Join(", ", _del)); + if (_add.Length > 0) + server.Noticef("Reloaded: proxies trusted keys {0} were added", string.Join(", ", _add)); + } +} + +// ============================================================================= +// ConfigReloader — stub for server/reload.go Reload() / ReloadOptions() +// ============================================================================= + +/// +/// Stub for the configuration reloader. +/// Full reload logic (diffOptions, applyOptions, recheckPinnedCerts) will be +/// implemented in a future session. +/// Mirrors Go Server.Reload() and Server.ReloadOptions() in +/// server/reload.go. +/// +internal sealed class ConfigReloader +{ + // TODO: session 13 — full reload logic + // Mirrors Go server.Reload() / server.ReloadOptions() in server/reload.go + + /// + /// Stub: read and apply the server config file. + /// Returns null on success; a non-null Exception describes the failure. + /// + public Exception? Reload(NatsServer server) => null; +} diff --git a/dotnet/src/ZB.MOM.NatsNet.Server/Events/EventTypes.cs b/dotnet/src/ZB.MOM.NatsNet.Server/Events/EventTypes.cs new file mode 100644 index 0000000..540ac72 --- /dev/null +++ b/dotnet/src/ZB.MOM.NatsNet.Server/Events/EventTypes.cs @@ -0,0 +1,778 @@ +// Copyright 2018-2026 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/events.go in the NATS server Go source. + +using System.Text.Json.Serialization; +using ZB.MOM.NatsNet.Server.Auth.CertificateIdentityProvider; +using ZB.MOM.NatsNet.Server.Internal; + +namespace ZB.MOM.NatsNet.Server; + +// ============================================================================ +// System subject constants +// Mirrors the const block at the top of server/events.go. +// ============================================================================ + +/// +/// System-account subject templates and constants used for internal NATS server +/// event routing. All format-string fields use with +/// the appropriate server/account ID substituted at call time. +/// Mirrors the const block in server/events.go. +/// +public static class SystemSubjects +{ + // Account lookup / claims + public const string AccLookupReqSubj = "$SYS.REQ.ACCOUNT.{0}.CLAIMS.LOOKUP"; + public const string AccPackReqSubj = "$SYS.REQ.CLAIMS.PACK"; + public const string AccListReqSubj = "$SYS.REQ.CLAIMS.LIST"; + public const string AccClaimsReqSubj = "$SYS.REQ.CLAIMS.UPDATE"; + public const string AccDeleteReqSubj = "$SYS.REQ.CLAIMS.DELETE"; + + // Connection events + public const string ConnectEventSubj = "$SYS.ACCOUNT.{0}.CONNECT"; + public const string DisconnectEventSubj = "$SYS.ACCOUNT.{0}.DISCONNECT"; + + // Direct request routing + public const string AccDirectReqSubj = "$SYS.REQ.ACCOUNT.{0}.{1}"; + public const string AccPingReqSubj = "$SYS.REQ.ACCOUNT.PING.{0}"; + + // Account update events (both old and new forms kept for backward compatibility) + public const string AccUpdateEventSubjOld = "$SYS.ACCOUNT.{0}.CLAIMS.UPDATE"; + public const string AccUpdateEventSubjNew = "$SYS.REQ.ACCOUNT.{0}.CLAIMS.UPDATE"; + + public const string ConnsRespSubj = "$SYS._INBOX_.{0}"; + public const string AccConnsEventSubjNew = "$SYS.ACCOUNT.{0}.SERVER.CONNS"; + public const string AccConnsEventSubjOld = "$SYS.SERVER.ACCOUNT.{0}.CONNS"; // backward compat + + // Server lifecycle events + public const string LameDuckEventSubj = "$SYS.SERVER.{0}.LAMEDUCK"; + public const string ShutdownEventSubj = "$SYS.SERVER.{0}.SHUTDOWN"; + + // Client control + public const string ClientKickReqSubj = "$SYS.REQ.SERVER.{0}.KICK"; + public const string ClientLdmReqSubj = "$SYS.REQ.SERVER.{0}.LDM"; + + // Auth error events + public const string AuthErrorEventSubj = "$SYS.SERVER.{0}.CLIENT.AUTH.ERR"; + public const string AuthErrorAccountEventSubj = "$SYS.ACCOUNT.CLIENT.AUTH.ERR"; + + // Stats + public const string ServerStatsSubj = "$SYS.SERVER.{0}.STATSZ"; + public const string ServerDirectReqSubj = "$SYS.REQ.SERVER.{0}.{1}"; + public const string ServerPingReqSubj = "$SYS.REQ.SERVER.PING.{0}"; + public const string ServerStatsPingReqSubj = "$SYS.REQ.SERVER.PING"; // deprecated; use STATSZ variant + public const string ServerReloadReqSubj = "$SYS.REQ.SERVER.{0}.RELOAD"; + + // Leaf node + public const string LeafNodeConnectEventSubj = "$SYS.ACCOUNT.{0}.LEAFNODE.CONNECT"; // internal only + + // Latency + public const string RemoteLatencyEventSubj = "$SYS.LATENCY.M2.{0}"; + public const string InboxRespSubj = "$SYS._INBOX.{0}.{1}"; + + // User info + public const string UserDirectInfoSubj = "$SYS.REQ.USER.INFO"; + public const string UserDirectReqSubj = "$SYS.REQ.USER.{0}.INFO"; + + // Subscription count + public const string AccNumSubsReqSubj = "$SYS.REQ.ACCOUNT.NSUBS"; + + // Debug + public const string AccSubsSubj = "$SYS.DEBUG.SUBSCRIBERS"; + + // OCSP peer events + public const string OcspPeerRejectEventSubj = "$SYS.SERVER.{0}.OCSP.PEER.CONN.REJECT"; + public const string OcspPeerChainlinkInvalidEventSubj = "$SYS.SERVER.{0}.OCSP.PEER.LINK.INVALID"; + + // Parsing constants (token indexes / counts) + public const int AccLookupReqTokens = 6; + public const int ShutdownEventTokens = 4; + public const int ServerSubjectIndex = 2; + public const int AccUpdateTokensNew = 6; + public const int AccUpdateTokensOld = 5; + public const int AccUpdateAccIdxOld = 2; + public const int AccReqTokens = 5; + public const int AccReqAccIndex = 3; +} + +// ============================================================================ +// Advisory message type schema URI constants +// Mirrors the const string variables near each struct in server/events.go. +// ============================================================================ + +public static class EventMsgTypes +{ + public const string ConnectEventMsgType = "io.nats.server.advisory.v1.client_connect"; + public const string DisconnectEventMsgType = "io.nats.server.advisory.v1.client_disconnect"; + public const string OcspPeerRejectEventMsgType = "io.nats.server.advisory.v1.ocsp_peer_reject"; + public const string OcspPeerChainlinkInvalidEventMsgType = "io.nats.server.advisory.v1.ocsp_peer_link_invalid"; + public const string AccountNumConnsMsgType = "io.nats.server.advisory.v1.account_connections"; +} + +// ============================================================================ +// Heartbeat / rate-limit intervals (mirrors package-level vars in events.go) +// ============================================================================ + +/// +/// Default timing constants for server event heartbeats and rate limiting. +/// Mirrors Go package-level var declarations in events.go. +/// +public static class EventIntervals +{ + /// Default HB interval for events. Mirrors Go eventsHBInterval = 30s. + public static readonly TimeSpan EventsHbInterval = TimeSpan.FromSeconds(30); + + /// Default HB interval for stats. Mirrors Go statsHBInterval = 10s. + public static readonly TimeSpan StatsHbInterval = TimeSpan.FromSeconds(10); + + /// Minimum interval between statsz publishes. Mirrors Go defaultStatszRateLimit = 1s. + public static readonly TimeSpan DefaultStatszRateLimit = TimeSpan.FromSeconds(1); +} + +// ============================================================================ +// SysMsgHandler — delegate for internal system message dispatch +// Mirrors Go sysMsgHandler func type in events.go. +// ============================================================================ + +/// +/// Callback invoked when an internal system message is dispatched. +/// Mirrors Go sysMsgHandler in server/events.go. +/// +public delegate void SysMsgHandler( + Subscription sub, + NatsClient client, + Account acc, + string subject, + string reply, + byte[] hdr, + byte[] msg); + +// ============================================================================ +// InSysMsg — queued internal system message +// Mirrors Go inSysMsg struct in server/events.go. +// ============================================================================ + +/// +/// Holds a system message queued for internal delivery, avoiding the +/// route/gateway path. +/// Mirrors Go inSysMsg struct in server/events.go. +/// +internal sealed class InSysMsg +{ + public Subscription? Sub { get; set; } + public NatsClient? Client { get; set; } + public Account? Acc { get; set; } + public string Subject { get; set; } = string.Empty; + public string Reply { get; set; } = string.Empty; + public byte[]? Hdr { get; set; } + public byte[]? Msg { get; set; } + public SysMsgHandler? Cb { get; set; } +} + +// ============================================================================ +// InternalState — server internal/sys state +// Mirrors Go internal struct in server/events.go. +// Uses Monitor lock (lock(this)) in place of Go's embedded sync.Mutex. +// ============================================================================ + +/// +/// Holds all internal state used by the server's system-account event +/// machinery: account reference, client, send/receive queues, timers, +/// reply handlers, and heartbeat configuration. +/// Mirrors Go internal struct in server/events.go. +/// +internal sealed class InternalState +{ + // ---- identity / sequencing ---- + public Account? Account { get; set; } + public NatsClient? Client { get; set; } + public ulong Seq { get; set; } + public int Sid { get; set; } + + // ---- remote server tracking ---- + /// Map of server ID → serverUpdate. Mirrors Go servers map[string]*serverUpdate. + public Dictionary Servers { get; set; } = new(); + + // ---- timers ---- + /// Sweeper timer. Mirrors Go sweeper *time.Timer. + public System.Threading.Timer? Sweeper { get; set; } + + /// Stats heartbeat timer. Mirrors Go stmr *time.Timer. + public System.Threading.Timer? StatsMsgTimer { get; set; } + + // ---- reply handlers ---- + /// + /// Pending reply subject → handler map. + /// Mirrors Go replies map[string]msgHandler. + /// + public Dictionary> Replies { get; set; } = new(); + + // ---- queues ---- + /// Outbound message send queue. Mirrors Go sendq *ipQueue[*pubMsg]. + public IpQueue? SendQueue { get; set; } + + /// Inbound receive queue. Mirrors Go recvq *ipQueue[*inSysMsg]. + public IpQueue? RecvQueue { get; set; } + + /// Priority receive queue for STATSZ/Pings. Mirrors Go recvqp *ipQueue[*inSysMsg]. + public IpQueue? RecvQueuePriority { get; set; } + + /// Reset channel used to restart the send loop. Mirrors Go resetCh chan struct{}. + public System.Threading.Channels.Channel? ResetChannel { get; set; } + + // ---- durations ---- + /// Maximum time before an orphaned server entry is removed. Mirrors Go orphMax. + public TimeSpan OrphanMax { get; set; } + + /// Interval at which orphan checks run. Mirrors Go chkOrph. + public TimeSpan CheckOrphan { get; set; } + + /// Interval between statsz publishes. Mirrors Go statsz. + public TimeSpan StatszInterval { get; set; } + + /// Client-facing statsz interval. Mirrors Go cstatsz. + public TimeSpan ClientStatszInterval { get; set; } + + // ---- misc ---- + /// Short hash used for shared-inbox routing. Mirrors Go shash string. + public string ShortHash { get; set; } = string.Empty; + + /// Inbox prefix for this server's internal client. Mirrors Go inboxPre string. + public string InboxPrefix { get; set; } = string.Empty; + + /// Subscription for remote stats. Mirrors Go remoteStatsSub *subscription. + public Subscription? RemoteStatsSub { get; set; } + + /// Time of the last statsz publish. Mirrors Go lastStatsz time.Time. + public DateTime LastStatsz { get; set; } +} + +// ============================================================================ +// ServerUpdate — remote server heartbeat tracking +// Mirrors Go serverUpdate struct in server/events.go. +// ============================================================================ + +/// +/// Tracks the sequence number and last-seen timestamp of a remote server's +/// system heartbeat. Used to detect orphaned servers. +/// Mirrors Go serverUpdate struct in server/events.go. +/// +internal sealed class ServerUpdate +{ + /// Last sequence number received from the remote server. + public ulong Seq { get; set; } + + /// Wall-clock time of the last heartbeat. + public DateTime LTime { get; set; } +} + +// ============================================================================ +// PubMsg — internally-queued outbound publish message +// Mirrors Go pubMsg struct in server/events.go. +// ============================================================================ + +/// +/// Holds an outbound message that the server wants to publish via the internal +/// send loop, avoiding direct route/gateway writes. +/// Mirrors Go pubMsg struct in server/events.go. +/// +internal sealed class PubMsg +{ + public NatsClient? Client { get; set; } + public string Subject { get; set; } = string.Empty; + public string Reply { get; set; } = string.Empty; + public ServerInfo? Si { get; set; } + public byte[]? Hdr { get; set; } + public object? Msg { get; set; } + + /// Compression type. TODO: session 12 — wire up compressionType enum. + public int Oct { get; set; } + + public bool Echo { get; set; } + public bool Last { get; set; } + + // TODO: session 12 — add pool return helper (returnToPool). +} + +// ============================================================================ +// DataStats — message/byte counter pair (sent or received) +// Mirrors Go DataStats struct in server/events.go. +// ============================================================================ + +/// +/// Reports how many messages and bytes were sent or received. +/// Optionally breaks out gateway, route, and leaf-node traffic. +/// Mirrors Go DataStats struct in server/events.go. +/// +public sealed class DataStats +{ + [JsonPropertyName("msgs")] + public long Msgs { get; set; } + + [JsonPropertyName("bytes")] + public long Bytes { get; set; } + + [JsonPropertyName("gateways")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public MsgBytes? Gateways { get; set; } + + [JsonPropertyName("routes")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public MsgBytes? Routes { get; set; } + + [JsonPropertyName("leafs")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public MsgBytes? Leafs { get; set; } +} + +// ============================================================================ +// MsgBytes — simple message+byte pair used inside DataStats +// Mirrors Go MsgBytes struct in server/events.go. +// ============================================================================ + +/// +/// A simple pair of message and byte counts, used as a nested breakdown +/// inside . +/// Mirrors Go MsgBytes struct in server/events.go. +/// +public sealed class MsgBytes +{ + [JsonPropertyName("msgs")] + public long Msgs { get; set; } + + [JsonPropertyName("bytes")] + public long Bytes { get; set; } +} + +// ============================================================================ +// RouteStat / GatewayStat — per-route and per-gateway stat snapshots +// Mirrors Go RouteStat and GatewayStat in server/events.go. +// ============================================================================ + +/// +/// Statistics snapshot for a single cluster route connection. +/// Mirrors Go RouteStat in server/events.go. +/// +public sealed class RouteStat +{ + [JsonPropertyName("rid")] + public ulong Id { get; set; } + + [JsonPropertyName("name")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? Name { get; set; } + + [JsonPropertyName("sent")] + public DataStats Sent { get; set; } = new(); + + [JsonPropertyName("received")] + public DataStats Received { get; set; } = new(); + + [JsonPropertyName("pending")] + public int Pending { get; set; } +} + +/// +/// Statistics snapshot for a gateway connection. +/// Mirrors Go GatewayStat in server/events.go. +/// +public sealed class GatewayStat +{ + [JsonPropertyName("gwid")] + public ulong Id { get; set; } + + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; + + [JsonPropertyName("sent")] + public DataStats Sent { get; set; } = new(); + + [JsonPropertyName("received")] + public DataStats Received { get; set; } = new(); + + [JsonPropertyName("inbound_connections")] + public int NumInbound { get; set; } +} + +// ============================================================================ +// ServerStatsMsg — periodic stats advisory published on $SYS.SERVER.{id}.STATSZ +// Mirrors Go ServerStatsMsg struct in server/events.go. +// ============================================================================ + +/// +/// Periodic advisory message containing the current server statistics. +/// Mirrors Go ServerStatsMsg struct in server/events.go. +/// +public sealed class ServerStatsMsg +{ + [JsonPropertyName("server")] + public ServerInfo Server { get; set; } = new(); + + [JsonPropertyName("statsz")] + public ServerStatsAdvisory Stats { get; set; } = new(); +} + +// ============================================================================ +// ServerStatsAdvisory — the statsz payload inside ServerStatsMsg +// Mirrors Go ServerStats struct (advisory form) in server/events.go. +// NOTE: distinct from the internal ServerStats in NatsServerTypes.cs. +// ============================================================================ + +/// +/// The JSON-serialisable statistics payload included inside . +/// Mirrors Go ServerStats struct (advisory form) in server/events.go. +/// +public sealed class ServerStatsAdvisory +{ + [JsonPropertyName("start")] + public DateTime Start { get; set; } + + [JsonPropertyName("mem")] + public long Mem { get; set; } + + [JsonPropertyName("cores")] + public int Cores { get; set; } + + [JsonPropertyName("cpu")] + public double Cpu { get; set; } + + [JsonPropertyName("connections")] + public int Connections { get; set; } + + [JsonPropertyName("total_connections")] + public ulong TotalConnections { get; set; } + + [JsonPropertyName("active_accounts")] + public int ActiveAccounts { get; set; } + + [JsonPropertyName("subscriptions")] + public uint NumSubs { get; set; } + + [JsonPropertyName("sent")] + public DataStats Sent { get; set; } = new(); + + [JsonPropertyName("received")] + public DataStats Received { get; set; } = new(); + + [JsonPropertyName("slow_consumers")] + public long SlowConsumers { get; set; } + + [JsonPropertyName("slow_consumer_stats")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public SlowConsumersStats? SlowConsumersStats { get; set; } + + [JsonPropertyName("stale_connections")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public long StaleConnections { get; set; } + + [JsonPropertyName("stale_connection_stats")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public StaleConnectionStats? StaleConnectionStats { get; set; } + + [JsonPropertyName("stalled_clients")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public long StalledClients { get; set; } + + [JsonPropertyName("routes")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public List? Routes { get; set; } + + [JsonPropertyName("gateways")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public List? Gateways { get; set; } + + [JsonPropertyName("active_servers")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public int ActiveServers { get; set; } + + /// JetStream stats. TODO: session 19 — wire JetStreamVarz type. + [JsonPropertyName("jetstream")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public object? JetStream { get; set; } + + [JsonPropertyName("gomemlimit")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public long MemLimit { get; set; } + + [JsonPropertyName("gomaxprocs")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public int MaxProcs { get; set; } +} + +// ============================================================================ +// SlowConsumersStats / StaleConnectionStats — advisory-layer per-kind counters +// These are the JSON-serialisable variants used in ServerStatsAdvisory. +// The internal atomic counters live in NatsServerTypes.cs (SlowConsumerStats / +// StaleConnectionStats with different casing). +// ============================================================================ + +/// +/// Per-kind slow-consumer counters included in stats advisories. +/// Mirrors Go SlowConsumersStats in server/monitor.go. +/// +public sealed class SlowConsumersStats +{ + [JsonPropertyName("clients")] + public ulong Clients { get; set; } + + [JsonPropertyName("routes")] + public ulong Routes { get; set; } + + [JsonPropertyName("gateways")] + public ulong Gateways { get; set; } + + [JsonPropertyName("leafs")] + public ulong Leafs { get; set; } +} + +/// +/// Per-kind stale-connection counters included in stats advisories. +/// Mirrors Go StaleConnectionStats in server/monitor.go. +/// +public sealed class StaleConnectionStats +{ + [JsonPropertyName("clients")] + public ulong Clients { get; set; } + + [JsonPropertyName("routes")] + public ulong Routes { get; set; } + + [JsonPropertyName("gateways")] + public ulong Gateways { get; set; } + + [JsonPropertyName("leafs")] + public ulong Leafs { get; set; } +} + +// ============================================================================ +// ConnectEventMsg / DisconnectEventMsg — client lifecycle advisories +// Mirrors Go structs in server/events.go. +// ============================================================================ + +/// +/// Advisory published on $SYS.ACCOUNT.{acc}.CONNECT when a new +/// client connection is established within a tracked account. +/// Mirrors Go ConnectEventMsg in server/events.go. +/// +public sealed class ConnectEventMsg : TypedEvent +{ + [JsonPropertyName("server")] + public ServerInfo Server { get; set; } = new(); + + [JsonPropertyName("client")] + public ClientInfo Client { get; set; } = new(); +} + +/// +/// Advisory published on $SYS.ACCOUNT.{acc}.DISCONNECT when a +/// previously-tracked client connection closes. +/// Mirrors Go DisconnectEventMsg in server/events.go. +/// +public sealed class DisconnectEventMsg : TypedEvent +{ + [JsonPropertyName("server")] + public ServerInfo Server { get; set; } = new(); + + [JsonPropertyName("client")] + public ClientInfo Client { get; set; } = new(); + + [JsonPropertyName("sent")] + public DataStats Sent { get; set; } = new(); + + [JsonPropertyName("received")] + public DataStats Received { get; set; } = new(); + + [JsonPropertyName("reason")] + public string Reason { get; set; } = string.Empty; +} + +// ============================================================================ +// OCSPPeerRejectEventMsg / OCSPPeerChainlinkInvalidEventMsg +// Mirrors Go structs in server/events.go. +// ============================================================================ + +/// +/// Advisory published when a peer TLS handshake is rejected due to OCSP +/// invalidation of the peer's leaf certificate. +/// Mirrors Go OCSPPeerRejectEventMsg in server/events.go. +/// +public sealed class OcspPeerRejectEventMsg : TypedEvent +{ + [JsonPropertyName("kind")] + public string Kind { get; set; } = string.Empty; + + [JsonPropertyName("peer")] + public CertInfo Peer { get; set; } = new(); + + [JsonPropertyName("server")] + public ServerInfo Server { get; set; } = new(); + + [JsonPropertyName("reason")] + public string Reason { get; set; } = string.Empty; +} + +/// +/// Advisory published when a certificate in a valid TLS chain is found to be +/// OCSP-invalid during a peer handshake. Both the invalid link and the +/// peer's leaf cert are included. +/// Mirrors Go OCSPPeerChainlinkInvalidEventMsg in server/events.go. +/// +public sealed class OcspPeerChainlinkInvalidEventMsg : TypedEvent +{ + [JsonPropertyName("link")] + public CertInfo Link { get; set; } = new(); + + [JsonPropertyName("peer")] + public CertInfo Peer { get; set; } = new(); + + [JsonPropertyName("server")] + public ServerInfo Server { get; set; } = new(); + + [JsonPropertyName("reason")] + public string Reason { get; set; } = string.Empty; +} + +// ============================================================================ +// AccountNumConns / AccountStat — account connection count advisories +// Mirrors Go structs in server/events.go. +// ============================================================================ + +/// +/// Advisory heartbeat published when the connection count for a tracked +/// account changes, or on a periodic schedule. +/// Mirrors Go AccountNumConns struct in server/events.go. +/// +public sealed class AccountNumConns : TypedEvent +{ + [JsonPropertyName("server")] + public ServerInfo Server { get; set; } = new(); + + // Embedded AccountStat fields are inlined via composition. + [JsonPropertyName("acc")] + public string Account { get; set; } = string.Empty; + + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; + + [JsonPropertyName("conns")] + public int Conns { get; set; } + + [JsonPropertyName("leafnodes")] + public int LeafNodes { get; set; } + + [JsonPropertyName("total_conns")] + public int TotalConns { get; set; } + + [JsonPropertyName("num_subscriptions")] + public uint NumSubs { get; set; } + + [JsonPropertyName("sent")] + public DataStats Sent { get; set; } = new(); + + [JsonPropertyName("received")] + public DataStats Received { get; set; } = new(); + + [JsonPropertyName("slow_consumers")] + public long SlowConsumers { get; set; } +} + +/// +/// Statistic data common to and account-level +/// monitoring responses. +/// Mirrors Go AccountStat struct in server/events.go. +/// +public sealed class AccountStat +{ + [JsonPropertyName("acc")] + public string Account { get; set; } = string.Empty; + + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; + + [JsonPropertyName("conns")] + public int Conns { get; set; } + + [JsonPropertyName("leafnodes")] + public int LeafNodes { get; set; } + + [JsonPropertyName("total_conns")] + public int TotalConns { get; set; } + + [JsonPropertyName("num_subscriptions")] + public uint NumSubs { get; set; } + + [JsonPropertyName("sent")] + public DataStats Sent { get; set; } = new(); + + [JsonPropertyName("received")] + public DataStats Received { get; set; } = new(); + + [JsonPropertyName("slow_consumers")] + public long SlowConsumers { get; set; } +} + +/// +/// Internal request payload sent when this server first starts tracking an +/// account, asking peer servers for their local connection counts. +/// Mirrors Go accNumConnsReq struct in server/events.go. +/// +internal sealed class AccNumConnsReq +{ + [JsonPropertyName("server")] + public ServerInfo Server { get; set; } = new(); + + [JsonPropertyName("acc")] + public string Account { get; set; } = string.Empty; +} + +// ============================================================================ +// ServerCapability / ServerID — server identity and capability flags +// Mirrors Go types in server/events.go. +// ============================================================================ + +/// +/// Bit-flag capability set for a remote server. +/// Mirrors Go ServerCapability uint64 in server/events.go. +/// +[Flags] +public enum ServerCapability : ulong +{ + /// No capabilities. + None = 0, + + /// Server has JetStream enabled. Mirrors Go JetStreamEnabled. + JetStreamEnabled = 1UL << 0, + + /// New stream snapshot capability. Mirrors Go BinaryStreamSnapshot. + BinaryStreamSnapshot = 1UL << 1, + + /// Move NRG traffic out of system account. Mirrors Go AccountNRG. + AccountNrg = 1UL << 2, +} + +/// +/// Minimal static identity for a remote server (name, host, ID). +/// Mirrors Go ServerID struct in server/events.go. +/// +public sealed class ServerIdentity +{ + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; + + [JsonPropertyName("host")] + public string Host { get; set; } = string.Empty; + + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; +} diff --git a/dotnet/src/ZB.MOM.NatsNet.Server/MessageTrace/MsgTraceTypes.cs b/dotnet/src/ZB.MOM.NatsNet.Server/MessageTrace/MsgTraceTypes.cs new file mode 100644 index 0000000..da75fa9 --- /dev/null +++ b/dotnet/src/ZB.MOM.NatsNet.Server/MessageTrace/MsgTraceTypes.cs @@ -0,0 +1,465 @@ +// Copyright 2024-2026 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/msgtrace.go in the NATS server Go source. + +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace ZB.MOM.NatsNet.Server; + +// ============================================================================ +// Message-trace header name constants +// Mirrors Go const block at top of server/msgtrace.go. +// ============================================================================ + +/// +/// NATS message-trace header names and special sentinel values. +/// Mirrors Go const block in server/msgtrace.go. +/// +public static class MsgTraceHeaders +{ + /// Header that carries the trace destination subject. Mirrors Go MsgTraceDest. + public const string MsgTraceDest = "Nats-Trace-Dest"; + + /// + /// Sentinel value placed in the trace-dest header to disable tracing + /// (must be an invalid NATS subject). Mirrors Go MsgTraceDestDisabled. + /// + public const string MsgTraceDestDisabled = "trace disabled"; + + /// Header used for hop-count tracking across servers. Mirrors Go MsgTraceHop. + public const string MsgTraceHop = "Nats-Trace-Hop"; + + /// Header that carries the originating account name. Mirrors Go MsgTraceOriginAccount. + public const string MsgTraceOriginAccount = "Nats-Trace-Origin-Account"; + + /// + /// When set to a truthy value, the message is consumed only for tracing + /// and not delivered to subscribers. Mirrors Go MsgTraceOnly. + /// + public const string MsgTraceOnly = "Nats-Trace-Only"; + + /// + /// W3C trace-context parent header. NATS no longer lower-cases this but + /// accepts it in any case. Mirrors Go traceParentHdr (internal). + /// + public const string TraceParentHdr = "traceparent"; +} + +// ============================================================================ +// MsgTraceType — discriminator string for polymorphic trace event lists +// Mirrors Go MsgTraceType string in server/msgtrace.go. +// ============================================================================ + +/// +/// Discriminator string identifying the concrete type of a trace event +/// within a list. +/// Mirrors Go MsgTraceType string and its constants in server/msgtrace.go. +/// +public sealed class MsgTraceType +{ + private readonly string _value; + private MsgTraceType(string value) => _value = value; + + /// + public override string ToString() => _value; + + public static implicit operator MsgTraceType(string value) => new(value); + public static implicit operator string(MsgTraceType t) => t._value; + + public override bool Equals(object? obj) => + obj is MsgTraceType other && _value == other._value; + + public override int GetHashCode() => _value.GetHashCode(); + + // ---- Well-known type constants (mirror Go const block) ---- + + /// Ingress event. Mirrors Go MsgTraceIngressType = "in". + public static readonly MsgTraceType Ingress = new("in"); + + /// Subject-mapping event. Mirrors Go MsgTraceSubjectMappingType = "sm". + public static readonly MsgTraceType SubjectMapping = new("sm"); + + /// Stream-export event. Mirrors Go MsgTraceStreamExportType = "se". + public static readonly MsgTraceType StreamExport = new("se"); + + /// Service-import event. Mirrors Go MsgTraceServiceImportType = "si". + public static readonly MsgTraceType ServiceImport = new("si"); + + /// JetStream storage event. Mirrors Go MsgTraceJetStreamType = "js". + public static readonly MsgTraceType JetStream = new("js"); + + /// Egress (delivery) event. Mirrors Go MsgTraceEgressType = "eg". + public static readonly MsgTraceType Egress = new("eg"); +} + +// ============================================================================ +// IMsgTrace — interface for polymorphic trace events +// Mirrors Go MsgTrace interface in server/msgtrace.go. +// ============================================================================ + +/// +/// Marker interface implemented by all concrete message-trace event types. +/// Enables polymorphic handling of the list. +/// Mirrors Go MsgTrace interface in server/msgtrace.go. +/// +public interface IMsgTrace +{ + /// Returns the discriminator type string for this trace event. + string Typ(); +} + +// ============================================================================ +// MsgTraceBase — shared fields present in every trace event +// Mirrors Go MsgTraceBase struct in server/msgtrace.go. +// ============================================================================ + +/// +/// Common base fields shared by all concrete message-trace event types. +/// Mirrors Go MsgTraceBase struct in server/msgtrace.go. +/// +public class MsgTraceBase : IMsgTrace +{ + [JsonPropertyName("type")] + public string Type { get; set; } = string.Empty; + + [JsonPropertyName("ts")] + public DateTime Timestamp { get; set; } + + /// + public virtual string Typ() => Type; +} + +// ============================================================================ +// MsgTraceIngress — client / route / gateway / leaf connection ingress event +// Mirrors Go MsgTraceIngress struct in server/msgtrace.go. +// ============================================================================ + +/// +/// Records the point at which a message was received by the server from a +/// client, route, gateway, or leaf connection. +/// Mirrors Go MsgTraceIngress struct in server/msgtrace.go. +/// +public sealed class MsgTraceIngress : MsgTraceBase +{ + [JsonPropertyName("kind")] + public int Kind { get; set; } + + [JsonPropertyName("cid")] + public ulong Cid { get; set; } + + [JsonPropertyName("name")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? Name { get; set; } + + [JsonPropertyName("acc")] + public string Account { get; set; } = string.Empty; + + [JsonPropertyName("subj")] + public string Subject { get; set; } = string.Empty; + + [JsonPropertyName("error")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? Error { get; set; } +} + +// ============================================================================ +// MsgTraceSubjectMapping — subject-mapping rewrite event +// Mirrors Go MsgTraceSubjectMapping struct in server/msgtrace.go. +// ============================================================================ + +/// +/// Records a subject-mapping rewrite applied to an in-flight message. +/// Mirrors Go MsgTraceSubjectMapping struct in server/msgtrace.go. +/// +public sealed class MsgTraceSubjectMapping : MsgTraceBase +{ + [JsonPropertyName("to")] + public string MappedTo { get; set; } = string.Empty; +} + +// ============================================================================ +// MsgTraceStreamExport — stream export / cross-account delivery event +// Mirrors Go MsgTraceStreamExport struct in server/msgtrace.go. +// ============================================================================ + +/// +/// Records delivery of a message to a stream-export destination account. +/// Mirrors Go MsgTraceStreamExport struct in server/msgtrace.go. +/// +public sealed class MsgTraceStreamExport : MsgTraceBase +{ + [JsonPropertyName("acc")] + public string Account { get; set; } = string.Empty; + + [JsonPropertyName("to")] + public string To { get; set; } = string.Empty; +} + +// ============================================================================ +// MsgTraceServiceImport — service import routing event +// Mirrors Go MsgTraceServiceImport struct in server/msgtrace.go. +// ============================================================================ + +/// +/// Records routing of a message via a service-import from one account to +/// another. +/// Mirrors Go MsgTraceServiceImport struct in server/msgtrace.go. +/// +public sealed class MsgTraceServiceImport : MsgTraceBase +{ + [JsonPropertyName("acc")] + public string Account { get; set; } = string.Empty; + + [JsonPropertyName("from")] + public string From { get; set; } = string.Empty; + + [JsonPropertyName("to")] + public string To { get; set; } = string.Empty; +} + +// ============================================================================ +// MsgTraceJetStream — JetStream storage event +// Mirrors Go MsgTraceJetStream struct in server/msgtrace.go. +// ============================================================================ + +/// +/// Records the attempt (and outcome) of storing or delivering a message +/// to a JetStream stream. +/// Mirrors Go MsgTraceJetStream struct in server/msgtrace.go. +/// +public sealed class MsgTraceJetStream : MsgTraceBase +{ + [JsonPropertyName("stream")] + public string Stream { get; set; } = string.Empty; + + [JsonPropertyName("subject")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? Subject { get; set; } + + [JsonPropertyName("nointerest")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public bool NoInterest { get; set; } + + [JsonPropertyName("error")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? Error { get; set; } +} + +// ============================================================================ +// MsgTraceEgress — outbound delivery event +// Mirrors Go MsgTraceEgress struct in server/msgtrace.go. +// ============================================================================ + +/// +/// Records the outbound delivery of a message to a subscriber, route, +/// gateway, or leaf connection. +/// Mirrors Go MsgTraceEgress struct in server/msgtrace.go. +/// +public sealed class MsgTraceEgress : MsgTraceBase +{ + [JsonPropertyName("kind")] + public int Kind { get; set; } + + [JsonPropertyName("cid")] + public ulong Cid { get; set; } + + [JsonPropertyName("name")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? Name { get; set; } + + [JsonPropertyName("hop")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? Hop { get; set; } + + [JsonPropertyName("acc")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? Account { get; set; } + + [JsonPropertyName("sub")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? Subscription { get; set; } + + [JsonPropertyName("queue")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? Queue { get; set; } + + [JsonPropertyName("error")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? Error { get; set; } + + /// + /// Optional link to the produced by the remote + /// server that received this egress message (route/leaf/gateway hop). + /// Not serialised. Mirrors Go Link *MsgTraceEvent. + /// + [JsonIgnore] + public MsgTraceEvent? Link { get; set; } +} + +// ============================================================================ +// MsgTraceEvents — polymorphic list with custom JSON deserialiser +// Mirrors Go MsgTraceEvents []MsgTrace and its UnmarshalJSON in msgtrace.go. +// ============================================================================ + +/// +/// Custom JSON converter that deserialises a MsgTraceEvents JSON array +/// into the correct concrete subtype, using the +/// "type" discriminator field. +/// Mirrors Go MsgTraceEvents.UnmarshalJSON in server/msgtrace.go. +/// +public sealed class MsgTraceEventsConverter : JsonConverter> +{ + private static readonly Dictionary> Factories = new() + { + ["in"] = e => e.Deserialize()!, + ["sm"] = e => e.Deserialize()!, + ["se"] = e => e.Deserialize()!, + ["si"] = e => e.Deserialize()!, + ["js"] = e => e.Deserialize()!, + ["eg"] = e => e.Deserialize()!, + }; + + public override List Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) + { + var result = new List(); + using var doc = JsonDocument.ParseValue(ref reader); + + foreach (var element in doc.RootElement.EnumerateArray()) + { + if (!element.TryGetProperty("type", out var typeProp)) + throw new JsonException("MsgTrace element missing 'type' field."); + + var typeStr = typeProp.GetString() ?? string.Empty; + + if (!Factories.TryGetValue(typeStr, out var factory)) + throw new JsonException($"Unknown MsgTrace type '{typeStr}'."); + + result.Add(factory(element)); + } + + return result; + } + + public override void Write( + Utf8JsonWriter writer, + List value, + JsonSerializerOptions options) + { + writer.WriteStartArray(); + foreach (var item in value) + JsonSerializer.Serialize(writer, item, item.GetType(), options); + writer.WriteEndArray(); + } +} + +// ============================================================================ +// MsgTraceRequest — the original request metadata included in a trace event +// Mirrors Go MsgTraceRequest struct in server/msgtrace.go. +// ============================================================================ + +/// +/// Captures the headers and size of the original message that triggered a +/// trace event. +/// Mirrors Go MsgTraceRequest struct in server/msgtrace.go. +/// +public sealed class MsgTraceRequest +{ + /// + /// Original message headers, preserving header-name casing. + /// Mirrors Go Header map[string][]string (not http.Header, so casing is preserved). + /// + [JsonPropertyName("header")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public Dictionary>? Header { get; set; } + + [JsonPropertyName("msgsize")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public int MsgSize { get; set; } +} + +// ============================================================================ +// MsgTraceEvent — top-level trace event published to the trace destination +// Mirrors Go MsgTraceEvent struct in server/msgtrace.go. +// ============================================================================ + +/// +/// The top-level message-trace advisory published to the trace destination +/// subject. Contains server identity, the original request metadata, the +/// hop count, and the ordered list of trace events. +/// Mirrors Go MsgTraceEvent struct in server/msgtrace.go. +/// +public sealed class MsgTraceEvent +{ + [JsonPropertyName("server")] + public ServerInfo Server { get; set; } = new(); + + [JsonPropertyName("request")] + public MsgTraceRequest Request { get; set; } = new(); + + [JsonPropertyName("hops")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public int Hops { get; set; } + + [JsonPropertyName("events")] + [JsonConverter(typeof(MsgTraceEventsConverter))] + public List Events { get; set; } = []; + + // ---- Convenience accessors (mirrors Go helper methods on MsgTraceEvent) ---- + + /// + /// Returns the first event if it is a , else null. + /// Mirrors Go MsgTraceEvent.Ingress(). + /// + public MsgTraceIngress? Ingress() => + Events.Count > 0 ? Events[0] as MsgTraceIngress : null; + + /// + /// Returns the first in the event list, or null. + /// Mirrors Go MsgTraceEvent.SubjectMapping(). + /// + public MsgTraceSubjectMapping? SubjectMapping() => + Events.OfType().FirstOrDefault(); + + /// + /// Returns all events. + /// Mirrors Go MsgTraceEvent.StreamExports(). + /// + public IReadOnlyList StreamExports() => + Events.OfType().ToList(); + + /// + /// Returns all events. + /// Mirrors Go MsgTraceEvent.ServiceImports(). + /// + public IReadOnlyList ServiceImports() => + Events.OfType().ToList(); + + /// + /// Returns the first event, or null. + /// Mirrors Go MsgTraceEvent.JetStream(). + /// + public MsgTraceJetStream? JetStream() => + Events.OfType().FirstOrDefault(); + + /// + /// Returns all events. + /// Mirrors Go MsgTraceEvent.Egresses(). + /// + public IReadOnlyList Egresses() => + Events.OfType().ToList(); +} diff --git a/dotnet/src/ZB.MOM.NatsNet.Server/Monitor/MonitorSortOptions.cs b/dotnet/src/ZB.MOM.NatsNet.Server/Monitor/MonitorSortOptions.cs new file mode 100644 index 0000000..9cc1167 --- /dev/null +++ b/dotnet/src/ZB.MOM.NatsNet.Server/Monitor/MonitorSortOptions.cs @@ -0,0 +1,294 @@ +// Copyright 2013-2026 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/monitor_sort_opts.go in the NATS server Go source. + +using System.Text.Json.Serialization; + +namespace ZB.MOM.NatsNet.Server; + +// ============================================================================ +// SortOpt — string wrapper type for connection-list sort options +// Mirrors Go SortOpt string in server/monitor_sort_opts.go. +// ============================================================================ + +/// +/// A strongly-typed sort option for . +/// Wraps a raw string value corresponding to the JSON sort key. +/// Mirrors Go SortOpt string in server/monitor_sort_opts.go. +/// +public sealed class SortOpt +{ + private readonly string _value; + + private SortOpt(string value) => _value = value; + + /// Returns the raw sort-option string value. + public override string ToString() => _value; + + /// Allows implicit conversion from a string literal. + public static implicit operator SortOpt(string value) => new(value); + + /// Allows implicit conversion back to a plain string. + public static implicit operator string(SortOpt opt) => opt._value; + + public override bool Equals(object? obj) => + obj is SortOpt other && _value == other._value; + + public override int GetHashCode() => _value.GetHashCode(); + + // ---- Well-known sort-option constants ---- + // Mirrors Go const block in monitor_sort_opts.go. + + /// Sort by connection ID (ascending). Mirrors Go ByCid = "cid". + public static readonly SortOpt ByCid = new("cid"); + + /// Sort by connection start time (same as ByCid). Mirrors Go ByStart = "start". + public static readonly SortOpt ByStart = new("start"); + + /// Sort by number of subscriptions (descending). Mirrors Go BySubs = "subs". + public static readonly SortOpt BySubs = new("subs"); + + /// Sort by pending bytes waiting to be sent (descending). Mirrors Go ByPending = "pending". + public static readonly SortOpt ByPending = new("pending"); + + /// Sort by number of outbound messages (descending). Mirrors Go ByOutMsgs = "msgs_to". + public static readonly SortOpt ByOutMsgs = new("msgs_to"); + + /// Sort by number of inbound messages (descending). Mirrors Go ByInMsgs = "msgs_from". + public static readonly SortOpt ByInMsgs = new("msgs_from"); + + /// Sort by bytes sent (descending). Mirrors Go ByOutBytes = "bytes_to". + public static readonly SortOpt ByOutBytes = new("bytes_to"); + + /// Sort by bytes received (descending). Mirrors Go ByInBytes = "bytes_from". + public static readonly SortOpt ByInBytes = new("bytes_from"); + + /// Sort by last activity time (descending). Mirrors Go ByLast = "last". + public static readonly SortOpt ByLast = new("last"); + + /// Sort by idle duration (descending). Mirrors Go ByIdle = "idle". + public static readonly SortOpt ByIdle = new("idle"); + + /// Sort by uptime (descending). Mirrors Go ByUptime = "uptime". + public static readonly SortOpt ByUptime = new("uptime"); + + /// Sort by stop time — only valid on closed connections. Mirrors Go ByStop = "stop". + public static readonly SortOpt ByStop = new("stop"); + + /// Sort by close reason — only valid on closed connections. Mirrors Go ByReason = "reason". + public static readonly SortOpt ByReason = new("reason"); + + /// Sort by round-trip time (descending). Mirrors Go ByRTT = "rtt". + public static readonly SortOpt ByRtt = new("rtt"); + + private static readonly HashSet ValidValues = + [ + "", "cid", "start", "subs", "pending", + "msgs_to", "msgs_from", "bytes_to", "bytes_from", + "last", "idle", "uptime", "stop", "reason", "rtt" + ]; + + /// + /// Returns true if this sort option is a recognised value. + /// Mirrors Go SortOpt.IsValid() in monitor_sort_opts.go. + /// + public bool IsValid() => ValidValues.Contains(_value); +} + +// ============================================================================ +// ConnInfos — sortable list wrapper for ConnInfo pointers +// Mirrors Go ConnInfos []*ConnInfo in monitor_sort_opts.go. +// ============================================================================ + +/// +/// A list of objects that can be sorted using one of +/// the SortBy* comparers defined in this file. +/// Mirrors Go ConnInfos []*ConnInfo in server/monitor_sort_opts.go. +/// +public sealed class ConnInfos : List +{ + public ConnInfos() { } + public ConnInfos(IEnumerable items) : base(items) { } +} + +// ============================================================================ +// IComparer implementations — one per sort option +// Each class mirrors the corresponding Less() method in monitor_sort_opts.go. +// ============================================================================ + +/// Sort by connection ID (ascending). Mirrors Go SortByCid. +public sealed class SortByCid : IComparer +{ + public static readonly SortByCid Instance = new(); + public int Compare(ConnInfo? x, ConnInfo? y) + { + if (x is null || y is null) return 0; + return x.Cid.CompareTo(y.Cid); + } +} + +/// Sort by number of subscriptions (ascending for underlying sort; caller reverses if needed). +/// Mirrors Go SortBySubs. +public sealed class SortBySubs : IComparer +{ + public static readonly SortBySubs Instance = new(); + public int Compare(ConnInfo? x, ConnInfo? y) + { + if (x is null || y is null) return 0; + return x.NumSubs.CompareTo(y.NumSubs); + } +} + +/// Sort by pending bytes. Mirrors Go SortByPending. +public sealed class SortByPending : IComparer +{ + public static readonly SortByPending Instance = new(); + public int Compare(ConnInfo? x, ConnInfo? y) + { + if (x is null || y is null) return 0; + return x.Pending.CompareTo(y.Pending); + } +} + +/// Sort by outbound message count. Mirrors Go SortByOutMsgs. +public sealed class SortByOutMsgs : IComparer +{ + public static readonly SortByOutMsgs Instance = new(); + public int Compare(ConnInfo? x, ConnInfo? y) + { + if (x is null || y is null) return 0; + return x.OutMsgs.CompareTo(y.OutMsgs); + } +} + +/// Sort by inbound message count. Mirrors Go SortByInMsgs. +public sealed class SortByInMsgs : IComparer +{ + public static readonly SortByInMsgs Instance = new(); + public int Compare(ConnInfo? x, ConnInfo? y) + { + if (x is null || y is null) return 0; + return x.InMsgs.CompareTo(y.InMsgs); + } +} + +/// Sort by outbound bytes. Mirrors Go SortByOutBytes. +public sealed class SortByOutBytes : IComparer +{ + public static readonly SortByOutBytes Instance = new(); + public int Compare(ConnInfo? x, ConnInfo? y) + { + if (x is null || y is null) return 0; + return x.OutBytes.CompareTo(y.OutBytes); + } +} + +/// Sort by inbound bytes. Mirrors Go SortByInBytes. +public sealed class SortByInBytes : IComparer +{ + public static readonly SortByInBytes Instance = new(); + public int Compare(ConnInfo? x, ConnInfo? y) + { + if (x is null || y is null) return 0; + return x.InBytes.CompareTo(y.InBytes); + } +} + +/// Sort by last activity timestamp. Mirrors Go SortByLast. +public sealed class SortByLast : IComparer +{ + public static readonly SortByLast Instance = new(); + public int Compare(ConnInfo? x, ConnInfo? y) + { + if (x is null || y is null) return 0; + return x.LastActivity.CompareTo(y.LastActivity); + } +} + +/// +/// Sort by idle duration (time since last activity), relative to a supplied +/// reference time. Mirrors Go SortByIdle. +/// +public sealed class SortByIdle : IComparer +{ + private readonly DateTime _now; + + public SortByIdle(DateTime now) => _now = now; + + public int Compare(ConnInfo? x, ConnInfo? y) + { + if (x is null || y is null) return 0; + var idleX = _now - x.LastActivity; + var idleY = _now - y.LastActivity; + return idleX.CompareTo(idleY); + } +} + +/// +/// Sort by uptime (time the connection has been open), relative to a supplied +/// reference time. Mirrors Go SortByUptime. +/// +public sealed class SortByUptime : IComparer +{ + private readonly DateTime _now; + + public SortByUptime(DateTime now) => _now = now; + + public int Compare(ConnInfo? x, ConnInfo? y) + { + if (x is null || y is null) return 0; + var uptimeX = (x.Stop is null || x.Stop == default) ? _now - x.Start : x.Stop.Value - x.Start; + var uptimeY = (y.Stop is null || y.Stop == default) ? _now - y.Start : y.Stop.Value - y.Start; + return uptimeX.CompareTo(uptimeY); + } +} + +/// Sort by stop time (closed connections only). Mirrors Go SortByStop. +public sealed class SortByStop : IComparer +{ + public static readonly SortByStop Instance = new(); + public int Compare(ConnInfo? x, ConnInfo? y) + { + if (x is null || y is null) return 0; + // If either stop is null treat as zero (shouldn't happen for closed-only queries) + var stopX = x.Stop ?? DateTime.MinValue; + var stopY = y.Stop ?? DateTime.MinValue; + return stopX.CompareTo(stopY); + } +} + +/// Sort by close reason string. Mirrors Go SortByReason. +public sealed class SortByReason : IComparer +{ + public static readonly SortByReason Instance = new(); + public int Compare(ConnInfo? x, ConnInfo? y) + { + if (x is null || y is null) return 0; + return string.Compare(x.Reason, y.Reason, StringComparison.Ordinal); + } +} + +/// +/// Sort by round-trip time (nanoseconds, internal field). +/// Mirrors Go SortByRTT. +/// +public sealed class SortByRtt : IComparer +{ + public static readonly SortByRtt Instance = new(); + public int Compare(ConnInfo? x, ConnInfo? y) + { + if (x is null || y is null) return 0; + return x.RttNanos.CompareTo(y.RttNanos); + } +} diff --git a/dotnet/src/ZB.MOM.NatsNet.Server/Monitor/MonitorTypes.cs b/dotnet/src/ZB.MOM.NatsNet.Server/Monitor/MonitorTypes.cs new file mode 100644 index 0000000..0135ada --- /dev/null +++ b/dotnet/src/ZB.MOM.NatsNet.Server/Monitor/MonitorTypes.cs @@ -0,0 +1,387 @@ +// Copyright 2013-2026 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/monitor.go in the NATS server Go source. + +using System.Text.Json.Serialization; + +namespace ZB.MOM.NatsNet.Server; + +// ============================================================================ +// Monitor list size defaults +// Mirrors Go const block near top of monitor.go. +// ============================================================================ + +/// +/// Default sizes for monitoring API response lists. +/// Mirrors Go constants in server/monitor.go. +/// +public static class MonitorDefaults +{ + /// Default maximum number of connection entries returned. Mirrors Go DefaultConnListSize = 1024. + public const int DefaultConnListSize = 1024; + + /// Default maximum number of subscription entries returned. Mirrors Go DefaultSubListSize = 1024. + public const int DefaultSubListSize = 1024; +} + +// ============================================================================ +// ConnState — connection state filter for Connz queries +// Mirrors Go ConnState and its iota constants in monitor.go. +// ============================================================================ + +/// +/// Filter applied to connection-list queries to select open, closed, or +/// all connections. +/// Mirrors Go ConnState in server/monitor.go. +/// +public enum ConnState +{ + /// Only return open (active) connections. Mirrors Go ConnOpen = 0. + ConnOpen = 0, + + /// Only return closed connections. Mirrors Go ConnClosed. + ConnClosed = 1, + + /// Return all connections, open or closed. Mirrors Go ConnAll. + ConnAll = 2, +} + +// ============================================================================ +// ConnzOptions — query options for the Connz endpoint +// Mirrors Go ConnzOptions struct in server/monitor.go. +// ============================================================================ + +/// +/// Options that control the output of a Connz monitoring query. +/// Mirrors Go ConnzOptions struct in server/monitor.go. +/// +public sealed class ConnzOptions +{ + /// + /// How to sort results. Only ByCid is ascending; all others are + /// descending. Mirrors Go Sort SortOpt. + /// + [JsonPropertyName("sort")] + public SortOpt Sort { get; set; } = SortOpt.ByCid; + + /// When true, usernames are included in results. Mirrors Go Username bool. + [JsonPropertyName("auth")] + public bool Username { get; set; } + + /// When true, subscription subjects are listed. Mirrors Go Subscriptions bool. + [JsonPropertyName("subscriptions")] + public bool Subscriptions { get; set; } + + /// When true, verbose subscription detail is included. Mirrors Go SubscriptionsDetail bool. + [JsonPropertyName("subscriptions_detail")] + public bool SubscriptionsDetail { get; set; } + + /// Zero-based offset for pagination. Mirrors Go Offset int. + [JsonPropertyName("offset")] + public int Offset { get; set; } + + /// Maximum number of connections to return. Mirrors Go Limit int. + [JsonPropertyName("limit")] + public int Limit { get; set; } + + /// Filter for a specific client connection by CID. Mirrors Go CID uint64. + [JsonPropertyName("cid")] + public ulong Cid { get; set; } + + /// Filter for a specific MQTT client ID. Mirrors Go MQTTClient string. + [JsonPropertyName("mqtt_client")] + public string MqttClient { get; set; } = string.Empty; + + /// Connection state filter. Mirrors Go State ConnState. + [JsonPropertyName("state")] + public ConnState State { get; set; } = ConnState.ConnOpen; + + /// Filter by username. Mirrors Go User string. + [JsonPropertyName("user")] + public string User { get; set; } = string.Empty; + + /// Filter by account name. Mirrors Go Account string. + [JsonPropertyName("acc")] + public string Account { get; set; } = string.Empty; + + /// Filter by subject interest (requires Account filter). Mirrors Go FilterSubject string. + [JsonPropertyName("filter_subject")] + public string FilterSubject { get; set; } = string.Empty; +} + +// ============================================================================ +// Connz — top-level connection list monitoring response +// Mirrors Go Connz struct in server/monitor.go. +// ============================================================================ + +/// +/// Top-level response type for the /connz monitoring endpoint. +/// Contains the current connection list and pagination metadata. +/// Mirrors Go Connz struct in server/monitor.go. +/// +public sealed class Connz +{ + [JsonPropertyName("server_id")] + public string Id { get; set; } = string.Empty; + + [JsonPropertyName("now")] + public DateTime Now { get; set; } + + [JsonPropertyName("num_connections")] + public int NumConns { get; set; } + + [JsonPropertyName("total")] + public int Total { get; set; } + + [JsonPropertyName("offset")] + public int Offset { get; set; } + + [JsonPropertyName("limit")] + public int Limit { get; set; } + + [JsonPropertyName("connections")] + public List Conns { get; set; } = []; +} + +// ============================================================================ +// ConnInfo — per-connection detail record +// Mirrors Go ConnInfo struct in server/monitor.go. +// ============================================================================ + +/// +/// Detailed information about a single client connection, as returned by the +/// /connz monitoring endpoint. +/// Mirrors Go ConnInfo struct in server/monitor.go. +/// +public sealed class ConnInfo +{ + [JsonPropertyName("cid")] + public ulong Cid { get; set; } + + [JsonPropertyName("kind")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? Kind { get; set; } + + [JsonPropertyName("type")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? Type { get; set; } + + [JsonPropertyName("ip")] + public string Ip { get; set; } = string.Empty; + + [JsonPropertyName("port")] + public int Port { get; set; } + + [JsonPropertyName("start")] + public DateTime Start { get; set; } + + [JsonPropertyName("last_activity")] + public DateTime LastActivity { get; set; } + + [JsonPropertyName("stop")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public DateTime? Stop { get; set; } + + [JsonPropertyName("reason")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? Reason { get; set; } + + [JsonPropertyName("rtt")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? Rtt { get; set; } + + [JsonPropertyName("uptime")] + public string Uptime { get; set; } = string.Empty; + + [JsonPropertyName("idle")] + public string Idle { get; set; } = string.Empty; + + [JsonPropertyName("pending_bytes")] + public int Pending { get; set; } + + [JsonPropertyName("in_msgs")] + public long InMsgs { get; set; } + + [JsonPropertyName("out_msgs")] + public long OutMsgs { get; set; } + + [JsonPropertyName("in_bytes")] + public long InBytes { get; set; } + + [JsonPropertyName("out_bytes")] + public long OutBytes { get; set; } + + [JsonPropertyName("stalls")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public long Stalls { get; set; } + + [JsonPropertyName("subscriptions")] + public uint NumSubs { get; set; } + + [JsonPropertyName("name")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? Name { get; set; } + + [JsonPropertyName("lang")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? Lang { get; set; } + + [JsonPropertyName("version")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? Version { get; set; } + + [JsonPropertyName("tls_version")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? TlsVersion { get; set; } + + [JsonPropertyName("tls_cipher_suite")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? TlsCipher { get; set; } + + [JsonPropertyName("tls_peer_certs")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public List? TlsPeerCerts { get; set; } + + [JsonPropertyName("tls_first")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public bool TlsFirst { get; set; } + + [JsonPropertyName("authorized_user")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? AuthorizedUser { get; set; } + + [JsonPropertyName("account")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? Account { get; set; } + + [JsonPropertyName("subscriptions_list")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public List? Subs { get; set; } + + [JsonPropertyName("subscriptions_list_detail")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public List? SubsDetail { get; set; } + + [JsonPropertyName("jwt")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? Jwt { get; set; } + + [JsonPropertyName("issuer_key")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? IssuerKey { get; set; } + + [JsonPropertyName("name_tag")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? NameTag { get; set; } + + [JsonPropertyName("tags")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string[]? Tags { get; set; } + + [JsonPropertyName("mqtt_client")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? MqttClient { get; set; } + + [JsonPropertyName("proxy")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public ProxyInfo? Proxy { get; set; } + + /// + /// Internal field used for fast RTT-based sorting. + /// Mirrors Go rtt int64 unexported field in ConnInfo. + /// Not serialised. + /// + [JsonIgnore] + internal long RttNanos { get; set; } +} + +// ============================================================================ +// ProxyInfo — proxy connection metadata +// Mirrors Go ProxyInfo struct in server/monitor.go. +// ============================================================================ + +/// +/// Information about a proxied connection (e.g. HAProxy PROXY protocol). +/// Mirrors Go ProxyInfo struct in server/monitor.go. +/// +public sealed class ProxyInfo +{ + [JsonPropertyName("key")] + public string Key { get; set; } = string.Empty; +} + +// ============================================================================ +// TlsPeerCert — TLS peer certificate summary +// Mirrors Go TLSPeerCert struct in server/monitor.go. +// ============================================================================ + +/// +/// Basic information about a TLS peer certificate. +/// Mirrors Go TLSPeerCert struct in server/monitor.go. +/// +public sealed class TlsPeerCert +{ + [JsonPropertyName("subject")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? Subject { get; set; } + + [JsonPropertyName("spki_sha256")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? SubjectPkiSha256 { get; set; } + + [JsonPropertyName("cert_sha256")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? CertSha256 { get; set; } +} + +// ============================================================================ +// SubDetail — verbose subscription information +// Mirrors Go SubDetail struct in server/monitor.go (line ~961). +// ============================================================================ + +/// +/// Verbose information about a single subscription, included in detailed +/// connection or account monitoring responses. +/// Mirrors Go SubDetail struct in server/monitor.go. +/// +public sealed class SubDetail +{ + [JsonPropertyName("account")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? Account { get; set; } + + [JsonPropertyName("account_tag")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? AccountTag { get; set; } + + [JsonPropertyName("subject")] + public string Subject { get; set; } = string.Empty; + + [JsonPropertyName("qgroup")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? Queue { get; set; } + + [JsonPropertyName("sid")] + public string Sid { get; set; } = string.Empty; + + [JsonPropertyName("msgs")] + public long Msgs { get; set; } + + [JsonPropertyName("max")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public long Max { get; set; } + + [JsonPropertyName("cid")] + public ulong Cid { get; set; } +} diff --git a/dotnet/src/ZB.MOM.NatsNet.Server/NatsServer.cs b/dotnet/src/ZB.MOM.NatsNet.Server/NatsServer.cs index bcef42a..73b5dfe 100644 --- a/dotnet/src/ZB.MOM.NatsNet.Server/NatsServer.cs +++ b/dotnet/src/ZB.MOM.NatsNet.Server/NatsServer.cs @@ -57,7 +57,7 @@ public sealed partial class NatsServer : INatsServer private readonly ServerStats _stats = new(); private readonly SlowConsumerStats _scStats = new(); - private readonly StaleConnectionStats _staleStats = new(); + private readonly InternalStaleStats _staleStats = new(); // ========================================================================= // Core identity diff --git a/dotnet/src/ZB.MOM.NatsNet.Server/NatsServerTypes.cs b/dotnet/src/ZB.MOM.NatsNet.Server/NatsServerTypes.cs index 5f73d89..29daa35 100644 --- a/dotnet/src/ZB.MOM.NatsNet.Server/NatsServerTypes.cs +++ b/dotnet/src/ZB.MOM.NatsNet.Server/NatsServerTypes.cs @@ -122,10 +122,12 @@ internal sealed class SlowConsumerStats } /// -/// Per-kind stale-connection counters (atomic). +/// Per-kind stale-connection counters (atomic, internal use only). /// Mirrors Go embedded staleStats in server.go. +/// NOTE: The public JSON-serialisable monitoring equivalent is StaleConnectionStats +/// in Events/EventTypes.cs. /// -internal sealed class StaleConnectionStats +internal sealed class InternalStaleStats { public long Clients; public long Routes; @@ -204,12 +206,7 @@ public static class CompressionMode // These stubs will be replaced with full implementations in later sessions. // They are declared here to allow the NatsServer class to compile. -/// Stub for the system/internal messaging state (session 12). -internal sealed class InternalState -{ - public Account? Account { get; set; } - // Full implementation in session 12 (events.go) -} +// InternalState is now fully defined in Events/EventTypes.cs (session 12). /// Stub for JetStream state pointer (session 19). internal sealed class JetStreamState { } diff --git a/porting.db b/porting.db index 685e0db..22ef20a 100644 Binary files a/porting.db and b/porting.db differ diff --git a/reports/current.md b/reports/current.md index b456a35..3fd230f 100644 --- a/reports/current.md +++ b/reports/current.md @@ -1,6 +1,6 @@ # NATS .NET Porting Status Report -Generated: 2026-02-26 20:37:09 UTC +Generated: 2026-02-26 20:46:14 UTC ## Modules (12 total) @@ -13,9 +13,9 @@ Generated: 2026-02-26 20:37:09 UTC | Status | Count | |--------|-------| -| complete | 1075 | +| complete | 1382 | | n_a | 82 | -| not_started | 2423 | +| not_started | 2116 | | stub | 93 | ## Unit Tests (3257 total) @@ -36,4 +36,4 @@ Generated: 2026-02-26 20:37:09 UTC ## Overall Progress -**1668/6942 items complete (24.0%)** +**1975/6942 items complete (28.5%)** diff --git a/reports/report_12a14ec.md b/reports/report_12a14ec.md new file mode 100644 index 0000000..3fd230f --- /dev/null +++ b/reports/report_12a14ec.md @@ -0,0 +1,39 @@ +# NATS .NET Porting Status Report + +Generated: 2026-02-26 20:46:14 UTC + +## Modules (12 total) + +| Status | Count | +|--------|-------| +| complete | 11 | +| not_started | 1 | + +## Features (3673 total) + +| Status | Count | +|--------|-------| +| complete | 1382 | +| n_a | 82 | +| not_started | 2116 | +| stub | 93 | + +## Unit Tests (3257 total) + +| Status | Count | +|--------|-------| +| complete | 319 | +| n_a | 181 | +| not_started | 2533 | +| stub | 224 | + +## Library Mappings (36 total) + +| Status | Count | +|--------|-------| +| mapped | 36 | + + +## Overall Progress + +**1975/6942 items complete (28.5%)**