Files
natsdotnet/gaps/routes.md
Joseph Doherty c30e67a69d Fix E2E test gaps and add comprehensive E2E + parity test suites
- Fix pull consumer fetch: send original stream subject in HMSG (not inbox)
  so NATS client distinguishes data messages from control messages
- Fix MaxAge expiry: add background timer in StreamManager for periodic pruning
- Fix JetStream wire format: Go-compatible anonymous objects with string enums,
  proper offset-based pagination for stream/consumer list APIs
- Add 42 E2E black-box tests (core messaging, auth, TLS, accounts, JetStream)
- Add ~1000 parity tests across all subsystems (gaps closure)
- Update gap inventory docs to reflect implementation status
2026-03-12 14:09:23 -04:00

21 KiB
Raw Blame History

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:

  1. Extract all exported types (structs, interfaces, type aliases)
  2. Extract all exported methods on those types (receiver functions)
  3. Extract all exported standalone functions
  4. Note key constants, enums, and protocol states
  5. Note important unexported helpers that implement core logic (functions >20 lines)
  6. 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:

  1. Search for a matching type, method, or function in .NET
  2. If found, compare the behavior: does it handle the same edge cases? Same error paths?
  3. If partially implemented, note what's missing
  4. If not found, note it as MISSING

Step 3: Cross-Reference Tests

Compare Go test functions against .NET test methods:

  1. For each Go Test* function, check if a corresponding .NET [Fact] or [Theory] exists
  2. Note which test scenarios are covered and which are missing
  3. 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.go read/write loop but with ClientKind = 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.
  • RMSG is the routed message format (differs from client MSG).

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, RMSG for routed messages.

Go Reference Files (Tests)

  • golang/nats-server/server/routes_test.go
  • golang/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:3644 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:5694 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:97101 MISSING Used internally for deferred pool-connection creation after first PONG; no .NET equivalent
