Files
natsdotnet/gaps/protocol.md
2026-02-25 15:12:52 -05:00

23 KiB
Raw Blame History

Protocol — Gap Analysis

This file tracks what has and hasn't been ported from Go to .NET for the Protocol 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 Protocol

  • The parser is a byte-by-byte state machine. In .NET, use System.IO.Pipelines for zero-copy parsing with ReadOnlySequence<byte>.
  • Control line limit: 4096 bytes. Default max payload: 1MB.
  • Extended protocol: HPUB/HMSG (headers), RPUB/RMSG (routes) — check if these are implemented.
  • Fuzz tests in Go may map to [Theory] tests with randomized inputs in .NET.

Go Reference Files (Source)

  • golang/nats-server/server/parser.go — Protocol state machine (PUB, SUB, UNSUB, CONNECT, INFO, PING/PONG, HPUB/HMSG, RPUB/RMSG)
  • golang/nats-server/server/proto.go — Wire-level protocol writing (sendProto, sendInfo, etc.)
  • golang/nats-server/server/const.go — Protocol constants, limits (control line 4096, default max payload 1MB)

Go Reference Files (Tests)

  • golang/nats-server/server/parser_test.go
  • golang/nats-server/server/parser_fuzz_test.go
  • golang/nats-server/server/server_fuzz_test.go
  • golang/nats-server/server/subject_fuzz_test.go
  • golang/nats-server/server/split_test.go

.NET Implementation Files (Source)

  • src/NATS.Server/Protocol/NatsParser.cs — State machine
  • src/NATS.Server/Protocol/NatsProtocol.cs — Wire-level writing
  • src/NATS.Server/Protocol/NatsHeaderParser.cs
  • src/NATS.Server/Protocol/ClientCommandMatrix.cs
  • src/NATS.Server/Protocol/MessageTraceContext.cs
  • src/NATS.Server/Protocol/ProxyProtocol.cs

.NET Implementation Files (Tests)

  • tests/NATS.Server.Tests/Protocol/

Gap Inventory

golang/nats-server/server/parser.go

Go Symbol Go File:Line Status .NET Equivalent Notes
parserState (type) parser.go:24 PORTED src/NATS.Server/Protocol/NatsParser.cs Go uses an iota int type; .NET uses enum CommandType + internal awaiting-payload state
parseState (struct) parser.go:25 PARTIAL src/NATS.Server/Protocol/NatsParser.cs:37 .NET NatsParser carries equivalent per-command state via fields (_awaitingPayload, _expectedPayloadSize, etc.). Missing: argBuf/msgBuf split-buffer accumulation fields, scratch fixed-size buffer, header lazy-parse field. The .NET parser relies on System.IO.Pipelines buffering rather than explicit accumulator fields.
pubArg (struct) parser.go:37 PARTIAL src/NATS.Server/Protocol/NatsParser.cs:21 ParsedCommand covers subject, reply, size, hdr. Missing: origin, account, pacache, mapped, queues, szb, hdb, psi, trace, delivered — clustering/routing/JetStream fields not yet needed for core client protocol.
OP_STARTINFO_ARG (parser state constants) parser.go:57134 PARTIAL src/NATS.Server/Protocol/NatsParser.cs:104 All CLIENT-facing states implemented (PUB, HPUB, SUB, UNSUB, CONNECT, INFO, PING, PONG, +OK, -ERR). MISSING states: OP_A/ASUB_ARG/AUSUB_ARG (A+/A- for gateways), OP_R/OP_RS/OP_L/OP_LS (RMSG/LMSG/RS+/RS-/LS+/LS-), OP_M/MSG_ARG/HMSG_ARG (routing MSG/HMSG). See ClientCommandMatrix.cs for partial routing opcode routing.
client.parse() parser.go:136 PARTIAL src/NATS.Server/Protocol/NatsParser.cs:69 Core CLIENT-facing parse loop ported as NatsParser.TryParse() using ReadOnlySequence<byte> + SequenceReader. Missing: byte-by-byte incremental state transitions (Go uses byte-by-byte state machine; .NET scans for \r\n on each call), auth-set check before non-CONNECT op, MQTT dispatch (c.mqttParse), gateway in-CONNECT gating, ROUTER/GATEWAY/LEAF protocol dispatch (RMSG, LMSG, RS+, RS-, A+, A-).
protoSnippet() parser.go:1236 MISSING Helper that formats a quoted snippet of the protocol buffer for error messages. The .NET parser throws ProtocolViolationException with a plain message; no equivalent snippet utility exists.
client.overMaxControlLineLimit() parser.go:1251 PARTIAL src/NATS.Server/Protocol/NatsParser.cs:82 .NET checks line.Length > NatsProtocol.MaxControlLineSize and throws. Missing: kind-check (Go only enforces for CLIENT kind), client close on violation (closeConnection(MaxControlLineExceeded)), structured error with state/buffer info.
client.clonePubArg() parser.go:1267 MISSING Split-buffer scenario: clones pubArg and re-processes when payload spans two reads. Not needed in .NET because System.IO.Pipelines handles buffering, but there is no explicit equivalent.
parseState.getHeader() parser.go:1297 PARTIAL src/NATS.Server/Protocol/NatsHeaderParser.cs:25 Go lazily parses http.Header from the raw message buffer. .NET has NatsHeaderParser.Parse() which parses NATS/1.0 headers. Missing: lazy evaluation on the parsed command (header is not cached on ParsedCommand).

