18 KiB
18 KiB
Routes — Gap Analysis
This file tracks what has and hasn't been ported from Go to .NET for the Routes module. See stillmissing.md for the full LOC comparison across all modules.
LLM Instructions: How to Analyze This Category
Step 1: Read the Go Reference Files
Read each Go source file listed below. For every file:
- Extract all exported types (structs, interfaces, type aliases)
- Extract all exported methods on those types (receiver functions)
- Extract all exported standalone functions
- Note key constants, enums, and protocol states
- Note important unexported helpers that implement core logic (functions >20 lines)
- Pay attention to concurrency patterns (goroutines, mutexes, channels) — these map to different .NET patterns
Step 2: Read the .NET Implementation Files
Read all .cs files in the .NET directories listed below. For each Go symbol found in Step 1:
- Search for a matching type, method, or function in .NET
- If found, compare the behavior: does it handle the same edge cases? Same error paths?
- If partially implemented, note what's missing
- If not found, note it as MISSING
Step 3: Cross-Reference Tests
Compare Go test functions against .NET test methods:
- For each Go
Test*function, check if a corresponding .NET[Fact]or[Theory]exists - Note which test scenarios are covered and which are missing
- Check the parity DB (
docs/test_parity.db) for existing mappings:sqlite3 docs/test_parity.db "SELECT go_test, dotnet_test, confidence FROM test_mappings tm JOIN go_tests gt ON tm.go_test_id=gt.rowid JOIN dotnet_tests dt ON tm.dotnet_test_id=dt.rowid WHERE gt.go_file LIKE '%PATTERN%'"
Step 4: Classify Each Item
Use these status values:
| Status | Meaning |
|---|---|
| PORTED | Equivalent exists in .NET with matching behavior |
| PARTIAL | .NET implementation exists but is incomplete (missing edge cases, error handling, or features) |
| MISSING | No .NET equivalent found — needs to be ported |
| NOT_APPLICABLE | Go-specific pattern that doesn't apply to .NET (build tags, platform-specific goroutine tricks, etc.) |
| DEFERRED | Intentionally skipped for now (document why) |
Step 5: Fill In the Gap Inventory
Add rows to the Gap Inventory table below. Group by Go source file. Include the Go file and line number so a porting LLM can jump directly to the reference implementation.
Key Porting Notes for Routes
- Route connections use the same
client.goread/write loop but withClientKind = ROUTER. - Route pooling sends different accounts over different connections to avoid head-of-line blocking.
RS+/RS-are subscription interest propagation messages between clustered servers.RMSGis the routed message format (differs from clientMSG).
Go Reference Files (Source)
golang/nats-server/server/route.go— Full-mesh cluster routes (~3,300 lines). Route pooling (default 3 connections per peer). Account-specific dedicated routes. Protocol:RS+/RS-for subscribe propagation,RMSGfor routed messages.
Go Reference Files (Tests)
golang/nats-server/server/routes_test.gogolang/nats-server/test/routes_test.go(integration)golang/nats-server/test/new_routes_test.go(integration)
.NET Implementation Files (Source)
src/NATS.Server/Routes/(all files)
.NET Implementation Files (Tests)
tests/NATS.Server.Tests/Routes/
Gap Inventory
golang/nats-server/server/route.go
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|---|---|---|---|---|
RouteType (type alias + consts Implicit/Explicit) |
route.go:36–44 | PORTED | src/NATS.Server/Routes/RouteConnection.cs — implicit/explicit distinction tracked in RouteConnection handshake and RouteManager.ConnectToRouteWithRetryAsync |
No explicit enum; Implicit/Explicit distinction is encoded in how routes are established (solicited vs inbound) |
route struct (unexported) |
route.go:56–94 | PARTIAL | src/NATS.Server/Routes/RouteConnection.cs:8 |
Fields remoteID, poolIdx, accName, noPool, compression, gossipMode are present. Fields for lnoc, lnocu, jetstream, connectURLs, wsConnURLs, gatewayURL, leafnodeURL, hash, idHash, startNewRoute, retry are MISSING — not modelled in .NET |
routeInfo struct (unexported) |
route.go:97–101 | MISSING | — | Used internally for deferred pool-connection creation after first PONG; no .NET equivalent |
gossipDefault/gossipDisabled/gossipOverride consts |
route.go:104–108 | MISSING | — | Gossip mode bytes used in INFO propagation; not implemented in .NET |
connectInfo struct |
route.go:110–124 | PARTIAL | src/NATS.Server/Routes/RouteConnection.cs:328 (BuildConnectInfoJson) |
.NET builds a simplified JSON payload; connectInfo fields Echo, Verbose, Pedantic, TLS, Headers, Cluster, Dynamic, LNOC, LNOCU are all MISSING from the .NET payload |
ConProto/InfoProto protocol format strings |
route.go:127–130 | PARTIAL | src/NATS.Server/Routes/RouteConnection.cs:73,83 |
Handshake uses a simplified ROUTE <serverId> format rather than CONNECT <json> / INFO <json> |
clusterTLSInsecureWarning const |
route.go:134 | NOT_APPLICABLE | — | TLS not yet implemented in .NET port; warning string has no counterpart |
defaultRouteMaxPingInterval const |
route.go:140 | MISSING | — | Ping interval management for compression RTT auto-mode not implemented |
routeConnectDelay/routeConnectMaxDelay/routeMaxPingInterval vars |
route.go:145–148 | PARTIAL | src/NATS.Server/Routes/RouteManager.cs:486 (250ms hardcoded delay) |
.NET hardcodes 250ms retry delay; Go uses configurable DEFAULT_ROUTE_CONNECT with exponential backoff |
(c *client) removeReplySub |
route.go:151 | MISSING | — | Reply-sub cleanup for remote reply subs not implemented |
(c *client) processAccountSub |
route.go:167 | NOT_APPLICABLE | — | Gateway-only path; gateway sub interest not in routes module |
(c *client) processAccountUnsub |
route.go:174 | NOT_APPLICABLE | — | Gateway-only path |
(c *client) processRoutedOriginClusterMsgArgs |
route.go:182 | MISSING | — | LMSG (origin-cluster routed msg) arg parsing not implemented; .NET only handles RMSG |
(c *client) processRoutedHeaderMsgArgs |
route.go:281 | MISSING | — | HMSG (header msg from route) arg parsing not implemented |
(c *client) processRoutedMsgArgs |
route.go:378 | PARTIAL | src/NATS.Server/Routes/RouteConnection.cs:187–222 (ReadFramesAsync) |
.NET parses basic RMSG account/subject/reply/size. Missing: queue-group routing indicators (+/` |
(c *client) processInboundRoutedMsg |
route.go:460 | PARTIAL | src/NATS.Server/Routes/RouteConnection.cs:219–221 |
.NET fires RoutedMessageReceived callback; missing: stats update, gateway reply handling (handleGatewayReply), account lookup and fanout via processMsgResults |
(c *client) sendRouteConnect |
route.go:503 | MISSING | — | Outbound CONNECT protocol (with cluster auth, TLS flags, etc.) not sent; .NET uses a simpler ROUTE <serverId> handshake |
computeRoutePoolIdx |
route.go:538 | PORTED | src/NATS.Server/Routes/RouteManager.cs:149 (ComputeRoutePoolIdx) |
FNV-1a 32-bit hash, identical algorithm |
(c *client) processRouteInfo |
route.go:549 | MISSING | — | Full INFO processing (cluster name negotiation, compression negotiation, duplicate detection, account route setup) not implemented; .NET handshake is a simple ID exchange |
(s *Server) negotiateRouteCompression |
route.go:897 | PARTIAL | src/NATS.Server/Routes/RouteCompressionCodec.cs:82 (NegotiateCompression) |
.NET has the negotiation logic; but integration into handshake (INFO exchange, switching compression writer/reader mid-stream) is MISSING |
(s *Server) updateRemoteRoutePerms |
route.go:953 | MISSING | — | Route permission update on INFO reload not implemented |
(s *Server) sendAsyncInfoToClients |
route.go:1015 | MISSING | — | Async INFO broadcast to connected clients not implemented |
(s *Server) processImplicitRoute |
route.go:1043 | PARTIAL | src/NATS.Server/Routes/RouteManager.cs:107 (ProcessImplicitRoute) |
.NET collects discovered URLs; missing: duplicate-ID check, pinned-account re-solicitation, hasThisRouteConfigured guard |
(s *Server) hasThisRouteConfigured |
route.go:1104 | MISSING | — | Check whether incoming gossip URL is already a configured explicit route; not implemented |
(s *Server) forwardNewRouteInfoToKnownServers |
route.go:1139 | PARTIAL | src/NATS.Server/Routes/RouteManager.cs:127 (ForwardNewRouteInfoToKnownServers) |
.NET raises an event with the new peer URL; missing: gossip mode logic (gossipDefault/gossipDisabled/gossipOverride), pinned-account route filtering, serialized INFO JSON sending |
(c *client) canImport |
route.go:1226 | MISSING | — | Route import permission check not implemented |
(c *client) canExport |
route.go:1235 | MISSING | — | Route export permission check not implemented |
(c *client) setRoutePermissions |
route.go:1244 | MISSING | — | Route permission mapping (Import→Publish, Export→Subscribe) not implemented |
asubs struct |
route.go:1263 | NOT_APPLICABLE | — | Internal Go helper to group subscriptions by account during cleanup; .NET uses LINQ equivalents |
getAccNameFromRoutedSubKey |
route.go:1273 | MISSING | — | Sub key parsing for account name extraction not implemented |
(c *client) getRoutedSubKeyInfo |
route.go:1290 | MISSING | — | Helper to determine account/key info for a route's subscriptions; not implemented |
(c *client) removeRemoteSubs |
route.go:1299 | PARTIAL | src/NATS.Server/Routes/RouteManager.cs:577 (RemoveRoute) |
.NET removes the route connection but does NOT remove individual remote subscriptions from the SubList on close |
(c *client) removeRemoteSubsForAcc |
route.go:1352 | MISSING | — | Per-account remote sub removal for dedicated route transition not implemented |
(c *client) parseUnsubProto |
route.go:1366 | MISSING | — | RS-/LS- protocol arg parser not implemented; .NET ReadFramesAsync only extracts account/subject/queue loosely |
(c *client) processRemoteUnsub |
route.go:1404 | PARTIAL | src/NATS.Server/Routes/RouteConnection.cs:177–185 |
.NET fires RemoteSubscriptionReceived with IsRemoval=true; missing: sub key lookup and removal from SubList, gateway/leafnode interest updates |
(c *client) processRemoteSub |
route.go:1489 | PARTIAL | src/NATS.Server/Routes/RouteConnection.cs:167–175 |
.NET fires RemoteSubscriptionReceived; missing: key construction with type byte prefix, account lookup/creation, permission check (canExport), SubList insertion, gateway/leafnode updates, queue-weight delta tracking |
(c *client) addRouteSubOrUnsubProtoToBuf |
route.go:1729 | PARTIAL | src/NATS.Server/Routes/RouteConnection.cs:95–109 (SendRsPlusAsync/SendRsMinusAsync) |
.NET sends RS+/RS- with account and optional queue; missing: LS+/LS- variant for leaf origin clusters, queue weight field in RS+ |
(s *Server) sendSubsToRoute |
route.go:1781 | MISSING | — | Bulk send of local subscription interest to newly connected route not implemented; .NET only propagates incremental sub/unsub |
(c *client) sendRouteSubProtos |
route.go:1881 | MISSING | — | Batch RS+ send not implemented |
(c *client) sendRouteUnSubProtos |
route.go:1890 | MISSING | — | Batch RS- send not implemented |
(c *client) sendRouteSubOrUnSubProtos |
route.go:1898 | MISSING | — | Low-level batch RS+/RS-/LS+/LS- sender not implemented |
(s *Server) createRoute |
route.go:1935 | PARTIAL | src/NATS.Server/Routes/RouteManager.cs:447,462 (HandleInboundRouteAsync/ConnectToRouteWithRetryAsync) |
.NET creates a RouteConnection and performs handshake; missing: TLS setup, auth timeout timer, CONNECT protocol sending, INFO JSON sending, compression negotiation, ping timer |
routeShouldDelayInfo |
route.go:2082 | MISSING | — | Logic to delay initial INFO until pool connection auth is confirmed not implemented |
(s *Server) generateRouteInitialInfoJSON |
route.go:2090 | MISSING | — | Route INFO JSON generation (with nonce, pool index, gossip mode, compression) not implemented |
(s *Server) addRoute |
route.go:2113 | PARTIAL | src/NATS.Server/Routes/RouteManager.cs:496 (Register) |
.NET registers route in dictionary; missing: pool index management, duplicate detection with handleDuplicateRoute, per-account route registration in accRoutes, sendSubsToRoute call, gateway/leafnode URL propagation, forwardNewRouteInfoToKnownServers |
hasSolicitedRoute |
route.go:2438 | MISSING | — | Helper to find a solicited route in a pool slice; not implemented |
upgradeRouteToSolicited |
route.go:2458 | MISSING | — | Upgrade an inbound route to solicited status; not implemented |
handleDuplicateRoute |
route.go:2473 | MISSING | — | Duplicate route resolution (close extra connection, preserve retry flag) not implemented |
(c *client) importFilter |
route.go:2510 | MISSING | — | Permission-based subscription filter for sending to routes not implemented |
(s *Server) updateRouteSubscriptionMap |
route.go:2519 | PARTIAL | src/NATS.Server/Routes/RouteManager.cs:381,392 (PropagateLocalSubscription/PropagateLocalUnsubscription) |
.NET broadcasts RS+/RS- to all routes; missing: account routePoolIdx-based routing, queue-weight dedup (sqmu/lqws), no-pool route handling, gateway/leafnode interest updates |
(s *Server) startRouteAcceptLoop |
route.go:2696 | PARTIAL | src/NATS.Server/Routes/RouteManager.cs:333 (StartAsync) |
.NET binds and starts accept loop, solicits configured routes; missing: cluster name logging, TLS config on accept, routeInfo construction, advertise/NoAdvertise, LeafNode/Gateway URL propagation |
(s *Server) setRouteInfoHostPortAndIP |
route.go:2829 | MISSING | — | Route INFO host/port/IP with Cluster.Advertise support not implemented |
(s *Server) StartRouting |
route.go:2849 | PORTED | src/NATS.Server/Routes/RouteManager.cs:333 (StartAsync) |
Functionally equivalent: starts accept loop and solicits routes |
(s *Server) reConnectToRoute |
route.go:2861 | PARTIAL | src/NATS.Server/Routes/RouteManager.cs:462 (ConnectToRouteWithRetryAsync) |
.NET retries indefinitely with 250ms delay; missing: random jitter delay, explicit vs implicit distinction affecting delay, quit-channel integration |
(s *Server) routeStillValid |
route.go:2881 | MISSING | — | Check that a route URL is still in configured routes list (for reconnect guard) not implemented |
(s *Server) connectToRoute |
route.go:2890 | PARTIAL | src/NATS.Server/Routes/RouteManager.cs:462 (ConnectToRouteWithRetryAsync) |
.NET connects and retries; missing: explicit/implicit distinction, ConnectRetries limit, exponential backoff (ConnectBackoff), routesToSelf exclusion, address randomization from DNS |
(c *client) isSolicitedRoute |
route.go:2976 | MISSING | — | Helper predicate; not implemented |
(s *Server) saveRouteTLSName |
route.go:2985 | NOT_APPLICABLE | — | TLS not yet implemented in .NET port |
(s *Server) solicitRoutes |
route.go:2996 | PARTIAL | src/NATS.Server/Routes/RouteManager.cs:347–354 |
.NET solicits configured routes with pool connections; missing: per-account (pinned) route solicitation, saveRouteTLSName |
(c *client) processRouteConnect |
route.go:3011 | MISSING | — | Parsing and validation of inbound CONNECT from route (cluster name check, wrong-port detection, LNOC/LNOCU flags) not implemented; .NET uses a simpler handshake |
(s *Server) removeAllRoutesExcept |
route.go:3085 | PORTED | src/NATS.Server/Routes/RouteManager.cs:602 (RemoveAllRoutesExcept) |
Equivalent behavior: remove all routes not in the keep-set |
(s *Server) removeRoute |
route.go:3113 | PARTIAL | src/NATS.Server/Routes/RouteManager.cs:577 (RemoveRoute) |
.NET removes from _routes dict; missing: per-account route cleanup (accRoutes), hash removal, gateway/leafnode URL withdrawal, noPool counter, reconnect-after-noPool logic |
(s *Server) isDuplicateServerName |
route.go:3233 | MISSING | — | Duplicate server name detection across routes not implemented |
(s *Server) forEachNonPerAccountRoute |
route.go:3263 | NOT_APPLICABLE | — | Internal Go iterator over route slice; .NET uses _routes.Values LINQ directly |
(s *Server) forEachRoute |
route.go:3277 | NOT_APPLICABLE | — | Internal Go iterator; .NET enumerates _routes and _accountRoutes directly |
(s *Server) forEachRouteIdx |
route.go:3292 | NOT_APPLICABLE | — | Internal Go pool-index iterator; .NET ComputeRoutePoolIdx achieves equivalent selection |
(s *Server) forEachRemote |
route.go:3305 | NOT_APPLICABLE | — | Internal Go iterator (first non-nil per remote); .NET has no equivalent but uses LINQ |
Keeping This File Updated
After porting work is completed:
- Update status: Change
MISSING → PORTEDorPARTIAL → PORTEDfor each item completed - Add .NET path: Fill in the ".NET Equivalent" column with the actual file:line
- Re-count LOC: Update the LOC numbers in
stillmissing.md:# Re-count .NET source LOC for this module find src/NATS.Server/Routes/ -name '*.cs' -type f -exec cat {} + | wc -l # Re-count .NET test LOC for this module find tests/NATS.Server.Tests/Routes/ -name '*.cs' -type f -exec cat {} + | wc -l - Add a changelog entry below with date and summary of what was ported
- Update the parity DB if new test mappings were created:
sqlite3 docs/test_parity.db "INSERT INTO test_mappings (go_test_id, dotnet_test_id, confidence, notes) VALUES (?, ?, 'manual', 'ported in YYYY-MM-DD session')"
Change Log
| Date | Change | By |
|---|---|---|
| 2026-02-25 | File created with LLM analysis instructions | auto |
| 2026-02-25 | Full gap inventory populated: 57 Go symbols classified across route.go (3,314 lines). Counts: PORTED 4, PARTIAL 21, MISSING 23, NOT_APPLICABLE 9, DEFERRED 0 | auto |