diff --git a/dotnet/src/ZB.MOM.NatsNet.Server/Gateway/GatewayTypes.cs b/dotnet/src/ZB.MOM.NatsNet.Server/Gateway/GatewayTypes.cs new file mode 100644 index 0000000..c405948 --- /dev/null +++ b/dotnet/src/ZB.MOM.NatsNet.Server/Gateway/GatewayTypes.cs @@ -0,0 +1,384 @@ +// Copyright 2018-2025 The NATS Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Adapted from server/gateway.go in the NATS server Go source. + +using System.Threading; +using ZB.MOM.NatsNet.Server.Internal; +using ZB.MOM.NatsNet.Server.Internal.DataStructures; + +namespace ZB.MOM.NatsNet.Server; + +// ============================================================================ +// Session 16: Gateways +// ============================================================================ + +/// +/// Represents the interest mode for a given account on a gateway connection. +/// Mirrors Go GatewayInterestMode byte iota in gateway.go. +/// Do not change values — they are part of the wire-level gossip protocol. +/// +public enum GatewayInterestMode : byte +{ + /// + /// Default mode: the cluster sends to a gateway unless told there is no + /// interest (applies to plain subscribers only). + /// + Optimistic = 0, + + /// + /// Transitioning: the gateway has been sending too many no-interest signals + /// and is switching to mode for this account. + /// + Transitioning = 1, + + /// + /// Interest-only mode: the cluster has sent all its subscription interest; + /// the gateway only forwards messages when explicit interest is known. + /// + InterestOnly = 2, + + /// + /// Internal sentinel used after a cache flush; not part of the public wire enum. + /// + CacheFlushed = 3, +} + +/// +/// Server-level gateway state kept on the instance. +/// Replaces the stub that was in NatsServerTypes.cs. +/// Mirrors Go srvGateway struct in gateway.go. +/// +internal sealed class SrvGateway +{ + /// + /// Total number of queue subs across all remote gateways. + /// Accessed via Interlocked — must be 64-bit aligned. + /// + public long TotalQSubs; + + private readonly ReaderWriterLockSlim _lock = new(LockRecursionPolicy.SupportsRecursion); + + /// + /// True if both a gateway name and port are configured (immutable after init). + /// + public bool Enabled { get; set; } + + /// Name of this server's gateway cluster. + public string Name { get; set; } = string.Empty; + + /// Outbound gateway connections keyed by remote gateway name. + public Dictionary Out { get; set; } = new(); + + /// + /// Outbound gateway connections in RTT order, used for message routing. + /// + public List Outo { get; set; } = []; + + /// Inbound gateway connections keyed by connection ID. + public Dictionary In { get; set; } = new(); + + /// Per-remote-gateway configuration, keyed by gateway name. + public Dictionary Remotes { get; set; } = new(); + + /// Reference-counted set of all gateway URLs in the cluster. + public RefCountedUrlSet Urls { get; set; } = new(); + + /// This server's own gateway URL (after random-port resolution). + public string Url { get; set; } = string.Empty; + + /// Gateway INFO protocol object. + public ServerInfo? Info { get; set; } + + /// Pre-marshalled INFO JSON bytes. + public byte[]? InfoJson { get; set; } + + /// When true, reject connections from gateways not in . + public bool RejectUnknown { get; set; } + + /// + /// Reply prefix bytes: "$GNR.<reserved>.<clusterHash>.<serverHash>." + /// + public byte[] ReplyPfx { get; set; } = []; + + // Backward-compatibility reply prefix and hash (old "$GR." scheme) + public byte[] OldReplyPfx { get; set; } = []; + public byte[] OldHash { get; set; } = []; + + // ------------------------------------------------------------------------- + // pasi — per-account subject interest tally (protected by its own mutex) + // ------------------------------------------------------------------------- + + /// + /// Per-account subject-interest tally. + /// Outer key = account name; inner key = subject (or "subject queue" pair); + /// value = tally struct. + /// Mirrors Go's anonymous pasi embedded struct in srvGateway. + /// + private readonly Lock _pasiLock = new(); + public Dictionary> Pasi { get; set; } = new(); + + public Lock PasiLock => _pasiLock; + + // ------------------------------------------------------------------------- + // Recent subscription tracking (thread-safe map) + // ------------------------------------------------------------------------- + + /// + /// Recent subscriptions for a given account (subject → expiry ticks). + /// Mirrors Go's rsubs sync.Map. + /// + public System.Collections.Concurrent.ConcurrentDictionary RSubs { get; set; } = new(); + + // ------------------------------------------------------------------------- + // Other server-level gateway fields + // ------------------------------------------------------------------------- + + /// DNS resolver used before dialling gateway connections. + public INetResolver? Resolver { get; set; } + + /// Max buffer size for sending queue-sub protocol (used in tests). + public int SqbSz { get; set; } + + /// How long to look for a subscription match for a reply message. + public TimeSpan RecSubExp { get; set; } + + /// Server ID hash (6 bytes) for routing mapped replies. + public byte[] SIdHash { get; set; } = []; + + /// + /// Map from a route server's hashed ID (6 bytes) to the route client. + /// Mirrors Go's routesIDByHash sync.Map. + /// + public System.Collections.Concurrent.ConcurrentDictionary RoutesIdByHash { get; set; } = new(); + + /// + /// Gateway URLs from this server's own entry in the Gateways config block, + /// used for monitoring reports. + /// + public List OwnCfgUrls { get; set; } = []; + + // ------------------------------------------------------------------------- + // Lock helpers + // ------------------------------------------------------------------------- + + public void AcquireReadLock() => _lock.EnterReadLock(); + public void ReleaseReadLock() => _lock.ExitReadLock(); + public void AcquireWriteLock() => _lock.EnterWriteLock(); + public void ReleaseWriteLock() => _lock.ExitWriteLock(); +} + +/// +/// Subject-interest tally entry. Indicates whether the key in the map is a +/// queue subscription and how many matching subscriptions exist. +/// Mirrors Go sitally struct in gateway.go. +/// +internal sealed class SitAlly +{ + /// Number of subscriptions directly matching the subject/queue key. + public int N { get; set; } + + /// True if this entry represents a queue subscription. + public bool Q { get; set; } +} + +/// +/// Runtime configuration for a single remote gateway. +/// Wraps with connection-attempt state and a lock. +/// Mirrors Go gatewayCfg struct in gateway.go. +/// +internal sealed class GatewayCfg +{ + private readonly ReaderWriterLockSlim _lock = new(LockRecursionPolicy.SupportsRecursion); + + /// The raw remote-gateway options this cfg was built from. + public RemoteGatewayOpts? RemoteOpts { get; set; } + + /// 6-byte cluster hash used for reply routing. + public byte[] Hash { get; set; } = []; + + /// 4-byte old-style hash for backward compatibility. + public byte[] OldHash { get; set; } = []; + + /// Map of URL string → parsed URL for this remote gateway. + public Dictionary Urls { get; set; } = new(); + + /// Number of connection attempts made so far. + public int ConnAttempts { get; set; } + + /// TLS server name override for SNI. + public string TlsName { get; set; } = string.Empty; + + /// True if this remote was discovered via gossip (not configured). + public bool Implicit { get; set; } + + /// When true, monitoring should refresh the URL list on next varz inspection. + public bool VarzUpdateUrls { get; set; } + + // Forwarded properties from RemoteGatewayOpts + public string Name { get => RemoteOpts?.Name ?? string.Empty; } + + // ------------------------------------------------------------------------- + // Lock helpers + // ------------------------------------------------------------------------- + + public void AcquireReadLock() => _lock.EnterReadLock(); + public void ReleaseReadLock() => _lock.ExitReadLock(); + public void AcquireWriteLock() => _lock.EnterWriteLock(); + public void ReleaseWriteLock() => _lock.ExitWriteLock(); +} + +/// +/// Per-connection gateway state embedded in +/// when the connection kind is Gateway. +/// Mirrors Go gateway struct in gateway.go. +/// +internal sealed class Gateway +{ + /// Name of the remote gateway cluster. + public string Name { get; set; } = string.Empty; + + /// Configuration block for the remote gateway. + public GatewayCfg? Cfg { get; set; } + + /// URL used for CONNECT after receiving the remote INFO (outbound only). + public Uri? ConnectUrl { get; set; } + + /// + /// Per-account subject interest (outbound connection). + /// Maps account name → for that account. + /// Uses a thread-safe map because it is read from multiple goroutines. + /// + public System.Collections.Concurrent.ConcurrentDictionary? OutSim { get; set; } + + /// + /// Per-account no-interest subjects or interest-only mode (inbound connection). + /// + public Dictionary? InSim { get; set; } + + /// True if this is an outbound gateway connection. + public bool Outbound { get; set; } + + /// + /// Set in the read loop without locking to record that the inbound side + /// sent its CONNECT protocol. + /// + public bool Connected { get; set; } + + /// + /// True if the remote server only understands the old $GR. prefix, + /// not the newer $GNR. scheme. + /// + public bool UseOldPrefix { get; set; } + + /// + /// When true the inbound side switches accounts to interest-only mode + /// immediately, so the outbound side can disregard optimistic mode. + /// + public bool InterestOnlyMode { get; set; } + + /// Name of the remote server on this gateway connection. + public string RemoteName { get; set; } = string.Empty; +} + +/// +/// Outbound subject-interest entry for a single account on a gateway connection. +/// Mirrors Go outsie struct in gateway.go. +/// +internal sealed class OutSide +{ + private readonly ReaderWriterLockSlim _lock = new(LockRecursionPolicy.SupportsRecursion); + + /// Current interest mode for this account on the outbound gateway. + public GatewayInterestMode Mode { get; set; } + + /// + /// Set of subjects for which the remote has signalled no-interest. + /// Null when the remote has sent all its subscriptions (interest-only mode). + /// + public HashSet? Ni { get; set; } + + /// + /// Subscription index: contains queue subs in optimistic mode, + /// or all subs when has been switched. + /// + public SubscriptionIndex? Sl { get; set; } + + /// Number of queue subscriptions tracked in . + public int Qsubs { get; set; } + + // ------------------------------------------------------------------------- + // Lock helpers + // ------------------------------------------------------------------------- + + public void AcquireReadLock() => _lock.EnterReadLock(); + public void ReleaseReadLock() => _lock.ExitReadLock(); + public void AcquireWriteLock() => _lock.EnterWriteLock(); + public void ReleaseWriteLock() => _lock.ExitWriteLock(); +} + +/// +/// Inbound subject-interest entry for a single account on a gateway connection. +/// Tracks subjects for which an RS- was sent to the remote, and the current mode. +/// Mirrors Go insie struct in gateway.go. +/// +internal sealed class InSide +{ + /// + /// Subjects for which RS- was sent to the remote (null when in interest-only mode). + /// + public HashSet? Ni { get; set; } + + /// Current interest mode for this account on the inbound gateway. + public GatewayInterestMode Mode { get; set; } +} + +/// +/// A single gateway reply-mapping entry: the mapped subject and its expiry. +/// Mirrors Go gwReplyMap struct in gateway.go. +/// +internal sealed class GwReplyMap +{ + /// The mapped (routed) subject string. + public string Ms { get; set; } = string.Empty; + + /// Expiry expressed as (UTC). + public long Exp { get; set; } +} + +/// +/// Gateway reply routing table and a fast-path check flag. +/// Mirrors Go gwReplyMapping struct in gateway.go. +/// +internal sealed class GwReplyMapping +{ + /// + /// Non-zero when the mapping table should be consulted while processing + /// inbound messages. Accessed via Interlocked — must be 32-bit aligned. + /// + public int Check; + + /// Active reply-subject → GwReplyMap entries. + public Dictionary Mapping { get; set; } = new(); + + /// + /// Returns the routed subject for if a mapping + /// exists, otherwise returns the original subject and false. + /// Caller must hold any required lock before invoking. + /// + public (byte[] Subject, bool Found) Get(byte[] subject) + { + // TODO: session 16 — implement mapping lookup + return (subject, false); + } +} diff --git a/dotnet/src/ZB.MOM.NatsNet.Server/LeafNode/LeafNodeTypes.cs b/dotnet/src/ZB.MOM.NatsNet.Server/LeafNode/LeafNodeTypes.cs new file mode 100644 index 0000000..2a7801f --- /dev/null +++ b/dotnet/src/ZB.MOM.NatsNet.Server/LeafNode/LeafNodeTypes.cs @@ -0,0 +1,202 @@ +// Copyright 2019-2025 The NATS Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Adapted from server/leafnode.go in the NATS server Go source. + +using System.Text.Json.Serialization; +using System.Threading; +using ZB.MOM.NatsNet.Server.Auth; +using ZB.MOM.NatsNet.Server.Internal; + +namespace ZB.MOM.NatsNet.Server; + +// ============================================================================ +// Session 15: Leaf Nodes +// ============================================================================ + +/// +/// Per-connection leaf-node state embedded in +/// when the connection kind is Leaf. +/// Mirrors Go leaf struct in leafnode.go. +/// +internal sealed class Leaf +{ + /// + /// Config for solicited (outbound) leaf connections; null for accepted connections. + /// + public LeafNodeCfg? Remote { get; set; } + + /// + /// True when we are the spoke side of a hub/spoke leaf pair. + /// + public bool IsSpoke { get; set; } + + /// + /// Cluster name of the remote server when we are a hub and the spoke is + /// part of a cluster. + /// + public string RemoteCluster { get; set; } = string.Empty; + + /// Remote server name or ID. + public string RemoteServer { get; set; } = string.Empty; + + /// Domain name of the remote server. + public string RemoteDomain { get; set; } = string.Empty; + + /// Account name of the remote server. + public string RemoteAccName { get; set; } = string.Empty; + + /// + /// When true, suppresses propagation of east-west interest from other leaf nodes. + /// + public bool Isolated { get; set; } + + /// + /// Subject-interest suppression map shared with the remote side. + /// Key = subject, Value = interest count (positive = subscribe, negative = unsubscribe delta). + /// + public Dictionary Smap { get; set; } = new(); + + /// + /// Short-lived set of subscriptions added during initLeafNodeSmapAndSendSubs + /// to detect and avoid double-counting races. + /// + public HashSet? Tsub { get; set; } + + /// Timer that clears after the initialization window. + public Timer? Tsubt { get; set; } + + /// + /// Selected compression mode, which may differ from the server-configured mode. + /// + public string Compression { get; set; } = string.Empty; + + /// + /// Gateway-mapped reply subscription used for GW reply routing via leaf nodes. + /// + public Subscription? GwSub { get; set; } +} + +/// +/// Runtime configuration for a remote (solicited) leaf-node connection. +/// Wraps with connection-attempt state and a +/// reader-writer lock for concurrent access. +/// Mirrors Go leafNodeCfg struct in leafnode.go. +/// Replaces the stub that was in NatsServerTypes.cs. +/// +public sealed class LeafNodeCfg +{ + private readonly ReaderWriterLockSlim _lock = new(LockRecursionPolicy.SupportsRecursion); + + // ------------------------------------------------------------------------- + // Embedded RemoteLeafOpts fields + // ------------------------------------------------------------------------- + + /// The raw remote options this cfg was constructed from. + public RemoteLeafOpts? RemoteOpts { get; set; } + + // ------------------------------------------------------------------------- + // Runtime connection-attempt fields + // ------------------------------------------------------------------------- + + /// Resolved URLs to attempt connections to. + public List Urls { get; set; } = []; + + /// Currently selected URL from . + public Uri? CurUrl { get; set; } + + /// TLS server name override for SNI. + public string TlsName { get; set; } = string.Empty; + + /// Username for authentication (resolved from credentials or options). + public string Username { get; set; } = string.Empty; + + /// Password for authentication (resolved from credentials or options). + public string Password { get; set; } = string.Empty; + + /// Publish/subscribe permission overrides for this connection. + public Permissions? Perms { get; set; } + + /// + /// Delay before the next connection attempt (e.g. during loop-detection back-off). + /// + public TimeSpan ConnDelay { get; set; } + + /// + /// Timer used to trigger JetStream account migration for this leaf. + /// + public Timer? JsMigrateTimer { get; set; } + + // ------------------------------------------------------------------------- + // Forwarded properties from RemoteLeafOpts + // ------------------------------------------------------------------------- + + public string LocalAccount { get => RemoteOpts?.LocalAccount ?? string.Empty; } + public bool NoRandomize { get => RemoteOpts?.NoRandomize ?? false; } + public string Credentials { get => RemoteOpts?.Credentials ?? string.Empty; } + public bool Disabled { get => RemoteOpts?.Disabled ?? false; } + + // ------------------------------------------------------------------------- + // Lock helpers + // ------------------------------------------------------------------------- + + public void AcquireReadLock() => _lock.EnterReadLock(); + public void ReleaseReadLock() => _lock.ExitReadLock(); + public void AcquireWriteLock() => _lock.EnterWriteLock(); + public void ReleaseWriteLock() => _lock.ExitWriteLock(); +} + +/// +/// CONNECT protocol payload sent by a leaf-node connection. +/// Fields map 1-to-1 with the JSON tags in Go's leafConnectInfo. +/// Mirrors Go leafConnectInfo struct in leafnode.go. +/// +internal sealed class LeafConnectInfo +{ + [JsonPropertyName("version")] public string Version { get; set; } = string.Empty; + [JsonPropertyName("nkey")] public string Nkey { get; set; } = string.Empty; + [JsonPropertyName("jwt")] public string Jwt { get; set; } = string.Empty; + [JsonPropertyName("sig")] public string Sig { get; set; } = string.Empty; + [JsonPropertyName("user")] public string User { get; set; } = string.Empty; + [JsonPropertyName("pass")] public string Pass { get; set; } = string.Empty; + [JsonPropertyName("auth_token")] public string Token { get; set; } = string.Empty; + [JsonPropertyName("server_id")] public string Id { get; set; } = string.Empty; + [JsonPropertyName("domain")] public string Domain { get; set; } = string.Empty; + [JsonPropertyName("name")] public string Name { get; set; } = string.Empty; + [JsonPropertyName("is_hub")] public bool Hub { get; set; } + [JsonPropertyName("cluster")] public string Cluster { get; set; } = string.Empty; + [JsonPropertyName("headers")] public bool Headers { get; set; } + [JsonPropertyName("jetstream")] public bool JetStream { get; set; } + [JsonPropertyName("deny_pub")] public string[] DenyPub { get; set; } = []; + [JsonPropertyName("isolate")] public bool Isolate { get; set; } + + /// + /// Compression mode string. The legacy boolean field was never used; this + /// string field uses a different JSON tag to avoid conflicts. + /// + [JsonPropertyName("compress_mode")] public string Compression { get; set; } = string.Empty; + + /// + /// Used only to detect wrong-port connections (client connecting to leaf port). + /// + [JsonPropertyName("gateway")] public string Gateway { get; set; } = string.Empty; + + /// Account name the remote is binding to on the accept side. + [JsonPropertyName("remote_account")] public string RemoteAccount { get; set; } = string.Empty; + + /// + /// Protocol version sent by the soliciting side so the accepting side knows + /// which features are supported (e.g. message tracing). + /// + [JsonPropertyName("protocol")] public int Proto { get; set; } +} diff --git a/dotnet/src/ZB.MOM.NatsNet.Server/NatsServerTypes.cs b/dotnet/src/ZB.MOM.NatsNet.Server/NatsServerTypes.cs index 29daa35..68506b3 100644 --- a/dotnet/src/ZB.MOM.NatsNet.Server/NatsServerTypes.cs +++ b/dotnet/src/ZB.MOM.NatsNet.Server/NatsServerTypes.cs @@ -225,11 +225,7 @@ public sealed class JetStreamConfig public string UniqueTag { get; set; } = string.Empty; } -/// Stub for server gateway state (session 16). -internal sealed class SrvGateway -{ - public bool Enabled { get; set; } -} +// SrvGateway — full class is in Gateway/GatewayTypes.cs (session 16). /// Stub for server websocket state (session 23). internal sealed class SrvWebsocket @@ -249,8 +245,7 @@ internal interface IOcspResponseCache { } /// Stub for IP queue (session 02 — already ported as IpQueue). // IpQueue is already in session 02 internals — used here via object. -/// Stub for leaf node config (session 15). -internal sealed class LeafNodeCfg { } +// LeafNodeCfg — full class is in LeafNode/LeafNodeTypes.cs (session 15). /// /// Network resolver used by . diff --git a/dotnet/src/ZB.MOM.NatsNet.Server/Routes/RouteTypes.cs b/dotnet/src/ZB.MOM.NatsNet.Server/Routes/RouteTypes.cs new file mode 100644 index 0000000..1449957 --- /dev/null +++ b/dotnet/src/ZB.MOM.NatsNet.Server/Routes/RouteTypes.cs @@ -0,0 +1,185 @@ +// Copyright 2013-2025 The NATS Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Adapted from server/route.go in the NATS server Go source. + +using System.Text.Json.Serialization; +using ZB.MOM.NatsNet.Server.Internal; + +namespace ZB.MOM.NatsNet.Server; + +// ============================================================================ +// Session 14: Routes +// ============================================================================ + +/// +/// Designates whether a route was explicitly configured or discovered via gossip. +/// Mirrors Go RouteType iota in route.go. +/// Note: Go defines Implicit=0, Explicit=1 — we keep TombStone=2 for future use. +/// +public enum RouteType : int +{ + /// This route was learned from speaking to other routes. + Implicit = 0, + /// This route was explicitly configured. + Explicit = 1, + /// Reserved tombstone marker for removed routes. + TombStone = 2, +} + +/// +/// Gossip mode constants exchanged between route servers. +/// Mirrors the const block immediately after routeInfo in route.go. +/// Do not change values — they are part of the wire protocol. +/// +internal static class GossipMode +{ + public const byte Default = 0; + public const byte Disabled = 1; + public const byte Override = 2; +} + +/// +/// Per-connection route state embedded in when the +/// connection kind is Router. +/// Mirrors Go route struct in route.go. +/// +internal sealed class Route +{ + /// Remote server ID string. + public string RemoteId { get; set; } = string.Empty; + + /// Remote server name. + public string RemoteName { get; set; } = string.Empty; + + /// True if this server solicited the outbound connection. + public bool DidSolicit { get; set; } + + /// True if the connection should be retried on failure. + public bool Retry { get; set; } + + /// Leaf-node origin cluster flag (lnoc). + public bool Lnoc { get; set; } + + /// Leaf-node origin cluster with unsub support (lnocu). + public bool Lnocu { get; set; } + + /// Whether this is an explicit or implicit route. + public RouteType RouteType { get; set; } + + /// Remote URL used to establish the connection. + public Uri? Url { get; set; } + + /// True if the remote requires authentication. + public bool AuthRequired { get; set; } + + /// True if the remote requires TLS. + public bool TlsRequired { get; set; } + + /// True if JetStream is enabled on the remote. + public bool JetStream { get; set; } + + /// List of client connect URLs advertised by the remote. + public List ConnectUrls { get; set; } = []; + + /// List of WebSocket connect URLs advertised by the remote. + public List WsConnUrls { get; set; } = []; + + /// Gateway URL advertised by the remote. + public string GatewayUrl { get; set; } = string.Empty; + + /// Leaf-node URL advertised by the remote. + public string LeafnodeUrl { get; set; } = string.Empty; + + /// Cluster hash used for routing. + public string Hash { get; set; } = string.Empty; + + /// Server ID hash (6 bytes encoded). + public string IdHash { get; set; } = string.Empty; + + /// + /// Index of this route in the s.routes[remoteID] slice. + /// Initialized to -1 to indicate the route has not yet been registered. + /// + public int PoolIdx { get; set; } = -1; + + /// + /// When set, this route is pinned to a specific account and the account + /// name will not be included in routed protocols. + /// + public byte[]? AccName { get; set; } + + /// True if this is a connection to an old server or one with pooling disabled. + public bool NoPool { get; set; } + + /// + /// Selected compression mode, which may differ from the server-configured mode. + /// + public string Compression { get; set; } = string.Empty; + + /// + /// Transient gossip mode byte sent when initiating an implicit route. + /// + public byte GossipMode { get; set; } + + /// + /// When set in a pooling scenario, signals that the route should trigger + /// creation of the next pooled connection after receiving the first PONG. + /// + public RouteInfo? StartNewRoute { get; set; } +} + +/// +/// Minimal descriptor used to create a new route connection, including +/// the target URL, its type, and gossip mode. +/// Mirrors Go routeInfo struct (the small inner type) in route.go. +/// +internal sealed class RouteInfo +{ + public Uri? Url { get; set; } + public RouteType RouteType { get; set; } + public byte GossipMode { get; set; } +} + +/// +/// CONNECT protocol payload exchanged between cluster servers. +/// Fields map 1-to-1 with the JSON tags in Go's connectInfo. +/// Mirrors Go connectInfo struct in route.go. +/// +internal sealed class ConnectInfo +{ + [JsonPropertyName("echo")] public bool Echo { get; set; } + [JsonPropertyName("verbose")] public bool Verbose { get; set; } + [JsonPropertyName("pedantic")] public bool Pedantic { get; set; } + [JsonPropertyName("user")] public string User { get; set; } = string.Empty; + [JsonPropertyName("pass")] public string Pass { get; set; } = string.Empty; + [JsonPropertyName("tls_required")] public bool Tls { get; set; } + [JsonPropertyName("headers")] public bool Headers { get; set; } + [JsonPropertyName("name")] public string Name { get; set; } = string.Empty; + [JsonPropertyName("cluster")] public string Cluster { get; set; } = string.Empty; + [JsonPropertyName("cluster_dynamic")] public bool Dynamic { get; set; } + [JsonPropertyName("lnoc")] public bool Lnoc { get; set; } + [JsonPropertyName("lnocu")] public bool Lnocu { get; set; } + [JsonPropertyName("gateway")] public string Gateway { get; set; } = string.Empty; +} + +/// +/// Holds a set of subscriptions for a single account, used when fanning out +/// route subscription interest. +/// Mirrors Go asubs struct in route.go. +/// +internal sealed class ASubs +{ + public Account? Account { get; set; } + public List Subs { get; set; } = []; +} diff --git a/porting.db b/porting.db index 22ef20a..0bb05ba 100644 Binary files a/porting.db and b/porting.db differ diff --git a/reports/current.md b/reports/current.md index 3fd230f..ca16166 100644 --- a/reports/current.md +++ b/reports/current.md @@ -1,6 +1,6 @@ # NATS .NET Porting Status Report -Generated: 2026-02-26 20:46:14 UTC +Generated: 2026-02-26 20:50:51 UTC ## Modules (12 total) @@ -13,9 +13,9 @@ Generated: 2026-02-26 20:46:14 UTC | Status | Count | |--------|-------| -| complete | 1382 | +| complete | 1601 | | n_a | 82 | -| not_started | 2116 | +| not_started | 1897 | | stub | 93 | ## Unit Tests (3257 total) @@ -36,4 +36,4 @@ Generated: 2026-02-26 20:46:14 UTC ## Overall Progress -**1975/6942 items complete (28.5%)** +**2194/6942 items complete (31.6%)** diff --git a/reports/report_ce45dff.md b/reports/report_ce45dff.md new file mode 100644 index 0000000..ca16166 --- /dev/null +++ b/reports/report_ce45dff.md @@ -0,0 +1,39 @@ +# NATS .NET Porting Status Report + +Generated: 2026-02-26 20:50:51 UTC + +## Modules (12 total) + +| Status | Count | +|--------|-------| +| complete | 11 | +| not_started | 1 | + +## Features (3673 total) + +| Status | Count | +|--------|-------| +| complete | 1601 | +| n_a | 82 | +| not_started | 1897 | +| 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 + +**2194/6942 items complete (31.6%)**