golang/nats-server/server/proto.go

Note: This Go file (proto.go) implements a protobuf wire-format scanner/encoder (field tags, varints, length-delimited bytes) used internally for NATS's binary internal protocol (e.g. JetStream metadata encoding). It is unrelated to the NATS text protocol.

Go Symbol Go File:Line Status .NET Equivalent Notes
errProtoInsufficient proto.go:24 MISSING Package-level error sentinel for varint parsing. No .NET equivalent (JetStream binary encoding not yet ported).
errProtoOverflow proto.go:25 MISSING Package-level error sentinel.
errProtoInvalidFieldNumber proto.go:26 MISSING Package-level error sentinel.
protoScanField() proto.go:28 MISSING Scans one protobuf field (tag + value) from a byte slice. Used by JetStream internal encoding. Not yet ported.
protoScanTag() proto.go:42 MISSING Decodes a protobuf tag (field number + wire type) from a varint. Not yet ported.
protoScanFieldValue() proto.go:61 MISSING Reads the value portion of a protobuf field by wire type. Not yet ported.
protoScanVarint() proto.go:77 MISSING 10-byte max varint decoder. Not yet ported.
protoScanBytes() proto.go:179 MISSING Length-delimited bytes field reader. Not yet ported.
protoEncodeVarint() proto.go:190 MISSING Varint encoder. Not yet ported.

golang/nats-server/server/const.go