gossipDefault/gossipDisabled/gossipOverride consts route.go:104108 PORTED src/NATS.Server/Routes/RouteManager.cs:1719 Gossip mode constants are defined as byte constants (GossipDefault, GossipDisabled, GossipOverride)
connectInfo struct route.go:110124 PORTED src/NATS.Server/Routes/RouteConnection.cs:378 (BuildConnectInfoJson) Connect payload now includes parity fields: echo, verbose, pedantic, tls_required, headers, cluster, dynamic, lnoc, lnocu
ConProto/InfoProto protocol format strings route.go:127130 PARTIAL src/NATS.Server/Routes/RouteConnection.cs:1112,8395 CONNECT/INFO format constants added, but active wire handshake remains simplified ROUTE <serverId> rather than full CONNECT/INFO exchange
clusterTLSInsecureWarning const route.go:134 NOT_APPLICABLE TLS not yet implemented in .NET port; warning string has no counterpart
defaultRouteMaxPingInterval const route.go:140 PORTED src/NATS.Server/Routes/RouteManager.cs:20 DefaultRouteMaxPingInterval constant added
routeConnectDelay/routeConnectMaxDelay/routeMaxPingInterval vars route.go:145148 PARTIAL src/NATS.Server/Routes/RouteManager.cs:1415,20,751757 Route reconnect delay now uses bounded exponential backoff (ComputeRetryDelay) with dedicated delay/max constants; still not runtime-configurable from route config
(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:187222 (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:219221 .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 PORTED src/NATS.Server/NatsServer.cs:181 Added UpdateServerINFOAndSendINFOToClients() INFO refresh/broadcast path to connected clients; validated with targeted socket-level parity test.
(s *Server) processImplicitRoute route.go:1043 PARTIAL src/NATS.Server/Routes/RouteManager.cs:115 (ProcessImplicitRoute) Now guards discovered URLs with HasThisRouteConfigured and normalized duplicate checks; pinned-account re-solicitation and duplicate-ID handling remain missing
(s *Server) hasThisRouteConfigured route.go:1104 PORTED src/NATS.Server/Routes/RouteManager.cs:164 Implemented with normalized URL matching against explicit configured routes and known route URLs
(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 PORTED src/NATS.Server/Subscriptions/SubList.cs:176 (GetAccNameFromRoutedSubKey) Routed-sub key parsing helper added and validated with parity tests
(c *client) getRoutedSubKeyInfo route.go:1290 PORTED src/NATS.Server/Subscriptions/SubList.cs:179 (GetRoutedSubKeyInfo) Routed-sub key decomposition helper added (route/account/subject/queue) and covered by tests
(c *client) removeRemoteSubs route.go:1299 PORTED src/NATS.Server/Subscriptions/SubList.cs:195 (RemoveRemoteSubs), src/NATS.Server/Routes/RouteManager.cs:593 (WatchRouteAsync), src/NATS.Server/NatsServer.cs:960 (RemoveRemoteSubscriptionsForRoute) Route close/removal now triggers remote-sub cleanup from account SubLists when the last connection for a remote server is gone
(c *client) removeRemoteSubsForAcc route.go:1352 PARTIAL src/NATS.Server/Subscriptions/SubList.cs:229 (RemoveRemoteSubsForAccount), src/NATS.Server/Routes/RouteManager.cs:286 (UnregisterAccountRoute), src/NATS.Server/NatsServer.cs:966 (RemoveRemoteSubscriptionsForRouteAccount) Per-account cleanup path is now wired on dedicated-route unregistration; full dedicated-route transition parity remains incomplete
(c *client) parseUnsubProto route.go:1366 PORTED src/NATS.Server/Routes/RouteConnection.cs:357 (TryParseRemoteUnsub) Dedicated RS-/LS- parser now handles account/subject/optional-queue extraction and is used by frame processing
(c *client) processRemoteUnsub route.go:1404 PARTIAL src/NATS.Server/Routes/RouteConnection.cs:177185 .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:167175 .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 PORTED src/NATS.Server/Routes/RouteConnection.cs:179 (SendRouteSubOrUnSubProtosAsync) Added low-level buffered route-proto sender that batches RS+/RS-/LS+/LS- control lines into a single write/flush.
(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 PORTED src/NATS.Server/Routes/RouteConnection.cs:146 (SendRouteSubProtosAsync) Added batched RS+ emission from remote-subscription models with queue/weight support.
(c *client) sendRouteUnSubProtos route.go:1890 PORTED src/NATS.Server/Routes/RouteConnection.cs:165 (SendRouteUnSubProtosAsync) Added batched RS- emission from remote-subscription models.
(c *client) sendRouteSubOrUnSubProtos route.go:1898 PORTED src/NATS.Server/Routes/RouteConnection.cs:179 (SendRouteSubOrUnSubProtosAsync) Added low-level batch sender for arbitrary route sub/unsub protocol lines.
(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 PORTED src/NATS.Server/Routes/RouteManager.cs:721 Implemented helper to query whether a given remote server currently has a solicited route
upgradeRouteToSolicited route.go:2458 PORTED src/NATS.Server/Routes/RouteManager.cs:730 Implemented route upgrade helper to flip an existing route into solicited mode
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:512 (ConnectToRouteWithRetryAsync) Retry loop now uses bounded exponential backoff and route validity guard; jitter, explicit/implicit-specific delay behavior, and quit-channel parity remain missing
(s *Server) routeStillValid route.go:2881 PORTED src/NATS.Server/Routes/RouteManager.cs:180 Implemented reconnect guard that validates configured and discovered route URLs using normalized comparisons
(s *Server) connectToRoute route.go:2890 PARTIAL src/NATS.Server/Routes/RouteManager.cs:512 (ConnectToRouteWithRetryAsync) Exponential backoff and route-validity checks are implemented; ConnectRetries/ConnectBackoff config parity, routes-to-self exclusion, and DNS randomization are still missing
(c *client) isSolicitedRoute route.go:2976 PORTED src/NATS.Server/Routes/RouteConnection.cs:35,370 IsSolicited state is tracked on route connections and exposed via IsSolicitedRoute() helper
(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:347354 .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:632 (RemoveRoute) Remove path now also cleans hash index and per-account route mappings tied to removed connections; gateway/leafnode URL withdrawal, noPool counters, and reconnect-after-noPool logic remain missing
(s *Server) isDuplicateServerName route.go:3233 PORTED src/NATS.Server/Routes/RouteManager.cs:748 Duplicate server-name detection helper implemented against current connected-server ID set
(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:

  1. Update status: Change MISSING → PORTED or PARTIAL → PORTED for each item completed
  2. Add .NET path: Fill in the ".NET Equivalent" column with the actual file:line
  3. 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
    
  4. Add a changelog entry below with date and summary of what was ported
  5. 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-26 Ported async INFO broadcast parity slice: wired/validated UpdateServerINFOAndSendINFOToClients() as the sendAsyncInfoToClients equivalent and added targeted socket broadcast test (RouteInfoBroadcastParityBatch4Tests). codex
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
2026-02-25 Ported route parity helper batch: gossip/default constants, connect-info parity fields, configured-route/reconnect guard helpers, solicited-route helpers, duplicate-server-name detection, RS-/LS- parser, and LS+/LS- + queue-weight wire helpers; updated row statuses and notes codex
2026-02-25 Ported routed-sub key helpers and remote-sub cleanup parity batch: added getAccNameFromRoutedSubKey/getRoutedSubKeyInfo equivalents plus route-close and per-account cleanup plumbing with targeted tests codex
2026-02-25 Ported route batch-proto parity batch: added buffered batch sender APIs (SendRouteSubProtosAsync, SendRouteUnSubProtosAsync, SendRouteSubOrUnSubProtosAsync) for RS+/RS-/LS+/LS- protocol frames with targeted tests (RouteBatchProtoParityBatch3Tests) codex