feat: port sessions 14-16 — Routes, Leaf Nodes & Gateways

Session 14 (57 features, IDs 2895-2951):
- RouteTypes: RouteType enum, Route, RouteInfo, ConnectInfo, ASubs, GossipMode

Session 15 (71 features, IDs 1979-2049):
- LeafNodeTypes: Leaf, LeafNodeCfg (replaces stub), LeafConnectInfo

Session 16 (91 features, IDs 1263-1353):
- GatewayTypes: GatewayInterestMode, SrvGateway (replaces stub), GatewayCfg,
  Gateway, OutSide, InSide, SitAlly, GwReplyMap, GwReplyMapping
This commit is contained in:
Joseph Doherty
2026-02-26 15:50:51 -05:00
parent ce45dff994
commit 77403e3d31
7 changed files with 816 additions and 11 deletions

View File

@@ -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
// ============================================================================
/// <summary>
/// Designates whether a route was explicitly configured or discovered via gossip.
/// Mirrors Go <c>RouteType</c> iota in route.go.
/// Note: Go defines Implicit=0, Explicit=1 — we keep TombStone=2 for future use.
/// </summary>
public enum RouteType : int
{
/// <summary>This route was learned from speaking to other routes.</summary>
Implicit = 0,
/// <summary>This route was explicitly configured.</summary>
Explicit = 1,
/// <summary>Reserved tombstone marker for removed routes.</summary>
TombStone = 2,
}
/// <summary>
/// Gossip mode constants exchanged between route servers.
/// Mirrors the const block immediately after <c>routeInfo</c> in route.go.
/// Do not change values — they are part of the wire protocol.
/// </summary>
internal static class GossipMode
{
public const byte Default = 0;
public const byte Disabled = 1;
public const byte Override = 2;
}
/// <summary>
/// Per-connection route state embedded in <see cref="ClientConnection"/> when the
/// connection kind is <c>Router</c>.
/// Mirrors Go <c>route</c> struct in route.go.
/// </summary>
internal sealed class Route
{
/// <summary>Remote server ID string.</summary>
public string RemoteId { get; set; } = string.Empty;
/// <summary>Remote server name.</summary>
public string RemoteName { get; set; } = string.Empty;
/// <summary>True if this server solicited the outbound connection.</summary>
public bool DidSolicit { get; set; }
/// <summary>True if the connection should be retried on failure.</summary>
public bool Retry { get; set; }
/// <summary>Leaf-node origin cluster flag (lnoc).</summary>
public bool Lnoc { get; set; }
/// <summary>Leaf-node origin cluster with unsub support (lnocu).</summary>
public bool Lnocu { get; set; }
/// <summary>Whether this is an explicit or implicit route.</summary>
public RouteType RouteType { get; set; }
/// <summary>Remote URL used to establish the connection.</summary>
public Uri? Url { get; set; }
/// <summary>True if the remote requires authentication.</summary>
public bool AuthRequired { get; set; }
/// <summary>True if the remote requires TLS.</summary>
public bool TlsRequired { get; set; }
/// <summary>True if JetStream is enabled on the remote.</summary>
public bool JetStream { get; set; }
/// <summary>List of client connect URLs advertised by the remote.</summary>
public List<string> ConnectUrls { get; set; } = [];
/// <summary>List of WebSocket connect URLs advertised by the remote.</summary>
public List<string> WsConnUrls { get; set; } = [];
/// <summary>Gateway URL advertised by the remote.</summary>
public string GatewayUrl { get; set; } = string.Empty;
/// <summary>Leaf-node URL advertised by the remote.</summary>
public string LeafnodeUrl { get; set; } = string.Empty;
/// <summary>Cluster hash used for routing.</summary>
public string Hash { get; set; } = string.Empty;
/// <summary>Server ID hash (6 bytes encoded).</summary>
public string IdHash { get; set; } = string.Empty;
/// <summary>
/// Index of this route in the <c>s.routes[remoteID]</c> slice.
/// Initialized to -1 to indicate the route has not yet been registered.
/// </summary>
public int PoolIdx { get; set; } = -1;
/// <summary>
/// When set, this route is pinned to a specific account and the account
/// name will not be included in routed protocols.
/// </summary>
public byte[]? AccName { get; set; }
/// <summary>True if this is a connection to an old server or one with pooling disabled.</summary>
public bool NoPool { get; set; }
/// <summary>
/// Selected compression mode, which may differ from the server-configured mode.
/// </summary>
public string Compression { get; set; } = string.Empty;
/// <summary>
/// Transient gossip mode byte sent when initiating an implicit route.
/// </summary>
public byte GossipMode { get; set; }
/// <summary>
/// When set in a pooling scenario, signals that the route should trigger
/// creation of the next pooled connection after receiving the first PONG.
/// </summary>
public RouteInfo? StartNewRoute { get; set; }
}
/// <summary>
/// Minimal descriptor used to create a new route connection, including
/// the target URL, its type, and gossip mode.
/// Mirrors Go <c>routeInfo</c> struct (the small inner type) in route.go.
/// </summary>
internal sealed class RouteInfo
{
public Uri? Url { get; set; }
public RouteType RouteType { get; set; }
public byte GossipMode { get; set; }
}
/// <summary>
/// CONNECT protocol payload exchanged between cluster servers.
/// Fields map 1-to-1 with the JSON tags in Go's <c>connectInfo</c>.
/// Mirrors Go <c>connectInfo</c> struct in route.go.
/// </summary>
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;
}
/// <summary>
/// Holds a set of subscriptions for a single account, used when fanning out
/// route subscription interest.
/// Mirrors Go <c>asubs</c> struct in route.go.
/// </summary>
internal sealed class ASubs
{
public Account? Account { get; set; }
public List<Internal.Subscription> Subs { get; set; } = [];
}