Go Symbol Go File:Line Status .NET Equivalent Notes
Command (type) const.go:23 NOT_APPLICABLE Go string type alias for signal commands (stop/quit/reload). Managed by OS signal handling; not applicable to .NET server lifecycle which uses CancellationToken.
CommandStop, CommandQuit, CommandReopen, CommandReload const.go:2734 NOT_APPLICABLE OS signal-based server control commands. .NET uses CancellationToken + IHostedService lifecycle.
gitCommit, serverVersion (build vars) const.go:39 NOT_APPLICABLE Go linker-injected build vars. Equivalent handled by .NET assembly info / AssemblyInformationalVersionAttribute.
formatRevision() const.go:47 NOT_APPLICABLE Formats a 7-char VCS commit hash for display. Go-specific build info pattern; not needed in .NET.
init() (build info) const.go:54 NOT_APPLICABLE Reads debug.BuildInfo at startup to extract VCS revision. Not applicable to .NET.
VERSION = "2.14.0-dev" const.go:69 PARTIAL src/NATS.Server/Protocol/NatsProtocol.cs:11 .NET has Version = "0.1.0". The version string is present but does not match Go's version.
PROTO = 1 const.go:76 PORTED src/NATS.Server/Protocol/NatsProtocol.cs:12 ProtoVersion = 1
DEFAULT_PORT = 4222 const.go:79 PORTED src/NATS.Server/Protocol/NatsProtocol.cs:10 DefaultPort = 4222
RANDOM_PORT = -1 const.go:83 NOT_APPLICABLE Used in Go test helpers to request a random port. .NET tests use GetFreePort() pattern.
DEFAULT_HOST = "0.0.0.0" const.go:86 MISSING No explicit constant in .NET; server defaults to 0.0.0.0 but the constant is not named.
MAX_CONTROL_LINE_SIZE = 4096 const.go:91 PORTED src/NATS.Server/Protocol/NatsProtocol.cs:7 MaxControlLineSize = 4096
MAX_PAYLOAD_SIZE = 1MB const.go:95 PORTED src/NATS.Server/Protocol/NatsProtocol.cs:8 MaxPayloadSize = 1024 * 1024
MAX_PAYLOAD_MAX_SIZE = 8MB const.go:99 MISSING Warning threshold for max_payload setting. No .NET equivalent.
MAX_PENDING_SIZE = 64MB const.go:103 PORTED src/NATS.Server/Protocol/NatsProtocol.cs:9 MaxPendingSize = 64 * 1024 * 1024
DEFAULT_MAX_CONNECTIONS = 64K const.go:106 MISSING Default max connections cap. No .NET equivalent constant.
TLS_TIMEOUT = 2s const.go:109 MISSING TLS handshake wait time. Not yet defined in .NET options.
DEFAULT_TLS_HANDSHAKE_FIRST_FALLBACK_DELAY = 50ms const.go:114 MISSING TLS-first handshake fallback delay. Not yet implemented in .NET.
AUTH_TIMEOUT = 2s const.go:118 MISSING Authorization wait timeout. No .NET equivalent constant.
DEFAULT_PING_INTERVAL = 2min const.go:122 MISSING Ping interval for keep-alive. No .NET equivalent.
DEFAULT_PING_MAX_OUT = 2 const.go:125 MISSING Max outstanding pings before disconnect. No .NET equivalent.
CR_LF = "\r\n" const.go:128 PORTED src/NATS.Server/Protocol/NatsProtocol.cs:15 CrLf byte array.
LEN_CR_LF = 2 const.go:131 PORTED Implicit in .NET (+ 2 literals in parser). Used as literal 2 in TryReadPayload.
DEFAULT_FLUSH_DEADLINE = 10s const.go:134 MISSING Write/flush deadline. Not yet defined.
DEFAULT_HTTP_PORT = 8222 const.go:137 MISSING Monitoring port. Not yet implemented.
DEFAULT_HTTP_BASE_PATH = "/" const.go:140 MISSING Monitoring HTTP base path. Not yet implemented.
ACCEPT_MIN_SLEEP = 10ms const.go:143 MISSING Retry sleep for transient accept errors. Not yet defined.
ACCEPT_MAX_SLEEP = 1s const.go:146 MISSING Max sleep for accept errors. Not yet defined.
DEFAULT_ROUTE_CONNECT = 1s const.go:149 MISSING Route solicitation interval. Clustering not yet implemented.
DEFAULT_ROUTE_CONNECT_MAX = 30s const.go:152 MISSING Route max solicitation interval.
DEFAULT_ROUTE_RECONNECT = 1s const.go:155 MISSING Route reconnect delay.
DEFAULT_ROUTE_DIAL = 1s const.go:158 MISSING Route dial timeout.
DEFAULT_ROUTE_POOL_SIZE = 3 const.go:161 MISSING Route connection pool size.
DEFAULT_LEAF_NODE_RECONNECT = 1s const.go:164 MISSING Leaf node reconnect interval.
DEFAULT_LEAF_TLS_TIMEOUT = 2s const.go:167 MISSING Leaf node TLS timeout.
PROTO_SNIPPET_SIZE = 32 const.go:170 MISSING Size of proto snippet in parse errors. No .NET equivalent (errors use plain messages).
MAX_CONTROL_LINE_SNIPPET_SIZE = 128 const.go:172 MISSING Snippet size for control-line-too-long errors.
MAX_MSG_ARGS = 4 const.go:175 NOT_APPLICABLE Used in Go's manual arg-split loop. .NET uses SplitArgs() with stack-allocated ranges.
MAX_RMSG_ARGS = 6 const.go:178 NOT_APPLICABLE Used in RMSG parsing. RMSG not yet ported.
MAX_HMSG_ARGS = 7 const.go:180 NOT_APPLICABLE Used in HMSG parsing. HMSG routing not yet ported.
MAX_PUB_ARGS = 3 const.go:183 NOT_APPLICABLE Used in PUB arg splitting. .NET uses dynamic SplitArgs.
MAX_HPUB_ARGS = 4 const.go:186 NOT_APPLICABLE Used in HPUB arg splitting. .NET uses dynamic SplitArgs.
MAX_RSUB_ARGS = 6 const.go:189 NOT_APPLICABLE Used in RS+/LS+ subscription arg splitting. Not yet ported.
DEFAULT_MAX_CLOSED_CLIENTS = 10000 const.go:192 MISSING Closed-connection history cap. Not yet implemented.
DEFAULT_LAME_DUCK_DURATION = 2min const.go:196 MISSING Lame-duck shutdown spread duration. Not yet implemented.
DEFAULT_LAME_DUCK_GRACE_PERIOD = 10s const.go:200 MISSING Lame-duck grace period. Not yet implemented.
DEFAULT_LEAFNODE_INFO_WAIT = 1s const.go:203 MISSING Leaf node INFO wait. Not yet implemented.
DEFAULT_LEAFNODE_PORT = 7422 const.go:206 MISSING Default leaf node port. Not yet implemented.
DEFAULT_CONNECT_ERROR_REPORTS = 3600 const.go:214 MISSING Error report throttle for initial connection failures. Not yet implemented.
DEFAULT_RECONNECT_ERROR_REPORTS = 1 const.go:220 MISSING Error report throttle for reconnect failures. Not yet implemented.
DEFAULT_RTT_MEASUREMENT_INTERVAL = 1h const.go:224 MISSING RTT measurement interval. Not yet implemented.
DEFAULT_ALLOW_RESPONSE_MAX_MSGS = 1 const.go:228 MISSING Default allowed response message count for reply subjects. Not yet implemented.
DEFAULT_ALLOW_RESPONSE_EXPIRATION = 2min const.go:232 MISSING Dynamic response permission expiry. Not yet implemented.
DEFAULT_SERVICE_EXPORT_RESPONSE_THRESHOLD = 2min const.go:237 MISSING Service export response threshold. Not yet implemented (accounts/JetStream).
DEFAULT_SERVICE_LATENCY_SAMPLING = 100 const.go:241 MISSING Service latency sampling rate. Not yet implemented.
DEFAULT_SYSTEM_ACCOUNT = "$SYS" const.go:244 MISSING System account name constant. Not yet implemented.
DEFAULT_GLOBAL_ACCOUNT = "$G" const.go:247 MISSING Global account name constant. Not yet implemented.
DEFAULT_ACCOUNT_FETCH_TIMEOUT = 1900ms const.go:250 MISSING Account fetch timeout. Not yet implemented.

