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

391 lines
38 KiB
Markdown

# Core Server — Gap Analysis
> This file tracks what has and hasn't been ported from Go to .NET for the **Core Server** 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 Core Server
- Go's `client.go` is the **largest single file** (~6,700 lines). It contains the readLoop/writeLoop goroutines, dynamic buffer sizing (512→65536), slow consumer detection, and per-client subscription tracking. The .NET equivalent should use `async/await` with `System.IO.Pipelines`.
- Go's `server.go` manages listeners, accept loops, and shutdown coordination. Map goroutines to `Task`-based async patterns.
- Go's `opts.go` handles both CLI flags and config file parsing (CLI overrides config). The .NET version separates this into `Configuration/`.
- `reload.go` implements hot-reload by diffing old vs new config and applying deltas without restart.
- Platform-specific files (`service_windows.go`, `signal_windows.go`, `signal_wasm.go`) may be NOT_APPLICABLE.
---
## Go Reference Files (Source)
- `golang/nats-server/server/server.go` — Server struct, startup lifecycle, listener management
- `golang/nats-server/server/client.go` — Connection handling, readLoop/writeLoop, per-client state (~6,700 lines)
- `golang/nats-server/server/opts.go` — Server options struct, CLI flag parsing, config file loading
- `golang/nats-server/server/reload.go` — Hot reload on signal, config diffing
- `golang/nats-server/server/service.go` — OS service/daemon management
- `golang/nats-server/server/signal.go` — Signal handling (SIGHUP, SIGTERM, etc.)
- `golang/nats-server/main.go` — Entry point
## Go Reference Files (Tests)
- `golang/nats-server/server/server_test.go`
- `golang/nats-server/server/client_test.go`
- `golang/nats-server/server/opts_test.go`
- `golang/nats-server/server/reload_test.go`
- `golang/nats-server/server/signal_test.go`
- `golang/nats-server/server/test_test.go` (test helpers)
## .NET Implementation Files (Source)
- `src/NATS.Server/NatsServer.cs` — Server lifecycle
- `src/NATS.Server/NatsClient.cs` — Per-connection client
- `src/NATS.Server/NatsOptions.cs` — Server options
- `src/NATS.Server/ClientFlags.cs`
- `src/NATS.Server/ClientKind.cs`
- `src/NATS.Server/ClientClosedReason.cs`
- `src/NATS.Server/ClientTraceInfo.cs`
- `src/NATS.Server/ClosedState.cs`
- `src/NATS.Server/INatsClient.cs`
- `src/NATS.Server/InternalClient.cs`
- `src/NATS.Server/ServerStats.cs`
- `src/NATS.Server/SlowConsumerTracker.cs`
- `src/NATS.Server/MqttOptions.cs`
- `src/NATS.Server.Host/Program.cs` — Entry point
## .NET Implementation Files (Tests)
- `tests/NATS.Server.Tests/` (root-level test files)
---
## Gap Inventory
<!-- After analysis, fill in this table. Group rows by Go source file. -->
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|-----------|:-------------|--------|:----------------|-------|
| **golang/nats-server/main.go** | | | | |
| main() | main.go:97 | PORTED | src/NATS.Server.Host/Program.cs:1 | CLI arg parsing, config load, server create, start, wait |
| usage() | main.go:92 | PORTED | src/NATS.Server.Host/Program.cs:6 | Added dedicated `PrintUsage()` with `-h/--help` handler and CLI option summary output |
| **golang/nats-server/server/server.go — Types** | | | | |
| Info struct | server.go:109 | PORTED | src/NATS.Server/Protocol/NatsProtocol.cs:39 (ServerInfo) | Core fields ported; route/gateway/leafnode-specific fields are partial |
| Server struct | server.go:169 | PARTIAL | src/NATS.Server/NatsServer.cs:31 | Core fields ported (clients, accounts, opts, listener, shutdown). Missing: route pool tracking, gateway internals, OCSP monitors, proxied conns, rate limiting maps |
| stats struct | server.go:402 | PORTED | src/NATS.Server/ServerStats.cs:6 | All counters present |
| scStats struct | server.go:413 | PORTED | src/NATS.Server/ServerStats.cs:18-25 | Per-kind slow consumer counters present |
| staleStats struct | server.go:421 | PORTED | src/NATS.Server/ServerStats.cs:26 | Per-kind stale connection counters present |
| nodeInfo struct | server.go:387 | NOT_APPLICABLE | — | JetStream cluster-specific; tracked in JetStream module |
| Ports struct | server.go:4236 | PORTED | src/NATS.Server/NatsOptions.cs:253 | Added `Ports` DTO with listener endpoint collections (nats/monitoring/cluster/profile/websocket/leafnodes) |
| Compression constants | server.go:437-446 | PORTED | src/NATS.Server/NatsOptions.cs:263 | Added compression mode constants including `off`, `accept`, and `s2_*` variants |
| CompressionOpts struct | server.go:97 (opts.go) | PORTED | src/NATS.Server/NatsOptions.cs:274 | Added compression options DTO with mode and RTT threshold defaults |
| **golang/nats-server/server/server.go — Exported Server Methods** | | | | |
| NewServer() | server.go:716 | PORTED | src/NATS.Server/NatsServer.cs constructor | Options validation, NKey identity, info setup |
| New() (deprecated) | server.go:698 | NOT_APPLICABLE | — | Deprecated wrapper |
| NewServerFromConfig() | server.go:703 | PORTED | src/NATS.Server.Host/Program.cs:20-22 | Config file processing then new server |
| (s) Start() | server.go:2263 | PORTED | src/NATS.Server/NatsServer.cs:502 (StartAsync) | Listener, accept loop, subsystem startup. Async in .NET |
| (s) Shutdown() | server.go:2583 | PORTED | src/NATS.Server/NatsServer.cs:163 (ShutdownAsync) | Client disconnect, listener close, subsystem teardown |
| (s) WaitForShutdown() | server.go:2775 | PORTED | src/NATS.Server/NatsServer.cs:150 | Blocks until shutdown complete |
| (s) AcceptLoop() | server.go:2780 | PORTED | src/NATS.Server/NatsServer.cs:502 (inline in StartAsync) | Accept loop is part of StartAsync |
| (s) ReadyForConnections() | server.go:4023 | PORTED | src/NATS.Server/NatsServer.cs:148 (WaitForReadyAsync) | Async TaskCompletionSource-based |
| (s) Running() | server.go:1695 | PORTED | src/NATS.Server/NatsServer.cs:108 (IsShuttingDown inverted) | Derived from shutdown flag |
| (s) ID() | server.go:4036 | PORTED | src/NATS.Server/NatsServer.cs:101 (ServerId) | — |
| (s) Name() | server.go:4046 | PORTED | src/NATS.Server/NatsServer.cs:102 (ServerName) | — |
| (s) NodeName() | server.go:4041 | PORTED | src/NATS.Server/NatsServer.cs:102 | Same as ServerName |
| (s) ClusterName() | server.go:1017 | PORTED | src/NATS.Server/NatsServer.cs:121 | Dedicated cluster name getter from `NatsOptions.Cluster.Name` |
| (s) ClientURL() | server.go:1086 | PORTED | src/NATS.Server/NatsServer.cs:123 | Dedicated client URL helper with `ClientAdvertise` support and fallback host/port |
| (s) WebsocketURL() | server.go:1100 | PORTED | src/NATS.Server/NatsServer.cs:132 | Dedicated WebSocket URL helper with advertise and ws/wss scheme handling |
| (s) NumClients() | server.go:3810 | PORTED | src/NATS.Server/NatsServer.cs:103 (ClientCount) | — |
| (s) NumRoutes() | server.go:3773 | PORTED | src/NATS.Server/NatsServer.cs:148 | Dedicated route counter accessor |
| (s) NumRemotes() | server.go:3790 | PORTED | src/NATS.Server/NatsServer.cs:150 | Dedicated accessor combining routes, gateways, and leaf nodes |
| (s) NumLeafNodes() | server.go:3803 | PORTED | src/NATS.Server/NatsServer.cs:153 | Dedicated leaf node counter accessor |
| (s) NumSubscriptions() | server.go:3836 | PORTED | src/NATS.Server/NatsServer.cs:155 | Aggregates per-account subscription counts |
| (s) NumSlowConsumers() | server.go:3855 | PORTED | src/NATS.Server/ServerStats.cs:12 | Direct field access |
| (s) NumSlowConsumersClients() | server.go:3865 | PORTED | src/NATS.Server/ServerStats.cs:18 | — |
| (s) NumSlowConsumersRoutes() | server.go:3870 | PORTED | src/NATS.Server/ServerStats.cs:19 | — |
| (s) NumSlowConsumersGateways() | server.go:3875 | PORTED | src/NATS.Server/ServerStats.cs:21 | — |
| (s) NumSlowConsumersLeafs() | server.go:3880 | PORTED | src/NATS.Server/ServerStats.cs:20 | — |
| (s) NumStalledClients() | server.go:3860 | PORTED | src/NATS.Server/ServerStats.cs:17 (Stalls) | — |
| (s) NumStaleConnections() | server.go:3885 | PORTED | src/NATS.Server/ServerStats.cs:13 | — |
| (s) NumStaleConnectionsClients() | server.go:3890 | PORTED | src/NATS.Server/ServerStats.cs:22 | — |
| (s) NumStaleConnectionsRoutes() | server.go:3895 | PORTED | src/NATS.Server/ServerStats.cs:23 | — |
| (s) NumStaleConnectionsGateways() | server.go:3900 | PORTED | src/NATS.Server/ServerStats.cs:24 | — |
| (s) NumStaleConnectionsLeafs() | server.go:3905 | PORTED | src/NATS.Server/ServerStats.cs:25 | — |
| (s) GetClient() | server.go:3817 | PORTED | src/NATS.Server/NatsServer.cs:119 (GetClients enumerable) | Enumerable, not by-ID lookup |
| (s) GetLeafNode() | server.go:3829 | MISSING | — | No leaf node by-CID lookup |
| (s) ConfigTime() | server.go:3910 | PORTED | src/NATS.Server/NatsServer.cs:157 | Tracks and exposes config load/reload timestamp |
| (s) Addr() | server.go:3917 | PORTED | src/NATS.Server/NatsServer.cs:159 | Dedicated host:port address accessor |
| (s) MonitorAddr() | server.go:3927 | PORTED | src/NATS.Server/NatsServer.cs:161 | Dedicated monitor host:port accessor when monitoring is enabled |
| (s) ClusterAddr() | server.go:3937 | PARTIAL | src/NATS.Server/NatsServer.cs:166 | Dedicated cluster listen accessor exists, but .NET returns string endpoint (not TCPAddr) |
| (s) ProfilerAddr() | server.go:3947 | PORTED | src/NATS.Server/NatsServer.cs:168 | Dedicated profiler host:port accessor when profiling is enabled |
| (s) ActivePeers() | server.go:1577 | PORTED | src/NATS.Server/NatsServer.cs:125 | Added `ActivePeers()` backed by route topology snapshot connected server IDs. |
| (s) NumActiveAccounts() | server.go:1716 | PORTED | src/NATS.Server/NatsServer.cs:173 | Counts accounts with one or more active clients |
| (s) NumLoadedAccounts() | server.go:1744 | PORTED | src/NATS.Server/NatsServer.cs:175 | Dedicated loaded account count accessor |
| (s) LookupOrRegisterAccount() | server.go:1749 | PORTED | src/NATS.Server/NatsServer.cs:1260 (GetOrCreateAccount) | — |
| (s) RegisterAccount() | server.go:1762 | PORTED | src/NATS.Server/NatsServer.cs:1260 | Via GetOrCreateAccount |
| (s) SetSystemAccount() | server.go:1775 | PORTED | src/NATS.Server/NatsServer.cs constructor | Set during construction |
| (s) SystemAccount() | server.go:1798 | PORTED | src/NATS.Server/NatsServer.cs:105 | — |
| (s) GlobalAccount() | server.go:1804 | PORTED | src/NATS.Server/NatsServer.cs:50 (_globalAccount) | — |
| (s) LookupAccount() | server.go:2106 | PORTED | src/NATS.Server/NatsServer.cs:1260 | Via GetOrCreateAccount |
| (s) StartProfiler() | server.go:2941 | PORTED | src/NATS.Server/NatsServer.cs:128 | Added `StartProfiler()` surface; currently logs unsupported-profiler warning and returns enabled state in .NET runtime model. |
| (s) StartMonitoring() | server.go:3014 | PORTED | src/NATS.Server/Monitoring/MonitorServer.cs | Separate monitoring server class |
| (s) StartHTTPMonitoring() | server.go:3003 | PORTED | src/NATS.Server/Monitoring/MonitorServer.cs:140 | — |
| (s) StartHTTPSMonitoring() | server.go:3009 | PORTED | src/NATS.Server/Monitoring/MonitorServer.cs | HTTPS variant via options |
| (s) HTTPHandler() | server.go:3207 | PARTIAL | src/NATS.Server/Monitoring/MonitorServer.cs | ASP.NET Kestrel handles routing, not an http.Handler |
| (s) InProcessConn() | server.go:2876 | MISSING | — | No in-process connection support |
| (s) LameDuckShutdown() | server.go:4421 | PORTED | src/NATS.Server/NatsServer.cs:239 (LameDuckShutdownAsync) | Full LDM with grace period and duration |
| (s) DisconnectClientByID() | server.go:4742 | PORTED | src/NATS.Server/NatsServer.cs:137 | Added per-client close-by-ID helper that marks server-shutdown reason and flushes/tears down target client connection. |
| (s) LDMClientByID() | server.go:4757 | PORTED | src/NATS.Server/NatsServer.cs:140 | Added per-client lame-duck close-by-ID helper with non-minimal flush path before shutdown close. |
| (s) PortsInfo() | server.go:4247 | PORTED | src/NATS.Server/NatsServer.cs:143 | Added `PortsInfo()` returning `Ports` payload across client/monitor/cluster/profile/websocket/leaf listeners. |
| (s) String() | server.go:4050 | PORTED | src/NATS.Server/NatsServer.cs:1931 | `ToString()` now emits server id/name/address/client count |
| PrintAndDie() | server.go:1664 | NOT_APPLICABLE | — | .NET uses exceptions/logging |
| PrintServerAndExit() | server.go:1670 | NOT_APPLICABLE | — | .NET uses --version flag differently |
| ProcessCommandLineArgs() | server.go:1678 | PORTED | src/NATS.Server.Host/Program.cs:25-137 | Inline switch-based CLI parsing |
| **golang/nats-server/server/server.go — Key Unexported Helpers** | | | | |
| (s) createClient() | server.go:3245 | PORTED | src/NATS.Server/NatsServer.cs:666 (AcceptClientAsync) | Creates NatsClient, runs it |
| (s) createInternalClient() | server.go:1925 | PORTED | src/NATS.Server/InternalClient.cs:29 | InternalClient class |
| (s) createInternalSystemClient() | server.go:1910 | PORTED | src/NATS.Server/NatsServer.cs:377 | System internal client creation |
| (s) acceptConnections() | server.go:2889 | PORTED | src/NATS.Server/NatsServer.cs:502 (StartAsync accept loop) | Inline in StartAsync |
| (s) removeClient() | server.go:3708 | PORTED | src/NATS.Server/NatsServer.cs RemoveClient() via IMessageRouter | — |
| (s) saveClosedClient() | server.go:3561 | PORTED | src/NATS.Server/Monitoring/ClosedConnectionRingBuffer.cs | ClosedConnectionRingBuffer |
| (s) configureAccounts() | server.go:1230 | PARTIAL | src/NATS.Server/NatsServer.cs constructor | Basic account setup; no multi-account import/export wiring |
| (s) setInfoHostPort() | server.go:2921 | PORTED | src/NATS.Server/NatsServer.cs:496 (BuildCachedInfo) | — |
| (s) lameDuckMode() | server.go:4428 | PORTED | src/NATS.Server/NatsServer.cs:239 (LameDuckShutdownAsync) | Async version |
| (s) handleSignals() | server.go (signal.go:37) | PORTED | src/NATS.Server/NatsServer.cs:320 (HandleSignals) | Uses PosixSignalRegistration on .NET |
| (s) logPorts() | server.go:4332 | PARTIAL | src/NATS.Server/NatsServer.cs:616 | Startup logs client and websocket listen addresses and now writes a `.ports` file when `PortsFileDir` is configured. Residual gap: Go logs richer multi-listener details via `Ports` payload. |
| (s) startGoRoutine() | server.go:4070 | NOT_APPLICABLE | — | .NET uses Task.Run; no goroutine tracking needed |
| (s) readyForConnections() | server.go:3956 | PORTED | src/NATS.Server/NatsServer.cs:148 (WaitForReadyAsync) | — |
| (s) getOpts() | server.go:1206 | PORTED | src/NATS.Server/NatsServer.cs:33 (_options field) | Direct field access |
| (s) isRunning() | server.go:1700 | PORTED | src/NATS.Server/NatsServer.cs:108 | Inverted IsShuttingDown |
| (s) isShuttingDown() | server.go:2577 | PORTED | src/NATS.Server/NatsServer.cs:108 (IsShuttingDown) | — |
| (s) updateServerINFOAndSendINFOToClients() | server.go:3622 | PORTED | src/NATS.Server/NatsServer.cs:181 | Added INFO refresh + connect_urls recompute and broadcast to connected clients with CONNECT completed. |
| (s) getConnectURLs() | server.go:4120 | PORTED | src/NATS.Server/NatsServer.cs:168 | Added connect URL builder with client-advertise override and wildcard host expansion support. |
| (s) getNonLocalIPsIfHostIsIPAny() | server.go:4159 | PORTED | src/NATS.Server/NatsServer.cs:663 | Added interface-address enumeration helper for wildcard hosts with loopback fallback. |
| (s) portFile() | server.go:4307 | PORTED | src/NATS.Server/NatsServer.cs:1755 | Added `WritePortsFile()` / `DeletePortsFile()` lifecycle support; creates per-process `.ports` file under `PortsFileDir` at startup and removes it on shutdown. |
| (s) logPid() | server.go:1704 | PORTED | src/NATS.Server/NatsServer.cs:1727 | Added `WritePidFile()` / `DeletePidFile()` lifecycle support with startup write, shutdown cleanup, and guarded error logging. |
| validateAndNormalizeCompressionOption() | server.go:466 | MISSING | — | No compression option validation |
| selectCompressionMode() | server.go:559 | MISSING | — | No compression mode negotiation |
| selectS2AutoModeBasedOnRTT() | server.go:618 | MISSING | — | No RTT-based auto compression |
| needsCompression() | server.go:549 | MISSING | — | — |
| s2WriterOptions() | server.go:678 | MISSING | — | — |
| **golang/nats-server/server/client.go — Types** | | | | |
| client struct | client.go:259 | PORTED | src/NATS.Server/NatsClient.cs:35 | Core fields: cid, kind, subs, opts, flags, stats. Missing: route/gw/leaf/mqtt sub-structs, perms cache, nonce, pubKey |
| ClosedState enum | client.go:188 | PORTED | src/NATS.Server/ClosedState.cs:14 | Full enum parity with all values |
| ClientOpts struct | client.go:661 | PORTED | src/NATS.Server/Protocol/ClientOptions.cs | Echo, Verbose, Pedantic, TLS, JWT, NKey, Headers, NoResponders |
| clientFlag type | client.go:132 | PORTED | src/NATS.Server/ClientFlags.cs:9 | Core flags present; missing some (infoReceived, noReconnect, expectConnect, compressionNegotiated, didTLSFirst) |
| WriteTimeoutPolicy | client.go:239 | PORTED | src/NATS.Server/NatsClient.cs:975 | Close and TcpFlush policies |
| subscription struct | client.go:631 | PORTED | src/NATS.Server/Subscriptions/Subscription.cs | Subject, queue, sid, client, max, messageCount |
| outbound struct | client.go:349 | PARTIAL | src/NATS.Server/NatsClient.cs:47-48 | Channel-based outbound; missing: net.Buffers, sync.Cond, S2 writer, stall channel |
| readCache struct | client.go:485 | MISSING | — | No per-client subject lookup cache |
| perAccountCache struct | client.go:541 | MISSING | — | No per-account L1 cache for routes/gateways |
| pinfo struct | client.go:343 | PORTED | src/NATS.Server/NatsClient.cs:99 (_pingsOut) | Simplified: no timer struct, uses PeriodicTimer |
| permissions struct | client.go:430 | PORTED | src/NATS.Server/Auth/ClientPermissions.cs | Pub/sub allow/deny; missing: pcache (permission cache) |
| routeTarget struct | client.go:458 | MISSING | — | Route message targeting structure |
| msgDeny struct | client.go:451 | MISSING | — | Deny-based message filtering cache |
| resp struct | client.go:442 | MISSING | — | Dynamic response permission tracking |
| CLIENT/ROUTER/GATEWAY/SYSTEM/LEAF/JETSTREAM/ACCOUNT constants | client.go:44-60 | PORTED | src/NATS.Server/ClientKind.cs:8 | All client kinds present |
| isInternalClient() | client.go:63 | PORTED | src/NATS.Server/ClientKind.cs:20 (IsInternal extension) | — |
| NON_CLIENT/NATS/MQTT/WS constants | client.go:70-79 | PORTED | src/NATS.Server/ClientConnectionType.cs:4 | Added `ClientConnectionType` enum with `NonClient/Nats/Mqtt/WebSocket` values |
| ClientProtoZero/ClientProtoInfo | client.go:82-88 | PORTED | src/NATS.Server/ClientConnectionType.cs:15 | Added `ClientProtocolVersion.ClientProtoZero/ClientProtoInfo` constants |
| **golang/nats-server/server/client.go — Exported Methods** | | | | |
| (c) String() | client.go:547 | PORTED | src/NATS.Server/NatsClient.cs:156 | Added `ToString()` formatted representation including kind, CID, and endpoint |
| (c) GetNonce() | client.go:557 | PORTED | src/NATS.Server/NatsClient.cs:141 | Added `GetNonce()` accessor returning nonce bytes |
| (c) GetName() | client.go:565 | PORTED | src/NATS.Server/NatsClient.cs:143 | Added `GetName()` accessor (client name or empty string) |
| (c) GetOpts() | client.go:573 | PORTED | src/NATS.Server/NatsClient.cs:58 (ClientOpts) | — |
| (c) GetTLSConnectionState() | client.go:579 | PORTED | src/NATS.Server/NatsClient.cs:110 (TlsState) | TlsConnectionState type |
| (c) RemoteAddress() | client.go:822 | PORTED | src/NATS.Server/NatsClient.cs:85-86 (RemoteIp, RemotePort) | Separate IP and port properties |
| (c) Kind() | client.go:844 | PORTED | src/NATS.Server/NatsClient.cs:57 (Kind property) | — |
| (c) RegisterUser() | client.go:981 | MISSING | — | User registration on client not implemented |
| (c) RegisterNkeyUser() | client.go:1018 | MISSING | — | NKey user registration on client not implemented |
| (c) Account() | client.go:6204 | PORTED | src/NATS.Server/NatsClient.cs:61 (Account property) | — |
| (c) Error/Errorf/Debugf/Noticef/Tracef/Warnf | client.go:6586-6610 | PORTED | src/NATS.Server/NatsClient.cs:52 (_logger) | Uses ILogger<T> structured logging |
| (c) RateLimitErrorf/Warnf/Debugf | client.go:6610-6650 | MISSING | — | No rate-limited logging per client |
| **golang/nats-server/server/client.go — Key Unexported Methods** | | | | |
| (c) initClient() | client.go:704 | PORTED | src/NATS.Server/NatsClient.cs:116 (constructor) | CID assignment, outbound setup, subs map, trace level |
| (c) readLoop() | client.go:1358 | PORTED | src/NATS.Server/NatsClient.cs:296 (FillPipeAsync + ProcessCommandsAsync) | System.IO.Pipelines instead of goroutine + byte buffer |
| (c) writeLoop() | client.go:1274 | PORTED | src/NATS.Server/NatsClient.cs:785 (RunWriteLoopAsync) | Channel-based async write loop |
| (c) processConnect() | client.go:2167 | PORTED | src/NATS.Server/NatsClient.cs:450 (ProcessConnectAsync) | JSON deserialization, auth, account binding |
| (c) processPub() | client.go:2822 | PORTED | src/NATS.Server/NatsClient.cs:627 (ProcessPub) | Subject validation, permission check, router dispatch |
| (c) processHeaderPub() | client.go:2745 | PORTED | src/NATS.Server/NatsClient.cs:660 | HPUB handling with header size split |
| (c) parseSub() / processSub() | client.go:2898/2927 | PORTED | src/NATS.Server/NatsClient.cs:560 (ProcessSub) | Permission check, limit check, sublist insert |
| (c) processUnsub() | client.go:3366 | PORTED | src/NATS.Server/NatsClient.cs:605 (ProcessUnsub) | Max messages auto-unsub, sublist remove |
| (c) processPing() | client.go:2628 | PORTED | src/NATS.Server/NatsClient.cs:414 (Pong case in DispatchCommandAsync) | — |
| (c) processPong() | client.go:2680 | PORTED | src/NATS.Server/NatsClient.cs:419-428 | RTT calculation, pingsOut reset |
| (c) queueOutbound() | client.go:2448 | PORTED | src/NATS.Server/NatsClient.cs:140 (QueueOutbound) | Channel-based with pending bytes tracking |
| (c) flushOutbound() | client.go:1618 | PORTED | src/NATS.Server/NatsClient.cs:785 (RunWriteLoopAsync drain) | Inline in write loop |
| (c) flushSignal() | client.go:1996 | PORTED | src/NATS.Server/NatsClient.cs:197 (SignalFlushPending) | Interlocked counter |
| (c) sendPing() | client.go:2577 | PORTED | src/NATS.Server/NatsClient.cs:888 | RTT start tracking, write PING |
| (c) sendPong() | client.go:2540 | PORTED | src/NATS.Server/NatsClient.cs:397 | Write PONG bytes |
| (c) sendRTTPing() | client.go:2548 | PORTED | src/NATS.Server/NatsClient.cs:887 | RTT start tick recording |
| (c) sendErr() | client.go:2608 | PORTED | src/NATS.Server/NatsClient.cs:779 (SendErr) | — |
| (c) sendOK() | client.go:2619 | PORTED | src/NATS.Server/NatsClient.cs:344 (OkBytes write on Verbose) | — |
| (c) processPingTimer() | client.go:5537 | PORTED | src/NATS.Server/NatsClient.cs:853 (RunPingTimerAsync) | PeriodicTimer-based, stale detection |
| (c) closeConnection() | client.go:5868 | PORTED | src/NATS.Server/NatsClient.cs:834 (CloseWithReasonAsync) | Async close with reason |
| (c) markConnAsClosed() | client.go:1906 | PORTED | src/NATS.Server/NatsClient.cs:902 (MarkClosed) | Skip-flush flag, reason tracking |
| (c) deliverMsg() | client.go:3620 | PARTIAL | src/NATS.Server/NatsClient.cs:692 (SendMessage) | Basic MSG/HMSG delivery. Missing: echo check, MQTT interop, GW reply mapping, stall wait |
| (c) processInboundMsg() | client.go:4139 | PARTIAL | src/NATS.Server/NatsServer.cs:938 (ProcessMessage) | Routes to local subs; missing: route/gateway/leaf-specific inbound handlers |
| (c) processInboundClientMsg() | client.go:4166 | PARTIAL | src/NATS.Server/NatsServer.cs:938 | Core pub path ported; missing: subject mapping, GW reply mapping, NRG prefix check |
| (c) processMsgResults() | client.go:4932 | PARTIAL | src/NATS.Server/NatsServer.cs:938-1088 | Queue group selection ported; missing: route forwarding, gateway forwarding, queue name collection |
| (c) stalledWait() | client.go:3575 | PORTED | src/NATS.Server/NatsClient.cs:1017 (StallGate class) | SemaphoreSlim-based stall gate |
| (c) flushClients() | client.go:1324 | MISSING | — | Post-readLoop batch flush of producer clients (pcd) not implemented |
| (c) handleWriteTimeout() | client.go:1842 | PARTIAL | src/NATS.Server/NatsClient.cs:807-813 | Slow consumer detection on timeout; missing: retry policy for routes |
| (c) msgHeader() | client.go:3534 | PORTED | src/NATS.Server/NatsClient.cs:692 (SendMessage) | Inline MSG line construction |
| (c) msgHeaderForRouteOrLeaf() | client.go:3447 | MISSING | — | RMSG header construction for routes/leafnodes |
| (c) setPermissions() | client.go:1055 | PORTED | src/NATS.Server/NatsClient.cs:477 | Via ClientPermissions.Build(authResult.Permissions) |
| (c) pubAllowed() | client.go:4052 | PORTED | src/NATS.Server/Auth/ClientPermissions.cs | IsPublishAllowed() |
| (c) canSubscribe() | client.go:3230 | PORTED | src/NATS.Server/Auth/ClientPermissions.cs | IsSubscribeAllowed() |
| (c) setExpiration() | client.go:1243 | PORTED | src/NATS.Server/NatsClient.cs:534-557 | Auth expiry timer via Task.Delay |
| (c) authTimeout() | client.go:2383 | PORTED | src/NATS.Server/NatsClient.cs:238-253 | Auth timeout Task |
| (c) authViolation() | client.go:2398 | PORTED | src/NATS.Server/NatsClient.cs:471-473 | Auth violation error + close |
| (c) maxPayloadViolation() | client.go:2440 | PORTED | src/NATS.Server/NatsClient.cs:633-639 | Max payload check and close |
| (c) maxSubsExceeded() | client.go:2433 | PORTED | src/NATS.Server/NatsClient.cs:571-576 | — |
| (c) maxConnExceeded() | client.go:2428 | PARTIAL | src/NATS.Server/NatsServer.cs:610 | Server-level max conn check; no per-client method |
| (c) subsAtLimit() | client.go:900 | PORTED | src/NATS.Server/NatsClient.cs:571 | MaxSubs check in ProcessSub |
| (c) applyAccountLimits() | client.go:923 | PARTIAL | src/NATS.Server/NatsClient.cs:488-494 | Account client count check; missing: maxPayload/maxSubs per-account override |
| (c) registerWithAccount() | client.go:854 | PORTED | src/NATS.Server/NatsClient.cs:480-494 | Account binding during connect |
| (c) setTraceLevel() | client.go:695 | PORTED | src/NATS.Server/NatsClient.cs:68 (SetTraceMode) | — |
| (c) clientType() | client.go:599 | PORTED | src/NATS.Server/NatsClient.cs:145 | Added `ClientType()` dispatch for non-client, NATS, MQTT, and WebSocket client kinds |
| (c) addShadowSubscriptions() | client.go:3057 | MISSING | — | Account import shadow subscription system |
| (c) pruneDenyCache() / prunePubPermsCache() / pruneReplyPerms() | client.go:4007-4019 | MISSING | — | Permission cache pruning |
| (c) trackRemoteReply() / pruneRemoteTracking() | client.go:3915/3956 | MISSING | — | Reply tracking for latency |
| (c) loadMsgDenyFilter() | client.go:1265 | MISSING | — | Message-level deny filter loading |
| (c) generateClientInfoJSON() | client.go:2589 | PARTIAL | src/NATS.Server/NatsClient.cs:674 (SendInfo) | Generates INFO JSON; missing: per-client IP inclusion, connect_urls |
| (c) updateS2AutoCompressionLevel() | client.go:2723 | MISSING | — | RTT-based compression adjustment |
| (c) addToPCD() | client.go:3905 | MISSING | — | Producer client data batch tracking |
| (c) collapsePtoNB() | client.go:1608 | NOT_APPLICABLE | — | Go-specific net.Buffers collapse; .NET uses Channel<T> |
| nbPoolGet/nbPoolPut | client.go:393-423 | NOT_APPLICABLE | — | Go-specific sync.Pool; .NET uses ArrayPool/MemoryPool |
| **golang/nats-server/server/opts.go — Types** | | | | |
| Options struct | opts.go:326 | PORTED | src/NATS.Server/NatsOptions.cs:8 | Core fields present. Missing: DontListen, NoLog, NoSigs, ProxyRequired, CustomAuth, CheckConfig, JetStreamTpm, resolverPreloads |
| ClusterOpts struct | opts.go:64 | PORTED | src/NATS.Server/Configuration/ClusterOptions.cs | Core fields; missing: compression, pool size, pinned accounts, write timeout policy |
| GatewayOpts struct | opts.go:114 | PORTED | src/NATS.Server/Configuration/GatewayOptions.cs | Core fields; missing: compression, reject unknown, send qsubs buf |
| RemoteGatewayOpts struct | opts.go:145 | PORTED | src/NATS.Server/Configuration/GatewayOptions.cs:27 | — |
| LeafNodeOpts struct | opts.go:154 | PORTED | src/NATS.Server/Configuration/LeafNodeOptions.cs:22 | Core fields; missing: compression, isolate subjects, min version |
| RemoteLeafOpts struct | opts.go:218 | PORTED | src/NATS.Server/Configuration/LeafNodeOptions.cs:7 | — |
| WebsocketOpts struct | opts.go:518 | PORTED | src/NATS.Server/NatsOptions.cs:138 (WebSocketOptions) | — |
| MQTTOpts struct | opts.go:613 | PORTED | src/NATS.Server/MqttOptions.cs:8 | — |
| TLSConfigOpts struct | opts.go:790 | PARTIAL | src/NATS.Server/NatsOptions.cs:96-107 | Flat TLS fields on NatsOptions; no TLSConfigOpts class |
| OCSPConfig struct | opts.go:823 | PARTIAL | src/NATS.Server/NatsOptions.cs:110 (OcspConfig) | Basic config; missing: full OCSP mode selection |
| AuthCallout struct | opts.go:308 | PORTED | src/NATS.Server/NatsOptions.cs:234 | Added auth callout DTO (`Issuer`, `Account`, `AuthUsers`, `XKey`, `AllowedAccounts`) |
| JSLimitOpts struct | opts.go:289 | PORTED | src/NATS.Server/NatsOptions.cs:222 | Added JetStream account limit DTO fields used by options parity |
| JSTpmOpts struct | opts.go:300 | NOT_APPLICABLE | — | TPM (Trusted Platform Module) not applicable to .NET |
| ProxiesConfig struct | opts.go:832 | PORTED | src/NATS.Server/NatsOptions.cs:243 | Added proxy configuration DTO (`ProxiesConfig.Trusted` + `ProxyConfig.Key`) |
| PinnedCertSet type | opts.go:59 | PORTED | src/NATS.Server/NatsOptions.cs:106 (TlsPinnedCerts HashSet) | — |
| **golang/nats-server/server/opts.go — Exported Functions** | | | | |
| ProcessConfigFile() | opts.go:870 | PORTED | src/NATS.Server/Configuration/ConfigProcessor.cs:15 | Full config file parsing |
| ConfigureOptions() | opts.go:6023 | PORTED | src/NATS.Server.Host/Program.cs:25-137 | CLI flag parsing inline |
| MergeOptions() | opts.go:5714 | PORTED | src/NATS.Server/Configuration/ConfigReloader.cs MergeCliOverrides | — |
| RoutesFromStr() | opts.go:5797 | PORTED | src/NATS.Server/NatsOptions.cs:151 | Added parser for comma-delimited route URL strings with trimming and URI validation |
| GenTLSConfig() | opts.go:5633 | PARTIAL | src/NATS.Server/Tls/ | TLS setup exists but not as a standalone GenTLSConfig function |
| PrintTLSHelpAndDie() | opts.go:4886 | NOT_APPLICABLE | — | Go-specific CLI help |
| NoErrOnUnknownFields() | opts.go:50 | PORTED | src/NATS.Server/NatsOptions.cs:144 | Added global toggle used by config parser to suppress unknown top-level field failures |
| **golang/nats-server/server/opts.go — Exported Options Methods** | | | | |
| (o) Clone() | opts.go:715 | PORTED | src/NATS.Server/NatsOptions.cs:166 | Added deep-copy clone behavior for common collections and pinned cert set |
| (o) ProcessConfigFile() | opts.go:974 | PORTED | src/NATS.Server/Configuration/ConfigProcessor.cs:17 | — |
| (o) ProcessConfigString() | opts.go:990 | PORTED | src/NATS.Server/NatsOptions.cs:196 | Added in-memory config parse/apply path and digest computation |
| (o) ConfigDigest() | opts.go:1000 | PORTED | src/NATS.Server/NatsOptions.cs:203 | Added SHA-256-based config digest accessor |
| **golang/nats-server/server/reload.go** | | | | |
| FlagSnapshot var | reload.go:36 | PORTED | src/NATS.Server/NatsServer.cs:44-46 (_cliSnapshot, _cliFlags) | — |
| option interface | reload.go:43 | PORTED | src/NATS.Server/Configuration/IConfigChange.cs | IConfigChange with Apply, IsLoggingChange, etc. |
| noopOption / loggingOption / traceLevelOption | reload.go:77-129 | PORTED | src/NATS.Server/Configuration/IConfigChange.cs:42 (ConfigChange) | Simplified: single ConfigChange class with flags |
| traceOption / debugOption / logfileOption / etc. | reload.go:131-200+ | PARTIAL | src/NATS.Server/Configuration/ConfigReloader.cs | Diff-based; individual option types not replicated, uses property comparison |
| (s) Reload() | reload.go:1090 | PORTED | src/NATS.Server/NatsServer.cs:1623 (ReloadConfig) | Reads config file, diffs, applies |
| (s) ReloadOptions() | reload.go:1111 | PORTED | src/NATS.Server/NatsServer.cs:1633 (ReloadConfigCore) | Config diff and apply |
| reloadContext struct | reload.go:38 | MISSING | — | Reload context for cluster perms |
| applyBoolFlags() | reload.go:1185 | PARTIAL | src/NATS.Server/Configuration/ConfigReloader.cs MergeCliOverrides | CLI override merge exists; no bool flag reflection |
| **golang/nats-server/server/signal.go** | | | | |
| SetProcessName() | signal.go:32 | NOT_APPLICABLE | — | Unix-specific process name |
| (s) handleSignals() | signal.go:37 | PORTED | src/NATS.Server/NatsServer.cs:320 (HandleSignals) | PosixSignalRegistration for SIGINT/SIGTERM/SIGHUP/SIGUSR2 |
| ProcessSignal() | signal.go:89 | NOT_APPLICABLE | — | Unix-specific kill/pgrep signal dispatch |
| CommandToSignal() | signal.go:145 | NOT_APPLICABLE | — | Unix syscall.Signal mapping |
| resolvePids() | signal.go:165 | NOT_APPLICABLE | — | Unix-specific pgrep |
| **golang/nats-server/server/service.go** | | | | |
| Run() | service.go:20 | PORTED | src/NATS.Server.Host/Program.cs:235 | StartAsync + WaitForShutdown |
| isWindowsService() | service.go:26 | NOT_APPLICABLE | — | Build-tag guarded; not relevant for .NET |
### Summary Counts
| Status | Count |
|--------|-------|
| PORTED | 165 |
| PARTIAL | 19 |
| MISSING | 24 |
| NOT_APPLICABLE | 14 |
| DEFERRED | 0 |
| **Total** | **222** |
---
## 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/ -maxdepth 1 -name '*.cs' -type f -exec cat {} + | wc -l
# Re-count .NET test LOC for this module
find tests/NATS.Server.Tests/ -maxdepth 1 -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 core-server batch 4 parity closures: added `ActivePeers`, `StartProfiler`, `DisconnectClientByID`, `LDMClientByID`, `PortsInfo`, `UpdateServerINFOAndSendINFOToClients`, `GetConnectURLs`, and `GetNonLocalIPsIfHostIsIPAny` with targeted tests in `CoreServerGapParityTests`. | codex |
| 2026-02-26 | Reclassified core server PID/ports-file parity rows: validated existing startup/shutdown PID and `.ports` file lifecycle implementation and updated `logPid`/`portFile` status to PORTED; refreshed `logPorts` residual note. | codex |
| 2026-02-25 | File created with LLM analysis instructions | auto |
| 2026-02-25 | Gap inventory populated: 222 symbols analyzed (123 PORTED, 30 PARTIAL, 55 MISSING, 14 NOT_APPLICABLE) across server.go, client.go, opts.go, reload.go, signal.go, service.go, main.go | claude-opus |
| 2026-02-25 | Executed core-server batch 1 parity closures: added dedicated server URL/address/account-count/config-time/string helpers with targeted unit tests (`CoreServerGapParityTests`), and reclassified 14 rows (9 MISSING + 5 PARTIAL) to PORTED | codex |
| 2026-02-25 | Executed core-server batch 3 options parity closures: added `Ports`, `CompressionModes`, `CompressionOpts`, `RoutesFromStr`, `NoErrOnUnknownFields`, `Clone`, `ProcessConfigString`, `ConfigDigest`, and DTOs (`JSLimitOpts`, `AuthCallout`, `ProxiesConfig`) with targeted tests (`CoreServerOptionsParityBatch3Tests`) | codex |