Files
natsdotnet/gaps/gateways.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

256 lines
31 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Gateways — Gap Analysis
> This file tracks what has and hasn't been ported from Go to .NET for the **Gateways** module.
> See [stillmissing.md](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:
```bash
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 Gateways
- Gateways connect separate NATS clusters. They start in "optimistic" mode (forward everything) then switch to "interest-only" mode.
- Reply subject mapping: `_GR_.<cluster>.<hash>.<reply>` prevents routing loops.
- Gateway connections use `ClientKind = GATEWAY`.
---
## Go Reference Files (Source)
- `golang/nats-server/server/gateway.go` — Inter-cluster bridges (~3,400 lines). Interest-only mode optimizes traffic. Reply subject mapping (`_GR_.` prefix) avoids cross-cluster conflicts.
## Go Reference Files (Tests)
- `golang/nats-server/server/gateway_test.go`
- `golang/nats-server/test/gateway_test.go` (integration)
## .NET Implementation Files (Source)
- `src/NATS.Server/Gateways/` (all files)
## .NET Implementation Files (Tests)
- `tests/NATS.Server.Tests/Gateways/`
---
## Gap Inventory
<!-- After analysis, fill in this table. Group rows by Go source file. -->
### `golang/nats-server/server/gateway.go`
#### Constants, Variables, and Enums
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|-----------|:-------------|--------|:----------------|-------|
| `SetGatewaysSolicitDelay` | gateway.go:76 | NOT_APPLICABLE | — | Test-only helper; sets atomic delay before soliciting gateways. Go-specific test hook. |
| `ResetGatewaysSolicitDelay` | gateway.go:83 | NOT_APPLICABLE | — | Test-only helper; resets atomic delay. Go-specific test hook. |
| `GatewayDoNotForceInterestOnlyMode` | gateway.go:130 | NOT_APPLICABLE | — | Test-only global flag; disables forced interest-only mode in tests. Go-specific test hook. |
| `GatewayInterestMode` (enum: Optimistic/Transitioning/InterestOnly) | gateway.go:94111 | PORTED | `src/NATS.Server/Gateways/GatewayInterestTracker.cs:16` | `GatewayInterestMode` enum with identical three values. |
| `GatewayInterestMode.String()` | gateway.go:113 | NOT_APPLICABLE | — | Go stringer pattern; C# uses `ToString()` automatically. |
| `gwReplyPrefix` / `gwReplyPrefixLen` / `gwHashLen` / offset constants | gateway.go:4958 | PORTED | `src/NATS.Server/Gateways/ReplyMapper.cs:1217` | Reply-prefix/length/hash-length constants are explicitly defined (`GatewayReplyPrefix`, `GatewayReplyPrefixLen`, `GatewayHashLen`, plus legacy counterparts). |
| `oldGWReplyPrefix` / `oldGWReplyPrefixLen` / `oldGWReplyStart` | gateway.go:4346 | PORTED | `src/NATS.Server/Gateways/ReplyMapper.cs:13,15,31` | Legacy `$GR.` prefix constants and old-prefix detection are implemented via `IsGatewayRoutedSubject(..., out isOldPrefix)`. |
| `gatewayTLSInsecureWarning` | gateway.go:71 | PORTED | `src/NATS.Server/Gateways/GatewayManager.cs:68` | Gateway TLS insecure warning constant is defined for parity/documentation and diagnostic use. |
#### Structs / Types
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|-----------|:-------------|--------|:----------------|-------|
| `srvGateway` struct | gateway.go:134 | PARTIAL | `src/NATS.Server/Gateways/GatewayManager.cs:66` | `GatewayManager` covers outbound/inbound maps, accept loop, discovery, registration, and stats. Missing: RTT-ordered outbound list (`outo`), `totalQSubs` atomic counter, `pasi` per-account subscription interest map, `rsubs` recent-subscription sync.Map, `sIDHash`/`routesIDByHash` for reply routing, `sqbsz`/`recSubExp`, and `oldHash`/`oldReplyPfx` for backward compat. |
| `sitally` struct | gateway.go:189 | MISSING | — | Subject-interest tally (ref count + queue flag) used in `pasi.m`. No equivalent in .NET. |
| `gatewayCfg` struct | gateway.go:194 | PARTIAL | `src/NATS.Server/Configuration/GatewayOptions.cs:27` (`RemoteGatewayOptions`) | Added parity state and helpers for `hash`/`oldHash`, `implicit`, connection attempts, TLS host capture, URL add/update/get flows, and varz URL-update flag. Remaining gap: per-remote TLS config wiring into active gateway dial/handshake path. |
| `gateway` struct (per-client) | gateway.go:207 | MISSING | — | Per-connection gateway state (outbound flag, `outsim` sync.Map, `insim` map, `connectURL`, `useOldPrefix`, `interestOnlyMode`, `remoteName`). Absorbed into `GatewayConnection` but without full per-message interest tracking. |
| `outsie` struct | gateway.go:229 | PARTIAL | `src/NATS.Server/Gateways/GatewayInterestTracker.cs` | `GatewayInterestTracker.AccountState` covers mode and no-interest set. Missing: per-account `sl *Sublist` for queue-sub tracking in InterestOnly mode, `qsubs` counter. |
| `insie` struct | gateway.go:256 | MISSING | — | Inbound per-account no-interest set with mode tracking. No distinct type in .NET; `GatewayInterestTracker` handles outbound-side equivalent but not the inbound RS-/RS+ tracking map. |
| `gwReplyMap` struct | gateway.go:261 | PARTIAL | `src/NATS.Server/Gateways/ReplyMapper.cs:289` (`CacheEntry`) | `ReplyMapCache.CacheEntry` provides key/value/TTL. Missing: Go stores `ms string` (routed subject) and `exp int64` (Unix nano expiry); .NET uses `DateTime` but does not store the destination server/cluster hash separately. |
| `gwReplyMapping` struct | gateway.go:266 | PARTIAL | `src/NATS.Server/Gateways/ReplyMapper.cs:181` (`ReplyMapCache`) | `ReplyMapCache` provides LRU+TTL. Missing: atomic `check int32` field for fast-path bypass; Go mapping is per-client or per-account with explicit locker, .NET is a single shared cache. |
| `GatewayConnectionState` enum | gateway.go (n/a) | PORTED | `src/NATS.Server/Gateways/GatewayManager.cs:14` | .NET addition: lifecycle states (Connecting/Connected/Disconnected/Draining). No direct Go equivalent but covers the connection state machine. |
| `GatewayRegistration` | gateway.go (n/a) | PORTED | `src/NATS.Server/Gateways/GatewayManager.cs:26` | .NET-specific registration record with stats counters. |
| `GatewayReconnectPolicy` | gateway.go:689 | PORTED | `src/NATS.Server/Gateways/GatewayManager.cs:43` | Exponential backoff with jitter matches Go's reconnect delay logic. |
| `GatewayInfo` | gateway.go (Info struct) | PARTIAL | `src/NATS.Server/Gateways/GatewayInfo.cs:7` | Covers name and URLs for gossip discovery. Go's `Info` struct has many more gateway-related fields (`GatewayCmd`, `GatewayCmdPayload`, `GatewayNRP`, `GatewayIOM`, `GatewayURLs`, etc.) that are not in this type. |
#### Functions and Methods — `srvGateway` receiver
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|-----------|:-------------|--------|:----------------|-------|
| `srvGateway.updateRemotesTLSConfig` | gateway.go:426 | MISSING | — | Config-reload TLS update for remote gateway connections. TLS not implemented in .NET. |
| `srvGateway.rejectUnknown` | gateway.go:476 | PARTIAL | `src/NATS.Server/Configuration/GatewayOptions.cs:11` | `GatewayOptions.RejectUnknown` property exists but is not enforced in the accept-loop or handshake logic. |
| `srvGateway.generateInfoJSON` | gateway.go:649 | MISSING | — | Generates the gateway `INFO` protocol JSON. No INFO protocol generation in .NET. |
| `srvGateway.getClusterHash` | gateway.go:2892 | MISSING | — | Returns 6-byte cluster hash for reply routing. No cluster hash computed at runtime in .NET. |
| `srvGateway.shouldMapReplyForGatewaySend` | gateway.go:2507 | MISSING | — | Checks recent subscriptions (`rsubs`) to decide whether to map a reply subject. `rsubs` not ported. |
| `srvGateway.orderOutboundConnectionsLocked` | gateway.go:1764 | MISSING | — | Sorts outbound connections by lowest RTT. RTT-based ordering not implemented. |
| `srvGateway.orderOutboundConnections` | gateway.go:1771 | MISSING | — | Locked wrapper for `orderOutboundConnectionsLocked`. |
#### Functions and Methods — `Server` receiver
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|-----------|:-------------|--------|:----------------|-------|
| `validateGatewayOptions` | gateway.go:306 | PORTED | `src/NATS.Server/Gateways/GatewayManager.cs:113` (`ValidateGatewayOptions`) | Basic gateway config validation implemented for required name, valid port range, and non-empty remotes. |
| `getGWHash` (standalone) | gateway.go:335 | PORTED | `src/NATS.Server/Gateways/ReplyMapper.cs:75` (`ComputeGatewayHash`) | Deterministic short gateway hash helper implemented (6-char hex). |
| `getOldHash` (standalone) | gateway.go:339 | PORTED | `src/NATS.Server/Gateways/ReplyMapper.cs:85` (`ComputeOldGatewayHash`) | Deterministic legacy short hash helper implemented (4-char hex). |
| `Server.newGateway` | gateway.go:350 | PARTIAL | `src/NATS.Server/Gateways/GatewayManager.cs:90` | `GatewayManager` constructor covers basic setup. Missing: hash computation, reply prefix assembly, `oldReplyPfx`, `pasi` init, resolver config. |
| `Server.startGateways` | gateway.go:487 | PARTIAL | `src/NATS.Server/Gateways/GatewayManager.cs:161` (`StartAsync`) | `StartAsync` starts accept loop and connects to remotes. Missing: solicit delay, cluster-formation wait. |
| `Server.startGatewayAcceptLoop` | gateway.go:511 | PARTIAL | `src/NATS.Server/Gateways/GatewayManager.cs:326` (`AcceptLoopAsync`) | Accept loop exists. Missing: TLS config, `authRequired`, reject-unknown check, advertising, `GatewayIOM` flag, INFO protocol send. |
| `Server.setGatewayInfoHostPort` | gateway.go:595 | MISSING | — | Configures gateway listener host/port with advertise override and non-local IP resolution. |
| `Server.solicitGateways` | gateway.go:668 | PARTIAL | `src/NATS.Server/Gateways/GatewayManager.cs:173` (`StartAsync` remote loop) | Connects to configured remotes. Missing: implicit vs explicit distinction, goroutine-per-remote with proper lifecycle. |
| `Server.reconnectGateway` | gateway.go:689 | PORTED | `src/NATS.Server/Gateways/GatewayManager.cs:149` (`ReconnectGatewayAsync`) | Exponential backoff reconnect delay with jitter. |
| `Server.solicitGateway` | gateway.go:706 | PARTIAL | `src/NATS.Server/Gateways/GatewayManager.cs:358` (`ConnectWithRetryAsync`) | Retry loop with multiple URL support. Missing: random URL selection, `shouldReportConnectErr` throttling, implicit gateway retry limits, DNS resolution via `resolver`, `ConnectBackoff` flag. |
| `srvGateway.hasInbound` | gateway.go:790 | PORTED | `src/NATS.Server/Gateways/GatewayManager.cs:345` (`HasInbound`) | Inbound-connection presence check is implemented by remote server id. |
| `Server.createGateway` | gateway.go:805 | PARTIAL | `src/NATS.Server/Gateways/GatewayManager.cs:344` (`HandleInboundAsync`) and `ConnectWithRetryAsync` | Creates client connection for inbound/outbound. Missing: full client lifecycle, TLS handshake, CONNECT/INFO protocol, `expectConnect` flag, ping timer setup, temp-client registration. |
| `client.sendGatewayConnect` | gateway.go:958 | PARTIAL | `src/NATS.Server/Gateways/GatewayConnection.cs:113` (`PerformOutboundHandshakeAsync`) | Handshake sends server ID. Go sends full CONNECT JSON with auth, TLS flag, gateway name. .NET sends a simplified `GATEWAY {serverId}` line. |
| `client.processGatewayConnect` | gateway.go:993 | MISSING | — | Parses CONNECT from inbound gateway; validates gateway field, rejects wrong port/unknown gateways. No protocol parsing in .NET. |
| `client.processGatewayInfo` | gateway.go:1045 | MISSING | — | Handles INFO protocol for both outbound (first connect + gossip) and inbound (register, switch to interest-only, send queue subs). Core of gateway handshake. Not ported. |
| `Server.gossipGatewaysToInboundGateway` | gateway.go:1253 | MISSING | — | Sends INFO gossip for all known gateways to a new inbound connection for full-mesh formation. |
| `Server.forwardNewGatewayToLocalCluster` | gateway.go:1279 | MISSING | — | Floods cluster routes with new gateway INFO to ensure all nodes connect. |
| `Server.sendQueueSubsToGateway` | gateway.go:1311 | PARTIAL | `src/NATS.Server/Gateways/GatewayConnection.cs:72` (`AddQueueSubscription`) | Registers queue subs locally. Missing: actual RS+ wire protocol emission to the remote peer. |
| `Server.sendAccountSubsToGateway` | gateway.go:1319 | PARTIAL | `src/NATS.Server/Gateways/GatewayManager.cs:210` (`SendAccountSubscriptions`) | Sends subject list to named gateway. Missing: wire protocol emission (`RS+` with account prefix) and mode switch to InterestOnly. |
| `gwBuildSubProto` (standalone) | gateway.go:1323 | MISSING | — | Builds `RS+`/`RS-` binary protocol buffer for a given account/subject map. |
| `Server.sendSubsToGateway` | gateway.go:1342 | MISSING | — | Full subscription send loop (queue subs on connect, or all subs for an account on mode switch). |
| `Server.processGatewayInfoFromRoute` | gateway.go:1394 | MISSING | — | Handles gateway gossip INFO received via a cluster route connection. |
| `Server.sendGatewayConfigsToRoute` | gateway.go:1406 | MISSING | — | Sends known outbound gateway configs to a new route connection. |
| `Server.processImplicitGateway` | gateway.go:1453 | PARTIAL | `src/NATS.Server/Gateways/GatewayManager.cs:119` (`ProcessImplicitGateway`) | Records discovered gateway name. Missing: URL augmentation of existing config, creation of `gatewayCfg`, launching `solicitGateway` goroutine for the new implicit remote. |
| `Server.NumOutboundGateways` | gateway.go:1501 | PORTED | `src/NATS.Server/NatsServer.cs:155` (`NumOutboundGateways`) | Public server-facing outbound gateway count now exposed. |
| `Server.numOutboundGateways` | gateway.go:1506 | PORTED | `src/NATS.Server/Gateways/GatewayManager.cs:331` (`NumOutboundGateways`) | Manager now computes outbound count from live connection direction (`IsOutbound`). |
| `Server.numInboundGateways` | gateway.go:1514 | PORTED | `src/NATS.Server/Gateways/GatewayManager.cs:338` (`NumInboundGateways`), `src/NATS.Server/NatsServer.cs:156` | Inbound gateway count is now tracked and exposed. |
| `Server.getRemoteGateway` | gateway.go:1522 | PARTIAL | `src/NATS.Server/Gateways/GatewayManager.cs:264` (`GetRegistration`) | Returns registration by name. Missing: returns `gatewayCfg` in Go (with TLS, URLs, hash); .NET returns `GatewayRegistration` (only state/stats). |
| `gatewayCfg.bumpConnAttempts` | gateway.go:1530 | PORTED | `src/NATS.Server/Configuration/GatewayOptions.cs:57` (`RemoteGatewayOptions.BumpConnAttempts`) | Added connection-attempt increment helper. |
| `gatewayCfg.getConnAttempts` | gateway.go:1537 | PORTED | `src/NATS.Server/Configuration/GatewayOptions.cs:59` (`RemoteGatewayOptions.GetConnAttempts`) | Added connection-attempt read helper. |
| `gatewayCfg.resetConnAttempts` | gateway.go:1545 | PORTED | `src/NATS.Server/Configuration/GatewayOptions.cs:61` (`RemoteGatewayOptions.ResetConnAttempts`) | Added connection-attempt reset helper. |
| `gatewayCfg.isImplicit` | gateway.go:1552 | PORTED | `src/NATS.Server/Configuration/GatewayOptions.cs:63` (`RemoteGatewayOptions.IsImplicit`) | Added implicit-vs-explicit gateway config query. |
| `gatewayCfg.getURLs` | gateway.go:1561 | PORTED | `src/NATS.Server/Configuration/GatewayOptions.cs:65` (`RemoteGatewayOptions.GetUrls`) | Added normalized + shuffled URL list helper for connection attempts. |
| `gatewayCfg.getURLsAsStrings` | gateway.go:1576 | PORTED | `src/NATS.Server/Configuration/GatewayOptions.cs:84` (`RemoteGatewayOptions.GetUrlsAsStrings`) | Added URL string projection helper for gossip/config sync paths. |
| `gatewayCfg.updateURLs` | gateway.go:1588 | PORTED | `src/NATS.Server/Configuration/GatewayOptions.cs:92` (`RemoteGatewayOptions.UpdateUrls`) | Added merged configured+discovered URL rebuild helper with normalization/deduplication. |
| `gatewayCfg.saveTLSHostname` | gateway.go:1612 | PORTED | `src/NATS.Server/Configuration/GatewayOptions.cs:100` (`RemoteGatewayOptions.SaveTlsHostname`) | Added TLS hostname extraction/storage from URL host. |
| `gatewayCfg.addURLs` | gateway.go:1621 | PORTED | `src/NATS.Server/Configuration/GatewayOptions.cs:106` (`RemoteGatewayOptions.AddUrls`) | Added incremental discovered-URL add helper with normalization/deduplication. |
| `Server.addGatewayURL` | gateway.go:1648 | MISSING | — | Adds a URL to the server's gateway URL set and regenerates INFO JSON. |
| `Server.removeGatewayURL` | gateway.go:1661 | MISSING | — | Removes a URL from the gateway URL set and regenerates INFO JSON. |
| `Server.sendAsyncGatewayInfo` | gateway.go:1676 | MISSING | — | Sends updated INFO to all inbound gateway connections (e.g., after URL change). |
| `Server.getGatewayURL` | gateway.go:1688 | PORTED | `src/NATS.Server/NatsServer.cs:259` | Added gateway listen URL accessor that returns configured listen endpoint when gateway manager is present. |
| `Server.getGatewayName` | gateway.go:1697 | PORTED | `src/NATS.Server/NatsServer.cs:260` | Added gateway name accessor returning configured gateway cluster name. |
| `Server.getAllGatewayConnections` | gateway.go:1703 | MISSING | — | Collects all inbound + outbound gateway clients into a map. |
| `Server.registerInboundGatewayConnection` | gateway.go:1720 | PARTIAL | `src/NATS.Server/Gateways/GatewayManager.cs:392` (`Register`) | `Register` adds connection to dictionary. Missing: separate inbound map keyed by CID. |
| `Server.registerOutboundGatewayConnection` | gateway.go:1728 | PARTIAL | `src/NATS.Server/Gateways/GatewayManager.cs:392` (`Register`) | `Register` handles registration. Missing: duplicate-prevention logic (return false if name already exists), RTT-ordered `outo` list. |
| `Server.getOutboundGatewayConnection` | gateway.go:1743 | PORTED | `src/NATS.Server/Gateways/GatewayManager.cs:352` (`GetOutboundGatewayConnection`) | Outbound connection lookup by remote server id is implemented. |
| `Server.getOutboundGatewayConnections` | gateway.go:1752 | PORTED | `src/NATS.Server/Gateways/GatewayManager.cs:359` (`GetOutboundGatewayConnections`) | Outbound connection snapshot enumeration is implemented. |
| `Server.getInboundGatewayConnections` | gateway.go:1778 | PORTED | `src/NATS.Server/Gateways/GatewayManager.cs:366` (`GetInboundGatewayConnections`) | Inbound connection snapshot enumeration is implemented. |
| `Server.removeRemoteGatewayConnection` | gateway.go:1788 | PARTIAL | `src/NATS.Server/Gateways/GatewayManager.cs:416` (`WatchConnectionAsync`) | Removes connection and decrements stats. Missing: outbound-specific cleanup (delete from `outo`/`out`, remove qsub tracking from `totalQSubs`), inbound-specific cleanup (remove `_R_` subscriptions). |
| `Server.GatewayAddr` | gateway.go:1862 | PORTED | `src/NATS.Server/NatsServer.cs:258` | Added gateway address accessor. .NET returns `host:port` string endpoint rather than Go `*net.TCPAddr`. |
| `client.processGatewayAccountUnsub` | gateway.go:1875 | PARTIAL | `src/NATS.Server/Gateways/GatewayInterestTracker.cs:86` (`TrackNoInterest`) | Tracks no-interest at account level. Missing: handling of queue subs (reset `ni` map but keep entry if `qsubs > 0`), Go's nil-vs-entry distinction in `outsim`. |
| `client.processGatewayAccountSub` | gateway.go:1904 | PARTIAL | `src/NATS.Server/Gateways/GatewayInterestTracker.cs:61` (`TrackInterest`) | Clears no-interest in optimistic mode. Missing: queue-sub check (don't delete entry if `qsubs > 0`). |
| `client.processGatewayRUnsub` | gateway.go:1934 | MISSING | — | Parses RS- protocol; for optimistic mode stores in ni map, for InterestOnly/queue removes from sublist. Full RS- processing not ported. |
| `client.processGatewayRSub` | gateway.go:2029 | MISSING | — | Parses RS+ protocol; registers interest in sublist for queue subs and InterestOnly mode. Full RS+ processing not ported. |
| `client.gatewayInterest` | gateway.go:2165 | PARTIAL | `src/NATS.Server/Gateways/GatewayInterestTracker.cs:113` (`ShouldForward`) | `ShouldForward` handles Optimistic/Transitioning/InterestOnly modes. Missing: separate queue-sub result (`*SublistResult`) return, `interestOnlyMode` flag override, `emptyResult` guard. |
| `Server.switchAccountToInterestMode` | gateway.go:2211 | PORTED | `src/NATS.Server/Gateways/GatewayInterestTracker.cs:144` (`SwitchToInterestOnly`) | Switches account to InterestOnly. Go iterates all inbound connections; .NET operates per-tracker instance. |
| `Server.maybeSendSubOrUnsubToGateways` | gateway.go:2237 | PARTIAL | `src/NATS.Server/Gateways/GatewayManager.cs:193` (`PropagateLocalSubscription/Unsubscription`) | Propagates sub/unsub to inbound gateways. Missing: per-mode decision (optimistic ni-map check vs InterestOnly always-send), wildcard cleanup of ni-map, A+ send when clearing A-, actual wire protocol. |
| `Server.sendQueueSubOrUnsubToGateways` | gateway.go:2335 | PARTIAL | `src/NATS.Server/Gateways/GatewayManager.cs:199` (`PropagateLocalUnsubscription`) | Propagates queue sub changes. Missing: wire RS+/RS- protocol, A- clearing logic. |
| `Server.gatewayUpdateSubInterest` | gateway.go:2391 | MISSING | — | Ref-counted `pasi` map update + recent-sub tracking + triggers send to gateways. Core subscription-interest accounting not ported. |
| `isGWRoutedReply` (standalone) | gateway.go:2484 | PORTED | `src/NATS.Server/Gateways/ReplyMapper.cs:17` (`HasGatewayReplyPrefix`) | Detects `_GR_.` prefix with length guard. |
| `isGWRoutedSubjectAndIsOldPrefix` (standalone) | gateway.go:2490 | PORTED | `src/NATS.Server/Gateways/ReplyMapper.cs:31` (`IsGatewayRoutedSubject`) | Implemented explicit routed-subject detection with old-prefix flag output (`isOldPrefix`) for `_GR_.` and `$GR.`. |
| `hasGWRoutedReplyPrefix` (standalone) | gateway.go:2502 | PORTED | `src/NATS.Server/Gateways/ReplyMapper.cs:17` (`HasGatewayReplyPrefix`) | Equivalent prefix check. |
| `srvGateway.shouldMapReplyForGatewaySend` | gateway.go:2507 | MISSING | — | Checks `rsubs` sync.Map to decide if a reply subject needs gateway mapping. No `rsubs` equivalent. |
| `client.sendMsgToGateways` | gateway.go:2540 | PARTIAL | `src/NATS.Server/Gateways/GatewayManager.cs:181` (`ForwardMessageAsync`) | Iterates connections and sends. Missing: direct-send path for `_GR_` subjects (hash routing), queue group filtering, reply subject mapping, header stripping for non-header peers, message tracing, per-account stats, RTT-ordered iteration. |
| `Server.gatewayHandleAccountNoInterest` | gateway.go:2787 | PARTIAL | Partially in `GatewayInterestTracker.TrackNoInterest` | Sends A- under `pasi` lock. Missing: pasi lock coordination, actual A- wire protocol emission from inbound side. |
| `client.sendAccountUnsubToGateway` | gateway.go:2802 | PARTIAL | `src/NATS.Server/Gateways/GatewayConnection.cs:142` (`SendAMinusAsync`) | Sends A- wire protocol. Missing: idempotency guard (don't send if already sent, tracked via nil entry in `insim`). |
| `Server.gatewayHandleSubjectNoInterest` | gateway.go:2830 | MISSING | — | On missing subject interest: sends RS- under pasi lock, counts RS-, triggers mode switch at threshold. Core interest-only negotiation not ported. |
| `Server.storeRouteByHash` | gateway.go:2901 | MISSING | — | Stores route client keyed by server-ID hash for gateway reply routing. |
| `Server.removeRouteByHash` | gateway.go:2909 | MISSING | — | Removes route entry by hash. |
| `Server.getRouteByHash` | gateway.go:2918 | MISSING | — | Looks up route by hash for per-account or pool routing. Used in `handleGatewayReply`. |
| `getSubjectFromGWRoutedReply` (standalone) | gateway.go:2948 | PORTED | `src/NATS.Server/Gateways/ReplyMapper.cs:74` (`TryRestoreGatewayReply`) | Extracts original subject from routed reply; handles both old and new prefix. |
| `client.handleGatewayReply` | gateway.go:2963 | MISSING | — | On inbound message: detects `_GR_` prefix, decodes cluster/server hash, routes to origin route or delivers locally. Core gateway reply routing not ported. |
| `client.processInboundGatewayMsg` | gateway.go:3107 | MISSING | — | Main inbound message processor: handles GW reply, account lookup, no-interest signaling, message delivery. Not ported. |
| `client.gatewayAllSubsReceiveStart` | gateway.go:3183 | PARTIAL | `src/NATS.Server/Gateways/GatewayInterestTracker.cs:86` (mode=Transitioning path) | Go sets mode to Transitioning; .NET sets Transitioning mode when threshold crossed. Missing: command-protocol parsing, explicit start triggered by INFO command. |
| `client.gatewayAllSubsReceiveComplete` | gateway.go:3216 | PARTIAL | `src/NATS.Server/Gateways/GatewayInterestTracker.cs:144` (`SwitchToInterestOnly`) | Final switch to InterestOnly. Go clears `ni` map and sets mode; .NET does equivalent. Missing: triggered by INFO command with `gatewayCmdAllSubsComplete`. |
| `getAccountFromGatewayCommand` (standalone) | gateway.go:3240 | MISSING | — | Extracts account from `GatewayCmdPayload` in INFO protocol. Not ported. |
| `client.gatewaySwitchAccountToSendAllSubs` | gateway.go:3260 | PARTIAL | `src/NATS.Server/Gateways/GatewayInterestTracker.cs:144` (`SwitchToInterestOnly`) | Switches mode and triggers async all-subs send. Missing: INFO command emission (`gatewayCmdAllSubsStart`/`gatewayCmdAllSubsComplete`), async `sendAccountSubsToGateway` goroutine. |
| `Server.trackGWReply` | gateway.go:3324 | PARTIAL | `src/NATS.Server/Gateways/ReplyMapper.cs:231` (`ReplyMapCache.Set`) | Caches reply mapping with TTL. Missing: per-client vs per-account duality, `gwrm.m` sync.Map for background cleanup, `check int32` atomic flag, `gwrm.ch` channel to trigger expiry timer. |
| `Server.startGWReplyMapExpiration` | gateway.go:3371 | PARTIAL | `src/NATS.Server/Gateways/ReplyMapper.cs:267` (`ReplyMapCache.PurgeExpired`) | Purge is manual on-demand. Go runs a dedicated goroutine with timer reset on new entries via channel. No background expiry goroutine in .NET. |
| `gwReplyMapping.get` | gateway.go:280 | PORTED | `src/NATS.Server/Gateways/ReplyMapper.cs:202` (`ReplyMapCache.TryGet`) | LRU get with TTL check. |
| `RemoteGatewayOpts.clone` | gateway.go:290 | PORTED | `src/NATS.Server/Configuration/GatewayOptions.cs:36` (`RemoteGatewayOptions.Clone`) | Deep-copy helper implemented for remote gateway option name + URL list. |
#### Additional .NET-Only Types (No Go Equivalent)
| .NET Symbol | .NET File:Line | Notes |
|-------------|:---------------|-------|
| `GatewayCommandType` enum | `src/NATS.Server/Gateways/GatewayCommands.cs:9` | .NET-specific wire command enum; Go uses string constants and byte dispatch. |
| `GatewayCommands` static class | `src/NATS.Server/Gateways/GatewayCommands.cs:31` | Wire-protocol constants and formatters. Partially maps to Go's `rSubBytes`/`rUnsubBytes`/`aSubBytes`/`aUnsubBytes`/`InfoProto` but uses a simplified format. |
| `GatewayMessage` record | `src/NATS.Server/Gateways/GatewayConnection.cs:346` | DTO for received messages; no direct Go equivalent. |
| `ReplyMapCache` | `src/NATS.Server/Gateways/ReplyMapper.cs:181` | LRU+TTL cache for reply mappings. |
---
## 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`:
```bash
# Re-count .NET source LOC for this module
find src/NATS.Server/Gateways/ -name '*.cs' -type f -exec cat {} + | wc -l
# Re-count .NET test LOC for this module
find tests/NATS.Server.Tests/Gateways/ -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:
```bash
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 | Executed gateways batch 4 accessor parity slice: added server gateway accessors (`GatewayAddr`, `GetGatewayURL`, `GetGatewayName`) and targeted tests (`GatewayServerAccessorParityBatch4Tests`). Reclassified 3 rows to PORTED. | codex |
| 2026-02-25 | File created with LLM analysis instructions | auto |
| 2026-02-25 | Full gap inventory completed: analyzed all ~3,427 lines of gateway.go; classified 80+ Go symbols against 6 .NET source files. Final counts: 9 PORTED, 35 PARTIAL, 37 MISSING, 5 NOT_APPLICABLE. | claude-sonnet-4-6 |
| 2026-02-25 | Ported gateway parity helper batch: reply-prefix/hash constants and old-prefix detection, gateway hash helpers (`getGWHash`/`getOldHash` analogs), gateway option validator, TLS warning constant, and `RemoteGatewayOptions.Clone`; added focused tests and updated status rows. | codex |
| 2026-02-25 | Ported gateway connection-direction parity batch: added inbound/outbound classification (`IsOutbound`), count/lookups (`NumOutboundGateways`, `NumInboundGateways`, `HasInbound`, outbound/inbound connection snapshots), and server wrappers with focused tests. | codex |
| 2026-02-25 | Ported gateway remote-config parity batch: added `RemoteGatewayOptions` attempt counters and URL lifecycle helpers (`Bump/Get/ResetConnAttempts`, `IsImplicit`, `GetUrls`, `GetUrlsAsStrings`, `UpdateUrls`, `SaveTlsHostname`, `AddUrls`) with focused tests (`GatewayRemoteConfigParityBatch3Tests`). | codex |