.NET-Only Additions (no Go counterpart in the three source files)

.NET Symbol .NET File:Line Status Notes
NatsHeaderParser src/NATS.Server/Protocol/NatsHeaderParser.cs:20 PORTED Parses NATS/1.0 header blocks. Go uses http.Header/textproto lazily from getHeader() on parseState. .NET provides an eager standalone parser.
NatsHeaders (struct) src/NATS.Server/Protocol/NatsHeaderParser.cs:6 PORTED Structured result for parsed NATS headers (status, description, key-value map). No direct Go counterpart — Go uses http.Header directly.
ClientCommandMatrix src/NATS.Server/Protocol/ClientCommandMatrix.cs:4 PORTED Encodes which inter-server opcodes (RS+, RS-, RMSG, A+, A-, LS+, LS-, LMSG) are allowed per client kind. Corresponds to the switch c.kind / switch c.op dispatch inside parse().
MessageTraceContext src/NATS.Server/Protocol/MessageTraceContext.cs:3 PORTED Captures client name/lang/version/headers from CONNECT options for trace logging. Equivalent to fields inside Go's client struct populated during processConnect.
ProxyProtocolParser src/NATS.Server/Protocol/ProxyProtocol.cs:48 PORTED PROXY protocol v1/v2 pure-parser. Corresponds to client_proxyproto.go (not in the three listed source files, but closely related to the protocol module).
ProxyAddress, ProxyParseResult, ProxyParseResultKind src/NATS.Server/Protocol/ProxyProtocol.cs:11 PORTED Supporting types for PROXY protocol parsing.
ServerInfo (class) src/NATS.Server/Protocol/NatsProtocol.cs:39 PORTED Wire-format INFO message JSON model. Corresponds to Go's Info struct in server.go.
ClientOptions (class) src/NATS.Server/Protocol/NatsProtocol.cs:98 PARTIAL Wire-format CONNECT options JSON model. Missing: nkey, sig, jwt fields present but auth handling not implemented server-side.

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/Protocol/ -name '*.cs' -type f -exec cat {} + | wc -l
    # Re-count .NET test LOC for this module
    find tests/NATS.Server.Tests/Protocol/ -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')"
    

Test Cross-Reference Summary

Go test file: parser_test.go

Go Test Status .NET Equivalent
TestParsePing PARTIAL tests/NATS.Server.Tests/ParserTests.cs: Parse_PING — covers full PING\r\n; missing byte-by-byte incremental state assertions
TestParsePong PARTIAL tests/NATS.Server.Tests/ParserTests.cs: Parse_PONG — covers full PONG\r\n; missing ping.out counter decrement test
TestParseConnect PORTED tests/NATS.Server.Tests/ParserTests.cs: Parse_CONNECT
TestParseSub PORTED tests/NATS.Server.Tests/ParserTests.cs: Parse_SUB_without_queue, Parse_SUB_with_queue
TestParsePub PARTIAL tests/NATS.Server.Tests/ParserTests.cs: Parse_PUB_with_payload, Parse_PUB_with_reply — missing overflow payload error scenario
TestParsePubSizeOverflow MISSING No .NET test for integer overflow on very large size values (>9 digits handled by ParseSize returning -1, but no explicit overflow test)
TestParsePubArg PORTED tests/NATS.Server.Tests/ParserTests.cs: Parse_PUB_argument_variations (Theory)
TestParsePubBadSize PARTIAL tests/NATS.Server.Tests/ParserTests.cs: Parse_malformed_protocol_fails covers some bad args; missing specific mpay (max payload per-client) test
TestParseHeaderPub PORTED tests/NATS.Server.Tests/ParserTests.cs: Parse_HPUB
TestParseHeaderPubArg MISSING No .NET Theory equivalent for the 32 HPUB argument variations with mixed spaces/tabs
TestParseRoutedHeaderMsg (HMSG) MISSING No .NET equivalent — ROUTER/GATEWAY HMSG parsing not yet ported
TestParseRouteMsg (RMSG) MISSING No .NET equivalent — ROUTER RMSG parsing not yet ported
TestParseMsgSpace MISSING No .NET equivalent — MSG opcode for routes not yet ported
TestShouldFail PARTIAL tests/NATS.Server.Tests/ParserTests.cs: Parse_malformed_protocol_fails — covers subset; documented behavioral differences for byte-by-byte vs prefix-scan parser
TestProtoSnippet MISSING No .NET equivalent for protoSnippet() helper
TestParseOK PORTED tests/NATS.Server.Tests/ParserTests.cs: Parse_case_insensitive includes +OK (via ParsedCommand.Simple)
TestMaxControlLine PARTIAL tests/NATS.Server.Tests/ParserTests.cs: Parse_exceeding_max_control_line_fails — covers basic enforcement; missing per-client-kind bypass (LEAF/ROUTER/GATEWAY exempt)

Go test file: split_test.go

Go Test Status .NET Equivalent
TestSplitBufferSubOp PARTIAL tests/NATS.Server.Tests/ParserTests.csSystem.IO.Pipelines handles split buffers transparently; no explicit split-state test
TestSplitBufferUnsubOp PARTIAL Same as above
TestSplitBufferPubOpTestSplitBufferPubOp5 PARTIAL Parse_PUB_with_payload covers basic case; no multi-chunk split test
TestSplitConnectArg PARTIAL No explicit argBuf accumulation test
TestSplitDanglingArgBuf NOT_APPLICABLE .NET parser has no argBuf — pipeline buffering makes this moot
TestSplitRoutedMsgArg MISSING RMSG not yet ported
TestSplitBufferMsgOp MISSING RMSG not yet ported
TestSplitBufferLeafMsgArg MISSING LMSG (leaf) not yet ported

Go test file: parser_fuzz_test.go

Go Test Status .NET Equivalent
FuzzParser MISSING No .NET fuzz test. Could be approximated with property-based testing ([Theory] with random inputs via FsCheck or Bogus).

Go test file: server_fuzz_test.go

Go Test Status .NET Equivalent
FuzzServerTLS MISSING TLS-first handshake and TLS fuzzing not yet implemented in .NET server.

Go test file: subject_fuzz_test.go

Go Test Status .NET Equivalent
FuzzSubjectsCollide MISSING SubjectsCollide() function not yet ported. .NET has SubjectMatch.IsValidSubject() and wildcard matching but not a SubjectsCollide API.

Change Log

Date Change By
2026-02-25 File created with LLM analysis instructions auto
2026-02-25 Full gap inventory populated: parser.go, proto.go, const.go; test cross-reference for all 5 Go test files claude-sonnet-4-6