Add Go-to-.NET gap inventory docs to track porting parity
This commit is contained in:
387
gaps/auth-and-accounts.md
Normal file
387
gaps/auth-and-accounts.md
Normal file
@@ -0,0 +1,387 @@
|
||||
# Auth & Accounts — Gap Analysis
|
||||
|
||||
> This file tracks what has and hasn't been ported from Go to .NET for the **Auth & Accounts** 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 Auth & Accounts
|
||||
|
||||
- `accounts.go` is the **second-largest functional file** (~4,100 lines). It manages multi-tenant isolation with per-account Sublists, service latency tracking, and subject import/export mappings.
|
||||
- The Go implementation supports 6+ auth mechanisms. Check which are implemented in .NET's `Auth/` directory.
|
||||
- JWT handling in Go includes account claims, user claims, activation tokens, and revocation lists.
|
||||
- Service imports/exports allow cross-account message delivery — check `src/NATS.Server/Imports/`.
|
||||
|
||||
---
|
||||
|
||||
## Go Reference Files (Source)
|
||||
|
||||
- `golang/nats-server/server/auth.go` — Auth mechanisms: username/password, token, NKeys (Ed25519), JWT, external auth callout, LDAP
|
||||
- `golang/nats-server/server/auth_callout.go` — External auth callout service
|
||||
- `golang/nats-server/server/nkey.go` — NKey (Ed25519) validation
|
||||
- `golang/nats-server/server/jwt.go` — JWT claims parsing, account/user claims
|
||||
- `golang/nats-server/server/accounts.go` — Multi-tenant account isolation (~4,100 lines). Each account has its own Sublist, client set, subject namespace. Supports exports/imports.
|
||||
|
||||
## Go Reference Files (Tests)
|
||||
|
||||
- `golang/nats-server/server/auth_test.go`
|
||||
- `golang/nats-server/server/auth_callout_test.go`
|
||||
- `golang/nats-server/server/nkey_test.go`
|
||||
- `golang/nats-server/server/jwt_test.go`
|
||||
- `golang/nats-server/server/accounts_test.go`
|
||||
- `golang/nats-server/server/trust_test.go`
|
||||
|
||||
## .NET Implementation Files (Source)
|
||||
|
||||
- `src/NATS.Server/Auth/` (all files including `Jwt/` subdirectory)
|
||||
- `src/NATS.Server/Imports/` (account import/export)
|
||||
|
||||
## .NET Implementation Files (Tests)
|
||||
|
||||
- `tests/NATS.Server.Tests/Auth/`
|
||||
- `tests/NATS.Server.Tests/Accounts/`
|
||||
|
||||
---
|
||||
|
||||
## Gap Inventory
|
||||
|
||||
<!-- After analysis, fill in this table. Group rows by Go source file. -->
|
||||
|
||||
### auth.go (1,697 lines)
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| Authentication interface | golang/nats-server/server/auth.go:40 | PORTED | src/NATS.Server/Auth/IAuthenticator.cs:7 | Renamed to IAuthenticator; Check(ClientAuthentication) -> Authenticate(ClientAuthContext) |
|
||||
| ClientAuthentication interface | golang/nats-server/server/auth.go:46 | PORTED | src/NATS.Server/Auth/IAuthenticator.cs:12 | Mapped to ClientAuthContext record |
|
||||
| NkeyUser struct | golang/nats-server/server/auth.go:62 | PARTIAL | src/NATS.Server/Auth/NKeyUser.cs:3 | Missing: Issued field, AllowedConnectionTypes, ProxyRequired |
|
||||
| User struct | golang/nats-server/server/auth.go:73 | PARTIAL | src/NATS.Server/Auth/User.cs:3 | Missing: AllowedConnectionTypes, ProxyRequired |
|
||||
| User.clone() | golang/nats-server/server/auth.go:85 | NOT_APPLICABLE | — | .NET uses immutable init-only records; deep clone not needed |
|
||||
| NkeyUser.clone() | golang/nats-server/server/auth.go:106 | NOT_APPLICABLE | — | .NET uses immutable init-only records; deep clone not needed |
|
||||
| SubjectPermission struct | golang/nats-server/server/auth.go:127 | PORTED | src/NATS.Server/Auth/Permissions.cs:10 | |
|
||||
| ResponsePermission struct | golang/nats-server/server/auth.go:134 | PORTED | src/NATS.Server/Auth/Permissions.cs:16 | |
|
||||
| Permissions struct | golang/nats-server/server/auth.go:141 | PORTED | src/NATS.Server/Auth/Permissions.cs:3 | |
|
||||
| RoutePermissions struct | golang/nats-server/server/auth.go:150 | MISSING | — | Route-level permissions for cluster; needed for clustering |
|
||||
| SubjectPermission.clone() | golang/nats-server/server/auth.go:156 | NOT_APPLICABLE | — | .NET uses immutable records |
|
||||
| Permissions.clone() | golang/nats-server/server/auth.go:174 | NOT_APPLICABLE | — | .NET uses immutable records |
|
||||
| checkAuthforWarnings() | golang/nats-server/server/auth.go:196 | MISSING | — | Warns about plaintext passwords at startup |
|
||||
| assignGlobalAccountToOrphanUsers() | golang/nats-server/server/auth.go:226 | MISSING | — | Auto-assigns global account to users without one |
|
||||
| validateResponsePermissions() | golang/nats-server/server/auth.go:243 | MISSING | — | Sets defaults for ResponsePermission (MaxMsgs, Expires) |
|
||||
| configureAuthorization() | golang/nats-server/server/auth.go:266 | PARTIAL | src/NATS.Server/Auth/AuthService.cs:30 | AuthService.Build covers the priority chain; missing websocket/mqtt auth config, auth callout account validation |
|
||||
| buildNkeysAndUsersFromOptions() | golang/nats-server/server/auth.go:325 | PARTIAL | src/NATS.Server/Auth/AuthService.cs:30 | User/NKey map building is in AuthService.Build; missing clone + account resolution + response permission validation |
|
||||
| checkAuthentication() | golang/nats-server/server/auth.go:365 | PARTIAL | src/NATS.Server/Auth/AuthService.cs:97 | Only CLIENT kind is implemented; ROUTER, GATEWAY, LEAF auth missing |
|
||||
| isClientAuthorized() | golang/nats-server/server/auth.go:382 | PORTED | src/NATS.Server/Auth/AuthService.cs:97 | Core flow matches; missing accountConnectEvent |
|
||||
| matchesPinnedCert() | golang/nats-server/server/auth.go:405 | MISSING | — | TLS pinned certificate validation |
|
||||
| processUserPermissionsTemplate() | golang/nats-server/server/auth.go:427 | PORTED | src/NATS.Server/Auth/Jwt/PermissionTemplates.cs:36 | Full template expansion with cartesian product |
|
||||
| processClientOrLeafAuthentication() | golang/nats-server/server/auth.go:588 | PARTIAL | src/NATS.Server/Auth/AuthService.cs:97 | Core client auth flow ported; missing leaf node auth, proxy check integration, auth callout defer, JWT src/time validation |
|
||||
| proxyCheck() | golang/nats-server/server/auth.go:1153 | PARTIAL | src/NATS.Server/Auth/ProxyAuthenticator.cs:3 | Basic proxy prefix auth exists; full NKey signature-based proxy verification missing |
|
||||
| getTLSAuthDCs() | golang/nats-server/server/auth.go:1198 | MISSING | — | Extract DC (Domain Component) from TLS cert RDN |
|
||||
| tlsMapAuthFn type | golang/nats-server/server/auth.go:1218 | NOT_APPLICABLE | — | Go function type; .NET uses delegate/lambda |
|
||||
| checkClientTLSCertSubject() | golang/nats-server/server/auth.go:1220 | PARTIAL | src/NATS.Server/Auth/TlsMapAuthenticator.cs:9 | Basic DN/CN matching ported; missing email, URI, DNS SAN matching, LDAP DN parsing |
|
||||
| dnsAltNameLabels() | golang/nats-server/server/auth.go:1316 | MISSING | — | DNS alt name label splitting for TLS |
|
||||
| dnsAltNameMatches() | golang/nats-server/server/auth.go:1321 | MISSING | — | DNS alt name matching against URLs |
|
||||
| isRouterAuthorized() | golang/nats-server/server/auth.go:1349 | MISSING | — | Cluster route authentication |
|
||||
| isGatewayAuthorized() | golang/nats-server/server/auth.go:1390 | MISSING | — | Gateway authentication |
|
||||
| registerLeafWithAccount() | golang/nats-server/server/auth.go:1425 | MISSING | — | Leaf node account registration |
|
||||
| isLeafNodeAuthorized() | golang/nats-server/server/auth.go:1443 | MISSING | — | Leaf node authentication |
|
||||
| isBcrypt() | golang/nats-server/server/auth.go:1569 | PORTED | src/NATS.Server/Auth/UserPasswordAuthenticator.cs:65 | Simplified to StartsWith("$2") check |
|
||||
| comparePasswords() | golang/nats-server/server/auth.go:1577 | PORTED | src/NATS.Server/Auth/UserPasswordAuthenticator.cs:46 | bcrypt + constant-time comparison |
|
||||
| validateAuth() | golang/nats-server/server/auth.go:1595 | MISSING | — | Top-level auth config validation |
|
||||
| validateAllowedConnectionTypes() | golang/nats-server/server/auth.go:1612 | PARTIAL | src/NATS.Server/Auth/Jwt/JwtConnectionTypes.cs:18 | Validation logic exists in JWT context; not used for config-level validation |
|
||||
| validateNoAuthUser() | golang/nats-server/server/auth.go:1631 | MISSING | — | Validates no_auth_user exists in Users/Nkeys |
|
||||
| validateProxies() | golang/nats-server/server/auth.go:1657 | MISSING | — | Validates proxy trusted keys are valid NKeys |
|
||||
| processProxiesTrustedKeys() | golang/nats-server/server/auth.go:1672 | MISSING | — | Builds NKey keypairs from proxy trusted keys |
|
||||
| getAuthErrClosedState() | golang/nats-server/server/auth.go:1688 | MISSING | — | Maps auth errors to connection close states |
|
||||
|
||||
### auth_callout.go (497 lines)
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| AuthCalloutSubject const | golang/nats-server/server/auth_callout.go:30 | MISSING | — | "$SYS.REQ.USER.AUTH" subject |
|
||||
| AuthRequestSubject const | golang/nats-server/server/auth_callout.go:31 | MISSING | — | "nats-authorization-request" |
|
||||
| AuthRequestXKeyHeader const | golang/nats-server/server/auth_callout.go:32 | MISSING | — | "Nats-Server-Xkey" header |
|
||||
| processClientOrLeafCallout() | golang/nats-server/server/auth_callout.go:36 | PARTIAL | src/NATS.Server/Auth/ExternalAuthCalloutAuthenticator.cs:3 | .NET has a simplified external auth callout via IExternalAuthClient interface; Go implementation uses internal NATS messaging ($SYS subjects), JWT encoding/decoding, XKey encryption, replay prevention — all missing from .NET |
|
||||
| fillClientInfo() | golang/nats-server/server/auth_callout.go:456 | MISSING | — | Fills jwt.ClientInformation for auth callout requests |
|
||||
| fillConnectOpts() | golang/nats-server/server/auth_callout.go:477 | MISSING | — | Fills jwt.ConnectOptions for auth callout requests |
|
||||
|
||||
### nkey.go (47 lines)
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| nonceRawLen const | golang/nats-server/server/nkey.go:23 | PORTED | src/NATS.Server/Auth/AuthService.cs:147 | Uses 11-byte raw nonce |
|
||||
| nonceLen const | golang/nats-server/server/nkey.go:24 | NOT_APPLICABLE | — | Derived constant (base64 length) |
|
||||
| NonceRequired() | golang/nats-server/server/nkey.go:28 | PORTED | src/NATS.Server/Auth/AuthService.cs:18 | Property on AuthService |
|
||||
| nonceRequired() | golang/nats-server/server/nkey.go:36 | PORTED | src/NATS.Server/Auth/AuthService.cs:34 | Checked during Build based on NKeys/TrustedKeys |
|
||||
| generateNonce() | golang/nats-server/server/nkey.go:42 | PORTED | src/NATS.Server/Auth/AuthService.cs:146 | GenerateNonce + EncodeNonce use RandomNumberGenerator.Fill |
|
||||
|
||||
### jwt.go (245 lines)
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| jwtPrefix const | golang/nats-server/server/jwt.go:29 | PORTED | src/NATS.Server/Auth/Jwt/NatsJwt.cs:17 | "eyJ" constant |
|
||||
| ReadOperatorJWT() | golang/nats-server/server/jwt.go:32 | MISSING | — | Reads operator JWT from file/inline string |
|
||||
| readOperatorJWT() | golang/nats-server/server/jwt.go:37 | MISSING | — | Internal helper for operator JWT parsing |
|
||||
| wipeSlice() | golang/nats-server/server/jwt.go:61 | NOT_APPLICABLE | — | Go-specific memory wiping; .NET has CryptographicOperations.ZeroMemory |
|
||||
| validateTrustedOperators() | golang/nats-server/server/jwt.go:70 | MISSING | — | Validates operator JWT config (keys, resolver, system account, version) |
|
||||
| validateSrc() | golang/nats-server/server/jwt.go:182 | MISSING | — | Validates user JWT source CIDR restrictions |
|
||||
| validateTimes() | golang/nats-server/server/jwt.go:204 | MISSING | — | Validates user JWT time-of-day restrictions |
|
||||
|
||||
### accounts.go (4,774 lines)
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| globalAccountName const | golang/nats-server/server/accounts.go:44 | PORTED | src/NATS.Server/Auth/Account.cs:9 | Account.GlobalAccountName = "$G" |
|
||||
| Account struct | golang/nats-server/server/accounts.go:52 | PARTIAL | src/NATS.Server/Auth/Account.cs:7 | Core fields ported (Name, SubList, limits, clients, revocations, exports, imports). Missing: stats (gw/rt/ln), gwReplyMapping, claimJWT, mappings, hasMapped, lleafs, leafClusters, js/jsLimits, nrgAccount, extAuth, defaultPerms, nameTag, tags, traceDest |
|
||||
| limits struct | golang/nats-server/server/accounts.go:127 | PARTIAL | src/NATS.Server/Auth/Account.cs:15-19 | MaxConnections, MaxSubscriptions ported; missing MaxPayload, MaxLeafNodeConnections, MaxRemoteConnections |
|
||||
| sconns struct | golang/nats-server/server/accounts.go:136 | MISSING | — | Remote server connection count tracking |
|
||||
| streamImport struct | golang/nats-server/server/accounts.go:142 | PORTED | src/NATS.Server/Imports/StreamImport.cs:7 | |
|
||||
| ClientInfoHdr const | golang/nats-server/server/accounts.go:157 | MISSING | — | "Nats-Request-Info" header for service imports |
|
||||
| serviceImport struct | golang/nats-server/server/accounts.go:160 | PORTED | src/NATS.Server/Imports/ServiceImport.cs:6 | |
|
||||
| serviceRespEntry struct | golang/nats-server/server/accounts.go:187 | MISSING | — | TTL-tracked response entries |
|
||||
| ServiceRespType enum | golang/nats-server/server/accounts.go:193 | PORTED | src/NATS.Server/Imports/ServiceResponseType.cs:3 | Singleton, Streamed, Chunked |
|
||||
| ServiceRespType.String() | golang/nats-server/server/accounts.go:203 | NOT_APPLICABLE | — | Go Stringer interface; .NET enum has ToString() |
|
||||
| exportAuth struct | golang/nats-server/server/accounts.go:217 | PORTED | src/NATS.Server/Imports/ExportAuth.cs:5 | TokenRequired, AccountPosition, ApprovedAccounts, RevokedAccounts |
|
||||
| streamExport struct | golang/nats-server/server/accounts.go:225 | PORTED | src/NATS.Server/Imports/StreamExport.cs:3 | |
|
||||
| serviceExport struct | golang/nats-server/server/accounts.go:230 | PORTED | src/NATS.Server/Imports/ServiceExport.cs:5 | |
|
||||
| serviceLatency struct | golang/nats-server/server/accounts.go:245 | PORTED | src/NATS.Server/Imports/ServiceLatency.cs:3 | |
|
||||
| exportMap struct | golang/nats-server/server/accounts.go:251 | PORTED | src/NATS.Server/Imports/ExportMap.cs:3 | |
|
||||
| importMap struct | golang/nats-server/server/accounts.go:259 | PORTED | src/NATS.Server/Imports/ImportMap.cs:5 | |
|
||||
| NewAccount() | golang/nats-server/server/accounts.go:266 | PORTED | src/NATS.Server/Auth/Account.cs:91 | Account constructor |
|
||||
| Account.String() | golang/nats-server/server/accounts.go:275 | NOT_APPLICABLE | — | Trivial Go Stringer |
|
||||
| Account.setTraceDest() | golang/nats-server/server/accounts.go:279 | MISSING | — | Message tracing destination |
|
||||
| Account.getTraceDestAndSampling() | golang/nats-server/server/accounts.go:285 | MISSING | — | Message tracing config |
|
||||
| Account.shallowCopy() | golang/nats-server/server/accounts.go:298 | MISSING | — | Shallow copy for account reload |
|
||||
| Account.nextEventID() | golang/nats-server/server/accounts.go:354 | MISSING | — | NUID-based event ID generation |
|
||||
| Account.getClientsLocked() | golang/nats-server/server/accounts.go:363 | MISSING | — | Returns client slice under lock |
|
||||
| Account.getClients() | golang/nats-server/server/accounts.go:375 | MISSING | — | Returns client slice with lock acquisition |
|
||||
| Account.getExternalClientsLocked() | golang/nats-server/server/accounts.go:384 | MISSING | — | Returns only external clients |
|
||||
| Account.updateRemoteServer() | golang/nats-server/server/accounts.go:399 | MISSING | — | Cluster remote server tracking |
|
||||
| Account.removeRemoteServer() | golang/nats-server/server/accounts.go:446 | MISSING | — | Cluster remote server removal |
|
||||
| Account.expectedRemoteResponses() | golang/nats-server/server/accounts.go:460 | MISSING | — | Expected responses from remote servers |
|
||||
| Account.clearEventing() | golang/nats-server/server/accounts.go:472 | MISSING | — | Clears event timers |
|
||||
| Account.GetName() | golang/nats-server/server/accounts.go:484 | PORTED | src/NATS.Server/Auth/Account.cs:12 | Name property |
|
||||
| Account.NumConnections() | golang/nats-server/server/accounts.go:519 | PORTED | src/NATS.Server/Auth/Account.cs:96 | ClientCount property |
|
||||
| Account.NumRemoteConnections() | golang/nats-server/server/accounts.go:528 | MISSING | — | Remote connection count (clustering) |
|
||||
| Account.NumLocalConnections() | golang/nats-server/server/accounts.go:537 | PORTED | src/NATS.Server/Auth/Account.cs:96 | ClientCount is local only |
|
||||
| Account.MaxTotalConnectionsReached() | golang/nats-server/server/accounts.go:563 | PORTED | src/NATS.Server/Auth/Account.cs:103 | AddClient checks MaxConnections |
|
||||
| Account.MaxActiveConnections() | golang/nats-server/server/accounts.go:575 | PORTED | src/NATS.Server/Auth/Account.cs:15 | MaxConnections property |
|
||||
| Account.MaxTotalLeafNodesReached() | golang/nats-server/server/accounts.go:583 | MISSING | — | Leaf node connection limits |
|
||||
| Account.NumLeafNodes() | golang/nats-server/server/accounts.go:599 | MISSING | — | Leaf node count |
|
||||
| Account.NumRemoteLeafNodes() | golang/nats-server/server/accounts.go:608 | MISSING | — | Remote leaf count |
|
||||
| Account.MaxActiveLeafNodes() | golang/nats-server/server/accounts.go:618 | MISSING | — | Leaf node limit |
|
||||
| Account.RoutedSubs() | golang/nats-server/server/accounts.go:627 | MISSING | — | Routed subscription count |
|
||||
| Account.TotalSubs() | golang/nats-server/server/accounts.go:634 | PORTED | src/NATS.Server/Auth/Account.cs:97 | SubscriptionCount |
|
||||
| Account.shouldLogMaxSubErr() | golang/nats-server/server/accounts.go:643 | MISSING | — | Rate-limited logging for max sub errors |
|
||||
| MapDest struct | golang/nats-server/server/accounts.go:660 | MISSING | — | Subject mapping destination with weight |
|
||||
| NewMapDest() | golang/nats-server/server/accounts.go:666 | MISSING | — | MapDest constructor |
|
||||
| destination struct | golang/nats-server/server/accounts.go:671 | MISSING | — | Internal mapping destination |
|
||||
| mapping struct | golang/nats-server/server/accounts.go:677 | MISSING | — | Subject mapping with weighted destinations |
|
||||
| Account.AddMapping() | golang/nats-server/server/accounts.go:686 | MISSING | — | Subject remapping (1:1) |
|
||||
| Account.AddWeightedMappings() | golang/nats-server/server/accounts.go:691 | MISSING | — | Weighted subject mappings |
|
||||
| Account.RemoveMapping() | golang/nats-server/server/accounts.go:810 | MISSING | — | Remove a subject mapping |
|
||||
| Account.hasMappings() | golang/nats-server/server/accounts.go:839 | MISSING | — | Check for subject mappings |
|
||||
| Account.selectMappedSubject() | golang/nats-server/server/accounts.go:848 | MISSING | — | Select destination via weighted mapping |
|
||||
| Account.SubscriptionInterest() | golang/nats-server/server/accounts.go:929 | PARTIAL | src/NATS.Server/Auth/Account.cs:787 | ServiceImportShadowed checks SubList.Match |
|
||||
| Account.Interest() | golang/nats-server/server/accounts.go:934 | MISSING | — | Returns count of matching subscriptions |
|
||||
| Account.addClient() | golang/nats-server/server/accounts.go:947 | PORTED | src/NATS.Server/Auth/Account.cs:103 | AddClient checks MaxConnections |
|
||||
| Account.registerLeafNodeCluster() | golang/nats-server/server/accounts.go:986 | MISSING | — | Leaf node cluster registration |
|
||||
| Account.hasLeafNodeCluster() | golang/nats-server/server/accounts.go:996 | MISSING | — | Check for leaf node cluster |
|
||||
| Account.isLeafNodeClusterIsolated() | golang/nats-server/server/accounts.go:1004 | MISSING | — | Leaf cluster isolation check |
|
||||
| Account.removeLeafNode() | golang/nats-server/server/accounts.go:1018 | MISSING | — | Remove leaf node from account |
|
||||
| Account.removeClient() | golang/nats-server/server/accounts.go:1038 | PORTED | src/NATS.Server/Auth/Account.cs:111 | RemoveClient |
|
||||
| setExportAuth() | golang/nats-server/server/accounts.go:1075 | PARTIAL | src/NATS.Server/Imports/ExportAuth.cs:5 | ExportAuth struct exists; setExportAuth helper logic partially in Account.AddServiceExport |
|
||||
| Account.AddServiceExport() | golang/nats-server/server/accounts.go:1101 | PORTED | src/NATS.Server/Auth/Account.cs:309 | AddServiceExport |
|
||||
| Account.AddServiceExportWithResponse() | golang/nats-server/server/accounts.go:1111 | PORTED | src/NATS.Server/Auth/Account.cs:309 | Included in AddServiceExport with responseType param |
|
||||
| Account.TrackServiceExport() | golang/nats-server/server/accounts.go:1163 | PARTIAL | src/NATS.Server/Auth/ServiceLatencyTracker.cs:7 | Tracker exists but wiring to export subjects via NATS internal pub missing |
|
||||
| Account.TrackServiceExportWithSampling() | golang/nats-server/server/accounts.go:1169 | PARTIAL | src/NATS.Server/Imports/ServiceLatency.cs:5 | SamplingPercentage field exists; full integration missing |
|
||||
| Account.UnTrackServiceExport() | golang/nats-server/server/accounts.go:1238 | MISSING | — | Remove latency tracking from export |
|
||||
| Account.IsExportService() | golang/nats-server/server/accounts.go:1281 | PORTED | src/NATS.Server/Auth/Account.cs:298 | HasServiceExport |
|
||||
| Account.IsExportServiceTracking() | golang/nats-server/server/accounts.go:1298 | MISSING | — | Check if latency tracking is enabled for export |
|
||||
| ServiceLatency struct | golang/nats-server/server/accounts.go:1326 | PORTED | src/NATS.Server/Imports/LatencyTracker.cs:5 | ServiceLatencyMsg |
|
||||
| ServiceLatencyType const | golang/nats-server/server/accounts.go:1340 | PORTED | src/NATS.Server/Imports/LatencyTracker.cs:8 | Type field default |
|
||||
| ServiceLatency.NATSTotalTime() | golang/nats-server/server/accounts.go:1343 | MISSING | — | Total time computation |
|
||||
| ServiceLatency.merge() | golang/nats-server/server/accounts.go:1354 | MISSING | — | Merge latency metrics |
|
||||
| sanitizeLatencyMetric() | golang/nats-server/server/accounts.go:1370 | MISSING | — | Sanitize negative latencies |
|
||||
| remoteLatency struct | golang/nats-server/server/accounts.go:1380 | MISSING | — | Remote latency for cluster |
|
||||
| Account.sendLatencyResult() | golang/nats-server/server/accounts.go:1388 | MISSING | — | Publish latency result to tracking subject |
|
||||
| Account.sendBadRequestTrackingLatency() | golang/nats-server/server/accounts.go:1401 | MISSING | — | Track bad request latency |
|
||||
| Account.sendReplyInterestLostTrackLatency() | golang/nats-server/server/accounts.go:1414 | MISSING | — | Track lost reply interest |
|
||||
| Account.sendBackendErrorTrackingLatency() | golang/nats-server/server/accounts.go:1430 | MISSING | — | Track backend error latency |
|
||||
| Account.sendTrackingLatency() | golang/nats-server/server/accounts.go:1458 | MISSING | — | Core latency tracking send |
|
||||
| updateAllClientsServiceExportResponseTime() | golang/nats-server/server/accounts.go:1527 | MISSING | — | Update client response times |
|
||||
| Account.lowestServiceExportResponseTime() | golang/nats-server/server/accounts.go:1542 | MISSING | — | Find lowest response time |
|
||||
| Account.AddServiceImportWithClaim() | golang/nats-server/server/accounts.go:1554 | PARTIAL | src/NATS.Server/Auth/Account.cs:338 | AddServiceImport exists; JWT claim-based import missing |
|
||||
| addServiceImportWithClaim() | golang/nats-server/server/accounts.go:1560 | PARTIAL | src/NATS.Server/Auth/Account.cs:338 | Core logic ported; missing claim validation, activation token handling |
|
||||
| MaxAccountCycleSearchDepth const | golang/nats-server/server/accounts.go:1593 | MISSING | — | Depth limit for cycle detection |
|
||||
| Account.serviceImportFormsCycle() | golang/nats-server/server/accounts.go:1595 | PORTED | src/NATS.Server/Auth/AccountImportExport.cs:18 | DetectCycle |
|
||||
| Account.checkServiceImportsForCycles() | golang/nats-server/server/accounts.go:1599 | PORTED | src/NATS.Server/Auth/AccountImportExport.cs:18 | DFS cycle detection |
|
||||
| Account.streamImportFormsCycle() | golang/nats-server/server/accounts.go:1627 | PORTED | src/NATS.Server/Auth/Account.cs:405 | StreamImportFormsCycle |
|
||||
| Account.hasServiceExportMatching() | golang/nats-server/server/accounts.go:1632 | PORTED | src/NATS.Server/Auth/Account.cs:298 | HasServiceExport |
|
||||
| Account.hasStreamExportMatching() | golang/nats-server/server/accounts.go:1642 | MISSING | — | Check if stream export matches subject |
|
||||
| Account.checkStreamImportsForCycles() | golang/nats-server/server/accounts.go:1651 | PORTED | src/NATS.Server/Auth/Account.cs:412 | DetectStreamImportCycle (private) |
|
||||
| Account.SetServiceImportSharing() | golang/nats-server/server/accounts.go:1686 | MISSING | — | Enable/disable service import sharing |
|
||||
| Account.AddServiceImport() | golang/nats-server/server/accounts.go:1715 | PORTED | src/NATS.Server/Auth/Account.cs:338 | |
|
||||
| Account.NumPendingReverseResponses() | golang/nats-server/server/accounts.go:1721 | PORTED | src/NATS.Server/Auth/Account.cs:772 | ReverseResponseMapCount |
|
||||
| Account.NumPendingAllResponses() | golang/nats-server/server/accounts.go:1728 | MISSING | — | Total pending across all service imports |
|
||||
| Account.NumPendingResponses() | golang/nats-server/server/accounts.go:1736 | MISSING | — | Filtered pending response count |
|
||||
| Account.NumServiceImports() | golang/nats-server/server/accounts.go:1756 | MISSING | — | Count of service imports |
|
||||
| rsiReason enum | golang/nats-server/server/accounts.go:1763 | MISSING | — | Response service import removal reason |
|
||||
| Account.removeRespServiceImport() | golang/nats-server/server/accounts.go:1772 | PARTIAL | src/NATS.Server/Imports/ResponseRouter.cs:60 | CleanupResponse exists; missing reason-based tracking/latency |
|
||||
| Account.getServiceImportForAccountLocked() | golang/nats-server/server/accounts.go:1795 | MISSING | — | Find service import by dest account + subject |
|
||||
| Account.removeServiceImport() | golang/nats-server/server/accounts.go:1812 | PORTED | src/NATS.Server/Auth/Account.cs:366 | RemoveServiceImport |
|
||||
| Account.addReverseRespMapEntry() | golang/nats-server/server/accounts.go:1858 | PORTED | src/NATS.Server/Auth/Account.cs:752 | AddReverseRespMapEntry |
|
||||
| Account.checkForReverseEntries() | golang/nats-server/server/accounts.go:1872 | PARTIAL | src/NATS.Server/Auth/Account.cs:761 | Lookup exists; missing recursive check + interest validation |
|
||||
| Account.serviceImportShadowed() | golang/nats-server/server/accounts.go:2015 | PORTED | src/NATS.Server/Auth/Account.cs:786 | ServiceImportShadowed |
|
||||
| Account.serviceImportExists() | golang/nats-server/server/accounts.go:2031 | MISSING | — | Check if service import exists by dest account + from |
|
||||
| Account.addServiceImport() | golang/nats-server/server/accounts.go:2041 | PARTIAL | src/NATS.Server/Auth/Account.cs:338 | Core add logic; missing claim/activation token handling |
|
||||
| Account.unsubscribeInternal() | golang/nats-server/server/accounts.go:2130 | MISSING | — | Internal subscription cleanup |
|
||||
| Account.subscribeServiceImportResponse() | golang/nats-server/server/accounts.go:2137 | MISSING | — | Response subscription for service import |
|
||||
| Account.subscribeInternalEx() | golang/nats-server/server/accounts.go:2141 | MISSING | — | Internal subscription with routing flag |
|
||||
| Account.addServiceImportSub() | golang/nats-server/server/accounts.go:2156 | MISSING | — | Wire up service import subscription |
|
||||
| Account.removeAllServiceImportSubs() | golang/nats-server/server/accounts.go:2190 | MISSING | — | Remove all service import subscriptions |
|
||||
| Account.addAllServiceImportSubs() | golang/nats-server/server/accounts.go:2215 | MISSING | — | Add all service import subscriptions |
|
||||
| shouldSample() | golang/nats-server/server/accounts.go:2279 | PORTED | src/NATS.Server/Imports/LatencyTracker.cs:28 | LatencyTracker.ShouldSample |
|
||||
| Account.processServiceImportResponse() | golang/nats-server/server/accounts.go:2358 | MISSING | — | Handle incoming response for service import |
|
||||
| Account.createRespWildcard() | golang/nats-server/server/accounts.go:2380 | MISSING | — | Create wildcard reply prefix |
|
||||
| isTrackedReply() | golang/nats-server/server/accounts.go:2391 | MISSING | — | Check if reply is for a tracked service |
|
||||
| Account.newServiceReply() | golang/nats-server/server/accounts.go:2398 | PARTIAL | src/NATS.Server/Imports/ResponseRouter.cs:19 | GenerateReplyPrefix exists |
|
||||
| serviceExport.setResponseThresholdTimer() | golang/nats-server/server/accounts.go:2446 | MISSING | — | Timer for response threshold |
|
||||
| serviceExport.clearResponseThresholdTimer() | golang/nats-server/server/accounts.go:2454 | MISSING | — | Clear response threshold timer |
|
||||
| serviceExport.checkExpiredResponses() | golang/nats-server/server/accounts.go:2465 | MISSING | — | Clean up expired responses |
|
||||
| Account.ServiceExportResponseThreshold() | golang/nats-server/server/accounts.go:2510 | PORTED | src/NATS.Server/Auth/Account.cs:468 | GetServiceResponseThreshold |
|
||||
| Account.SetServiceExportResponseThreshold() | golang/nats-server/server/accounts.go:2522 | PORTED | src/NATS.Server/Auth/Account.cs:461 | SetServiceResponseThreshold |
|
||||
| Account.SetServiceExportAllowTrace() | golang/nats-server/server/accounts.go:2549 | MISSING | — | Enable tracing on service export |
|
||||
| Account.addRespServiceImport() | golang/nats-server/server/accounts.go:2562 | PORTED | src/NATS.Server/Imports/ResponseRouter.cs:33 | CreateResponseImport |
|
||||
| Account.AddStreamImportWithClaim() | golang/nats-server/server/accounts.go:2598 | PARTIAL | src/NATS.Server/Auth/Account.cs:371 | AddStreamImport exists; claim validation missing |
|
||||
| Account.AddMappedStreamImport() | golang/nats-server/server/accounts.go:2629 | PORTED | src/NATS.Server/Auth/Account.cs:371 | AddStreamImport with 'to' parameter |
|
||||
| Account.isStreamImportDuplicate() | golang/nats-server/server/accounts.go:2694 | MISSING | — | Duplicate stream import detection |
|
||||
| Account.AddStreamImport() | golang/nats-server/server/accounts.go:2704 | PORTED | src/NATS.Server/Auth/Account.cs:371 | |
|
||||
| IsPublicExport var | golang/nats-server/server/accounts.go:2709 | MISSING | — | Sentinel for public export |
|
||||
| Account.AddStreamExport() | golang/nats-server/server/accounts.go:2713 | PORTED | src/NATS.Server/Auth/Account.cs:323 | AddStreamExport |
|
||||
| Account.checkStreamImportAuthorized() | golang/nats-server/server/accounts.go:2746 | PARTIAL | src/NATS.Server/Imports/ExportAuth.cs:12 | IsAuthorized checks approved accounts; missing token/claim validation |
|
||||
| Account.checkAuth() | golang/nats-server/server/accounts.go:2761 | PARTIAL | src/NATS.Server/Imports/ExportAuth.cs:12 | Basic approval; missing token validation and wildcard matching |
|
||||
| Account.checkStreamExportApproved() | golang/nats-server/server/accounts.go:2782 | PARTIAL | src/NATS.Server/Imports/ExportAuth.cs:12 | Via IsAuthorized; missing wildcard subject matching |
|
||||
| Account.checkServiceExportApproved() | golang/nats-server/server/accounts.go:2809 | PARTIAL | src/NATS.Server/Imports/ExportAuth.cs:12 | Via IsAuthorized; missing wildcard subject matching |
|
||||
| Account.getServiceExport() | golang/nats-server/server/accounts.go:2837 | PORTED | src/NATS.Server/Auth/Account.cs:267 | GetExactServiceExport |
|
||||
| Account.getWildcardServiceExport() | golang/nats-server/server/accounts.go:2849 | PORTED | src/NATS.Server/Auth/Account.cs:279 | GetWildcardServiceExport |
|
||||
| Account.streamActivationExpired() | golang/nats-server/server/accounts.go:2860 | PARTIAL | src/NATS.Server/Auth/Account.cs:613 | IsActivationExpired exists; missing auto-removal and re-check logic |
|
||||
| Account.serviceActivationExpired() | golang/nats-server/server/accounts.go:2895 | PARTIAL | src/NATS.Server/Auth/Account.cs:613 | IsActivationExpired exists; missing auto-removal |
|
||||
| Account.activationExpired() | golang/nats-server/server/accounts.go:2920 | PARTIAL | src/NATS.Server/Auth/Account.cs:620 | GetExpiredActivations + RemoveExpiredActivations |
|
||||
| isRevoked() | golang/nats-server/server/accounts.go:2929 | PORTED | src/NATS.Server/Auth/Account.cs:52 | IsUserRevoked |
|
||||
| Account.checkActivation() | golang/nats-server/server/accounts.go:2943 | PARTIAL | src/NATS.Server/Auth/Account.cs:598 | CheckActivationExpiry exists; missing issuer trust chain validation |
|
||||
| Account.isIssuerClaimTrusted() | golang/nats-server/server/accounts.go:2988 | MISSING | — | Validates issuer against account signing keys |
|
||||
| Account.checkStreamImportsEqual() | golang/nats-server/server/accounts.go:3011 | MISSING | — | Equality check for reload |
|
||||
| Account.checkStreamExportsEqual() | golang/nats-server/server/accounts.go:3036 | MISSING | — | Equality check for reload |
|
||||
| isStreamExportEqual() | golang/nats-server/server/accounts.go:3054 | MISSING | — | Stream export comparison |
|
||||
| Account.checkServiceExportsEqual() | golang/nats-server/server/accounts.go:3067 | MISSING | — | Equality check for reload |
|
||||
| isServiceExportEqual() | golang/nats-server/server/accounts.go:3085 | MISSING | — | Service export comparison |
|
||||
| isExportAuthEqual() | golang/nats-server/server/accounts.go:3121 | MISSING | — | Export auth comparison |
|
||||
| Account.checkServiceImportAuthorized() | golang/nats-server/server/accounts.go:3148 | PARTIAL | src/NATS.Server/Imports/ExportAuth.cs:12 | Via IsAuthorized; missing token validation |
|
||||
| Account.IsExpired() | golang/nats-server/server/accounts.go:3165 | PORTED | src/NATS.Server/Auth/Account.cs:523 | IsExpired property |
|
||||
| Account.expiredTimeout() | golang/nats-server/server/accounts.go:3170 | MISSING | — | Timer callback for expired accounts |
|
||||
| Account.setExpirationTimer() | golang/nats-server/server/accounts.go:3184 | PARTIAL | src/NATS.Server/Auth/Account.cs:553 | SetExpiration exists; actual Timer not created |
|
||||
| Account.clearExpirationTimer() | golang/nats-server/server/accounts.go:3189 | PARTIAL | src/NATS.Server/Auth/Account.cs:557 | ClearExpiration exists; no Timer to clear |
|
||||
| Account.checkUserRevoked() | golang/nats-server/server/accounts.go:3199 | PORTED | src/NATS.Server/Auth/Account.cs:52 | IsUserRevoked |
|
||||
| Account.failBearer() | golang/nats-server/server/accounts.go:3206 | MISSING | — | Check if account disallows bearer tokens |
|
||||
| Account.checkExpiration() | golang/nats-server/server/accounts.go:3213 | PARTIAL | src/NATS.Server/Auth/Account.cs:523 | IsExpired check exists; missing timer setup |
|
||||
| Account.hasIssuer() | golang/nats-server/server/accounts.go:3235 | MISSING | — | Check signing keys with scope |
|
||||
| Account.getLDSubject() | golang/nats-server/server/accounts.go:3249 | MISSING | — | Leaf node deny subject |
|
||||
| SetAccountResolver() | golang/nats-server/server/accounts.go:3267 | MISSING | — | Server-level resolver assignment |
|
||||
| AccountResolver() | golang/nats-server/server/accounts.go:3274 | MISSING | — | Server-level resolver getter |
|
||||
| Account.isClaimAccount() | golang/nats-server/server/accounts.go:3283 | MISSING | — | Check if account backed by JWT |
|
||||
| UpdateAccountClaims() | golang/nats-server/server/accounts.go:3290 | PARTIAL | src/NATS.Server/Auth/Account.cs:684 | UpdateAccountClaims exists; missing full claim application (exports/imports rebuild, signing keys, external auth, mappings) |
|
||||
| updateAccountClaimsWithRefresh() | golang/nats-server/server/accounts.go:3374 | PARTIAL | src/NATS.Server/Auth/Account.cs:684 | Diff-based update exists; missing export/import refresh, dependent account refresh |
|
||||
| buildInternalAccount() | golang/nats-server/server/accounts.go:3957 | MISSING | — | Build account from JWT claims |
|
||||
| buildPermissionsFromJwt() | golang/nats-server/server/accounts.go:3979 | PORTED | src/NATS.Server/Auth/JwtAuthenticator.cs:117 | Inline in JwtAuthenticator.Authenticate |
|
||||
| buildInternalNkeyUser() | golang/nats-server/server/accounts.go:4012 | MISSING | — | Build NkeyUser from JWT user claims |
|
||||
| fetchAccount() | golang/nats-server/server/accounts.go:4027 | MISSING | — | Fetch account JWT with NKey validation |
|
||||
| AccountResolver interface | golang/nats-server/server/accounts.go:4035 | PARTIAL | src/NATS.Server/Auth/Jwt/AccountResolver.cs:14 | IAccountResolver has Fetch/Store/IsReadOnly; missing Start, IsTrackingUpdate, Reload, Close |
|
||||
| MemAccResolver struct | golang/nats-server/server/accounts.go:4073 | PORTED | src/NATS.Server/Auth/Jwt/AccountResolver.cs:44 | MemAccountResolver |
|
||||
| MemAccResolver.Fetch() | golang/nats-server/server/accounts.go:4079 | PORTED | src/NATS.Server/Auth/Jwt/AccountResolver.cs:53 | FetchAsync |
|
||||
| MemAccResolver.Store() | golang/nats-server/server/accounts.go:4087 | PORTED | src/NATS.Server/Auth/Jwt/AccountResolver.cs:60 | StoreAsync |
|
||||
| URLAccResolver struct | golang/nats-server/server/accounts.go:4097 | MISSING | — | HTTP-based account resolver |
|
||||
| NewURLAccResolver() | golang/nats-server/server/accounts.go:4104 | MISSING | — | URL resolver constructor |
|
||||
| URLAccResolver.Fetch() | golang/nats-server/server/accounts.go:4123 | MISSING | — | HTTP GET for account JWT |
|
||||
| DirAccResolver struct | golang/nats-server/server/accounts.go:4143 | MISSING | — | Directory-based resolver with NATS sync |
|
||||
| DirAccResolver.Start() | golang/nats-server/server/accounts.go:4352 | MISSING | — | Start directory resolver with NATS subscriptions |
|
||||
| DirAccResolver.Fetch() | golang/nats-server/server/accounts.go:4533 | MISSING | — | Fetch from directory store |
|
||||
| DirAccResolver.Store() | golang/nats-server/server/accounts.go:4548 | MISSING | — | Store to directory |
|
||||
| NewDirAccResolver() | golang/nats-server/server/accounts.go:4574 | MISSING | — | Dir resolver constructor |
|
||||
| CacheDirAccResolver struct | golang/nats-server/server/accounts.go:4594 | MISSING | — | Caching directory resolver |
|
||||
| Server.fetch() | golang/nats-server/server/accounts.go:4599 | MISSING | — | Server-level account fetch via NATS |
|
||||
| NewCacheDirAccResolver() | golang/nats-server/server/accounts.go:4664 | MISSING | — | Cache dir resolver constructor |
|
||||
| CacheDirAccResolver.Start() | golang/nats-server/server/accounts.go:4679 | MISSING | — | Start cache resolver with NATS sync |
|
||||
|
||||
---
|
||||
|
||||
## 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/Auth/ src/NATS.Server/Imports/ -name '*.cs' -type f -exec cat {} + | wc -l
|
||||
# Re-count .NET test LOC for this module
|
||||
find tests/NATS.Server.Tests/Auth/ tests/NATS.Server.Tests/Accounts/ -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-25 | File created with LLM analysis instructions | auto |
|
||||
| 2026-02-25 | Gap inventory populated: 5 Go files analyzed (7,260 LOC), 239 symbols classified across auth.go, auth_callout.go, nkey.go, jwt.go, accounts.go. Summary: 64 PORTED, 38 PARTIAL, 128 MISSING, 9 NOT_APPLICABLE, 0 DEFERRED | auto |
|
||||
286
gaps/configuration.md
Normal file
286
gaps/configuration.md
Normal file
@@ -0,0 +1,286 @@
|
||||
# Configuration — Gap Analysis
|
||||
|
||||
> This file tracks what has and hasn't been ported from Go to .NET for the **Configuration** 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 Configuration
|
||||
|
||||
- .NET LOC **exceeds** Go LOC (4,559 vs 1,871) because .NET implements a full lexer/parser/token pipeline.
|
||||
- The Go config format is a custom format (not JSON, not YAML). It supports includes, variables, and block structures.
|
||||
- `opts.go` (in Core Server category) also participates in config — it maps CLI flags + config file to the options struct.
|
||||
- Hot reload (`reload.go` in Core Server) depends on config diffing — ensure the .NET config model supports this.
|
||||
|
||||
---
|
||||
|
||||
## Go Reference Files (Source)
|
||||
|
||||
- `golang/nats-server/conf/lex.go` — Lexer for NATS config file format
|
||||
- `golang/nats-server/conf/parse.go` — Parser for config files (supports includes, variables)
|
||||
- `golang/nats-server/conf/token.go` — Token types for config lexer
|
||||
|
||||
## Go Reference Files (Tests)
|
||||
|
||||
- `golang/nats-server/conf/lex_test.go`
|
||||
- `golang/nats-server/conf/parse_test.go`
|
||||
- `golang/nats-server/server/config_check_test.go`
|
||||
|
||||
## .NET Implementation Files (Source)
|
||||
|
||||
- `src/NATS.Server/Configuration/NatsConfLexer.cs`
|
||||
- `src/NATS.Server/Configuration/NatsConfParser.cs`
|
||||
- `src/NATS.Server/Configuration/NatsConfToken.cs`
|
||||
- `src/NATS.Server/Configuration/ConfigProcessor.cs`
|
||||
- `src/NATS.Server/Configuration/ConfigReloader.cs`
|
||||
- All other files in `src/NATS.Server/Configuration/`
|
||||
|
||||
## .NET Implementation Files (Tests)
|
||||
|
||||
- `tests/NATS.Server.Tests/Configuration/`
|
||||
|
||||
---
|
||||
|
||||
## Gap Inventory
|
||||
|
||||
<!-- After analysis, fill in this table. Group rows by Go source file. -->
|
||||
|
||||
### golang/nats-server/conf/lex.go
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `itemType` (type alias `int`) | `conf/lex.go:37` | PORTED | `src/NATS.Server/Configuration/NatsConfToken.cs:5` | Ported as `TokenType` enum |
|
||||
| `itemError` constant | `conf/lex.go:40` | PORTED | `NatsConfToken.cs:7` | `TokenType.Error` |
|
||||
| `itemNIL` constant | `conf/lex.go:41` | NOT_APPLICABLE | — | Go-only sentinel used in parser internal state; .NET uses `Token` default struct instead |
|
||||
| `itemEOF` constant | `conf/lex.go:42` | PORTED | `NatsConfToken.cs:8` | `TokenType.Eof` |
|
||||
| `itemKey` constant | `conf/lex.go:43` | PORTED | `NatsConfToken.cs:9` | `TokenType.Key` |
|
||||
| `itemText` constant | `conf/lex.go:44` | PARTIAL | — | Go emits `itemText` for comment body; .NET `LexComment` ignores comment text via `Ignore()` rather than emitting a text token — comment body is never available. Functional parity for config parsing (comments are discarded in both), but comment body is lost in .NET. |
|
||||
| `itemString` constant | `conf/lex.go:45` | PORTED | `NatsConfToken.cs:10` | `TokenType.String` |
|
||||
| `itemBool` constant | `conf/lex.go:46` | PORTED | `NatsConfToken.cs:11` | `TokenType.Bool` |
|
||||
| `itemInteger` constant | `conf/lex.go:47` | PORTED | `NatsConfToken.cs:12` | `TokenType.Integer` |
|
||||
| `itemFloat` constant | `conf/lex.go:48` | PORTED | `NatsConfToken.cs:13` | `TokenType.Float` |
|
||||
| `itemDatetime` constant | `conf/lex.go:49` | PORTED | `NatsConfToken.cs:14` | `TokenType.DateTime` |
|
||||
| `itemArrayStart` constant | `conf/lex.go:50` | PORTED | `NatsConfToken.cs:15` | `TokenType.ArrayStart` |
|
||||
| `itemArrayEnd` constant | `conf/lex.go:51` | PORTED | `NatsConfToken.cs:16` | `TokenType.ArrayEnd` |
|
||||
| `itemMapStart` constant | `conf/lex.go:52` | PORTED | `NatsConfToken.cs:17` | `TokenType.MapStart` |
|
||||
| `itemMapEnd` constant | `conf/lex.go:53` | PORTED | `NatsConfToken.cs:18` | `TokenType.MapEnd` |
|
||||
| `itemCommentStart` constant | `conf/lex.go:54` | PARTIAL | `NatsConfToken.cs:21` | Go emits `itemCommentStart` then `itemText`; .NET `LexCommentStart` emits `TokenType.Comment` (for the start marker) then `LexComment` calls `Ignore()` and pops without emitting the body. Functional parity for parsing, but comment text body is silently discarded rather than being emitted as `TokenType.Text`. |
|
||||
| `itemVariable` constant | `conf/lex.go:55` | PORTED | `NatsConfToken.cs:19` | `TokenType.Variable` |
|
||||
| `itemInclude` constant | `conf/lex.go:56` | PORTED | `NatsConfToken.cs:20` | `TokenType.Include` |
|
||||
| `stateFn` type | `conf/lex.go:84` | PORTED | `NatsConfLexer.cs:30` | Ported as `delegate LexState? LexState(NatsConfLexer lx)` — identical functional model |
|
||||
| `lexer` struct | `conf/lex.go:86` | PORTED | `NatsConfLexer.cs:6` | All fields: `_input`, `_start`, `_pos`, `_width`, `_line`, `_stack`, `_stringParts`, `_stringStateFn`, `_lstart`, `_ilstart` |
|
||||
| `item` struct | `conf/lex.go:113` | PORTED | `NatsConfToken.cs:24` | Ported as `readonly record struct Token(TokenType Type, string Value, int Line, int Position)` |
|
||||
| `(lx *lexer) nextItem()` | `conf/lex.go:120` | PORTED | `NatsConfLexer.cs:62` | Go uses a goroutine channel; .NET runs state machine eagerly in `Tokenize()` collecting all tokens into a `List<Token>` — semantically equivalent |
|
||||
| `lex(input string)` | `conf/lex.go:131` | PORTED | `NatsConfLexer.cs:48` | Ported as private constructor + `Tokenize()` factory |
|
||||
| `(lx *lexer) push()` | `conf/lex.go:143` | PORTED | `NatsConfLexer.cs:75` | `Push()` using `Stack<LexState>` |
|
||||
| `(lx *lexer) pop()` | `conf/lex.go:147` | PORTED | `NatsConfLexer.cs:77` | `Pop()` |
|
||||
| `(lx *lexer) emit()` | `conf/lex.go:157` | PORTED | `NatsConfLexer.cs:87` | `Emit()` — identical logic for computing position and clearing stringParts |
|
||||
| `(lx *lexer) emitString()` | `conf/lex.go:166` | PORTED | `NatsConfLexer.cs:106` | `EmitString()` |
|
||||
| `(lx *lexer) addCurrentStringPart()` | `conf/lex.go:181` | PORTED | `NatsConfLexer.cs:125` | `AddCurrentStringPart()` |
|
||||
| `(lx *lexer) addStringPart()` | `conf/lex.go:186` | PORTED | `NatsConfLexer.cs:131` | `AddStringPart()` |
|
||||
| `(lx *lexer) hasEscapedParts()` | `conf/lex.go:192` | PORTED | `NatsConfLexer.cs:138` | `HasEscapedParts()` |
|
||||
| `(lx *lexer) next()` | `conf/lex.go:196` | PARTIAL | `NatsConfLexer.cs:140` | Go uses `utf8.DecodeRuneInString` for multi-byte rune support; .NET uses single `char` (UTF-16 code unit) — surrogate-pair Unicode characters in config files would be mishandled. Extremely unlikely in practice for NATS config files. |
|
||||
| `(lx *lexer) ignore()` | `conf/lex.go:215` | PORTED | `NatsConfLexer.cs:160` | `Ignore()` |
|
||||
| `(lx *lexer) backup()` | `conf/lex.go:221` | PORTED | `NatsConfLexer.cs:166` | `Backup()` |
|
||||
| `(lx *lexer) peek()` | `conf/lex.go:229` | PORTED | `NatsConfLexer.cs:175` | `Peek()` |
|
||||
| `(lx *lexer) errorf()` | `conf/lex.go:238` | PARTIAL | `NatsConfLexer.cs:182` | Go version escapes rune arguments; .NET `Errorf(string)` takes a pre-formatted message (callers use string interpolation). Character escaping is done inline at call sites via `EscapeSpecial()`. Functionally equivalent. |
|
||||
| `lexTop` | `conf/lex.go:257` | PORTED | `NatsConfLexer.cs:202` | `LexTop` static method |
|
||||
| `lexTopValueEnd` | `conf/lex.go:296` | PORTED | `NatsConfLexer.cs:247` | `LexTopValueEnd` static method |
|
||||
| `lexBlockStart` | `conf/lex.go:321` | PORTED | `NatsConfLexer.cs:291` | `LexBlockStart` static method |
|
||||
| `lexBlockValueEnd` | `conf/lex.go:363` | PORTED | `NatsConfLexer.cs:338` | `LexBlockValueEnd` static method |
|
||||
| `lexBlockEnd` | `conf/lex.go:392` | PORTED | `NatsConfLexer.cs:388` | `LexBlockEnd` static method |
|
||||
| `lexKeyStart` | `conf/lex.go:422` | PORTED | `NatsConfLexer.cs:438` | `LexKeyStart` static method |
|
||||
| `lexDubQuotedKey` | `conf/lex.go:443` | PORTED | `NatsConfLexer.cs:469` | `LexDubQuotedKey` static method |
|
||||
| `lexQuotedKey` | `conf/lex.go:460` | PORTED | `NatsConfLexer.cs:494` | `LexQuotedKey` static method |
|
||||
| `(lx *lexer) keyCheckKeyword()` | `conf/lex.go:480` | PORTED | `NatsConfLexer.cs:519` | `KeyCheckKeyword()` — instance method |
|
||||
| `lexIncludeStart` | `conf/lex.go:495` | PORTED | `NatsConfLexer.cs:537` | `LexIncludeStart` static method |
|
||||
| `lexIncludeQuotedString` | `conf/lex.go:507` | PORTED | `NatsConfLexer.cs:549` | `LexIncludeQuotedString` static method |
|
||||
| `lexIncludeDubQuotedString` | `conf/lex.go:525` | PORTED | `NatsConfLexer.cs:569` | `LexIncludeDubQuotedString` static method |
|
||||
| `lexIncludeString` | `conf/lex.go:541` | PORTED | `NatsConfLexer.cs:589` | `LexIncludeString` static method |
|
||||
| `lexInclude` | `conf/lex.go:559` | PORTED | `NatsConfLexer.cs:611` | `LexInclude` static method |
|
||||
| `lexKey` | `conf/lex.go:587` | PORTED | `NatsConfLexer.cs:646` | `LexKey` static method |
|
||||
| `lexKeyEnd` | `conf/lex.go:601` | PORTED | `NatsConfLexer.cs:671` | `LexKeyEnd` static method |
|
||||
| `lexValue` | `conf/lex.go:621` | PORTED | `NatsConfLexer.cs:695` | `LexValue` static method |
|
||||
| `lexArrayValue` | `conf/lex.go:665` | PORTED | `NatsConfLexer.cs:745` | `LexArrayValue` static method |
|
||||
| `lexArrayValueEnd` | `conf/lex.go:696` | PORTED | `NatsConfLexer.cs:788` | `LexArrayValueEnd` static method |
|
||||
| `lexArrayEnd` | `conf/lex.go:723` | PORTED | `NatsConfLexer.cs:832` | `LexArrayEnd` static method |
|
||||
| `lexMapKeyStart` | `conf/lex.go:732` | PORTED | `NatsConfLexer.cs:839` | `LexMapKeyStart` static method |
|
||||
| `lexMapQuotedKey` | `conf/lex.go:772` | PORTED | `NatsConfLexer.cs:906` | `LexMapQuotedKey` static method |
|
||||
| `lexMapDubQuotedKey` | `conf/lex.go:784` | PORTED | `NatsConfLexer.cs:925` | `LexMapDubQuotedKey` static method |
|
||||
| `lexMapKey` | `conf/lex.go:799` | PORTED | `NatsConfLexer.cs:944` | `LexMapKey` static method |
|
||||
| `lexMapKeyEnd` | `conf/lex.go:815` | PORTED | `NatsConfLexer.cs:972` | `LexMapKeyEnd` static method |
|
||||
| `lexMapValue` | `conf/lex.go:833` | PORTED | `NatsConfLexer.cs:990` | `LexMapValue` static method |
|
||||
| `lexMapValueEnd` | `conf/lex.go:850` | PORTED | `NatsConfLexer.cs:1013` | `LexMapValueEnd` static method |
|
||||
| `lexMapEnd` | `conf/lex.go:875` | PORTED | `NatsConfLexer.cs:1059` | `LexMapEnd` static method |
|
||||
| `(lx *lexer) isBool()` | `conf/lex.go:884` | PORTED | `NatsConfLexer.cs:1066` | `IsBool()` instance method |
|
||||
| `(lx *lexer) isVariable()` | `conf/lex.go:892` | PORTED | `NatsConfLexer.cs:1072` | `IsVariable()` instance method |
|
||||
| `lexQuotedString` | `conf/lex.go:906` | PORTED | `NatsConfLexer.cs:1088` | `LexQuotedString` static method |
|
||||
| `lexDubQuotedString` | `conf/lex.go:928` | PORTED | `NatsConfLexer.cs:1114` | `LexDubQuotedString` static method |
|
||||
| `lexString` | `conf/lex.go:951` | PORTED | `NatsConfLexer.cs:1146` | `LexString` static method |
|
||||
| `lexBlock` | `conf/lex.go:986` | PORTED | `NatsConfLexer.cs:1193` | `LexBlock` static method |
|
||||
| `lexStringEscape` | `conf/lex.go:1021` | PORTED | `NatsConfLexer.cs:1235` | `LexStringEscape` static method |
|
||||
| `lexStringBinary` | `conf/lex.go:1043` | PORTED | `NatsConfLexer.cs:1250` | `LexStringBinary` static method — uses `Convert.FromHexString` + `Latin1.GetString` |
|
||||
| `lexNumberOrDateOrStringOrIPStart` | `conf/lex.go:1065` | PORTED | `NatsConfLexer.cs:1276` | `LexNumberOrDateOrStringOrIPStart` static method |
|
||||
| `lexNumberOrDateOrStringOrIP` | `conf/lex.go:1079` | PORTED | `NatsConfLexer.cs:1292` | `LexNumberOrDateOrStringOrIP` static method |
|
||||
| `lexConvenientNumber` | `conf/lex.go:1106` | PORTED | `NatsConfLexer.cs:1333` | `LexConvenientNumber` static method |
|
||||
| `lexDateAfterYear` | `conf/lex.go:1124` | PORTED | `NatsConfLexer.cs:1354` | `LexDateAfterYear` static method |
|
||||
| `lexNegNumberStart` | `conf/lex.go:1152` | PORTED | `NatsConfLexer.cs:1385` | `LexNegNumberStart` static method |
|
||||
| `lexNegNumber` | `conf/lex.go:1165` | PORTED | `NatsConfLexer.cs:1401` | `LexNegNumber` static method |
|
||||
| `lexFloatStart` | `conf/lex.go:1182` | PORTED | `NatsConfLexer.cs:1424` | `LexFloatStart` static method |
|
||||
| `lexFloat` | `conf/lex.go:1193` | PORTED | `NatsConfLexer.cs:1435` | `LexFloat` static method |
|
||||
| `lexIPAddr` | `conf/lex.go:1210` | PORTED | `NatsConfLexer.cs:1454` | `LexIPAddr` static method |
|
||||
| `lexCommentStart` | `conf/lex.go:1222` | PARTIAL | `NatsConfLexer.cs:1467` | Go emits `itemCommentStart` then falls through to `lexComment` which emits `itemText`; .NET emits `TokenType.Comment` then `LexComment` calls `Ignore()` silently discarding the body. Comment text body is unavailable in .NET. |
|
||||
| `lexComment` | `conf/lex.go:1231` | PARTIAL | `NatsConfLexer.cs:1474` | Go emits `itemText` with comment body; .NET calls `Ignore()` — comment body is silently discarded. Same functional effect for config parsing. |
|
||||
| `lexSkip` | `conf/lex.go:1242` | PORTED | `NatsConfLexer.cs:1489` | `LexSkip` static method — identical logic |
|
||||
| `isNumberSuffix()` | `conf/lex.go:1250` | PORTED | `NatsConfLexer.cs:197` | `IsNumberSuffix()` static method |
|
||||
| `isKeySeparator()` | `conf/lex.go:1255` | PORTED | `NatsConfLexer.cs:195` | `IsKeySeparator()` static method |
|
||||
| `isWhitespace()` | `conf/lex.go:1261` | PORTED | `NatsConfLexer.cs:191` | `IsWhitespace()` static method |
|
||||
| `isNL()` | `conf/lex.go:1265` | PORTED | `NatsConfLexer.cs:193` | `IsNL()` static method |
|
||||
| `(itemType) String()` | `conf/lex.go:1269` | PORTED | — | Go's stringer is used for debugging; .NET `TokenType` is an enum (has `.ToString()` built in) — functionally equivalent |
|
||||
| `(item) String()` | `conf/lex.go:1309` | PORTED | — | Go debug helper; .NET `Token` is a record struct with auto-generated `ToString()` |
|
||||
| `escapeSpecial()` | `conf/lex.go:1313` | PORTED | `NatsConfLexer.cs:1495` | `EscapeSpecial()` static method — .NET version also handles `\r`, `\t`, and EOF |
|
||||
|
||||
---
|
||||
|
||||
### golang/nats-server/conf/parse.go
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `_EMPTY_` constant | `conf/parse.go:40` | NOT_APPLICABLE | — | Go-only string constant alias for `""` |
|
||||
| `parser` struct | `conf/parse.go:42` | PORTED | `NatsConfParser.cs:88` | Ported as private `ParserState` class inside `NatsConfParser`. All fields present: `mapping`, `lx`/`_tokens`, `ctx`, `ctxs`, `keys`, `fp`/`_baseDir`. Missing: `ikeys` (pedantic item keys) and `pedantic` flag — see below. |
|
||||
| `Parse()` | `conf/parse.go:71` | PORTED | `NatsConfParser.cs:29` | `NatsConfParser.Parse(string)` — identical signature and semantics |
|
||||
| `ParseWithChecks()` | `conf/parse.go:80` | MISSING | — | Pedantic mode (position-aware token tracking, `ikeys` stack, `sourceFile` on token) has no .NET equivalent. Config validation tools using pedantic mode (e.g., `nats-server --config-check`) cannot be supported without this. |
|
||||
| `ParseFile()` | `conf/parse.go:89` | PORTED | `NatsConfParser.cs:40` | `NatsConfParser.ParseFile(string)` |
|
||||
| `ParseFileWithChecks()` | `conf/parse.go:103` | MISSING | — | Pedantic mode file variant — not ported. Same gap as `ParseWithChecks`. |
|
||||
| `cleanupUsedEnvVars()` | `conf/parse.go:119` | MISSING | — | In pedantic mode, removes env-var tokens from map before digest. .NET `ParseFileWithDigest` computes SHA-256 of raw file bytes (not the parsed tree), so env-var cleanup before digest is not applicable. Functionally different approach but both produce a stable digest. |
|
||||
| `ParseFileWithChecksDigest()` | `conf/parse.go:135` | PARTIAL | `NatsConfParser.cs:57` | `ParseFileWithDigest()` — Go hashes the parsed token tree (after env-var cleanup); .NET hashes the raw file bytes. Both produce a `"sha256:<hex>"` digest stable across re-reads. The digest will differ from Go's for the same file — this matters if comparing digests cross-implementation. |
|
||||
| `token` struct | `conf/parse.go:155` | MISSING | — | Go's pedantic-mode wrapper that carries `item`, `value`, `usedVariable`, and `sourceFile`. .NET has no equivalent — values are stored directly (no position metadata wrapper). |
|
||||
| `(t *token) MarshalJSON()` | `conf/parse.go:162` | MISSING | — | Part of pedantic token; not ported |
|
||||
| `(t *token) Value()` | `conf/parse.go:166` | MISSING | — | Part of pedantic token; not ported |
|
||||
| `(t *token) Line()` | `conf/parse.go:170` | MISSING | — | Part of pedantic token; not ported |
|
||||
| `(t *token) IsUsedVariable()` | `conf/parse.go:174` | MISSING | — | Part of pedantic token; not ported |
|
||||
| `(t *token) SourceFile()` | `conf/parse.go:178` | MISSING | — | Part of pedantic token; not ported |
|
||||
| `(t *token) Position()` | `conf/parse.go:182` | MISSING | — | Part of pedantic token; not ported |
|
||||
| `newParser()` | `conf/parse.go:186` | PORTED | `NatsConfParser.cs:105` | `ParserState` constructors |
|
||||
| `parse()` | `conf/parse.go:199` | PORTED | `NatsConfParser.cs:118` | `ParserState.Run()` — identical loop structure |
|
||||
| `parseEnv()` | `conf/parse.go:207` | PORTED | `NatsConfParser.cs:75` | `ParseEnvValue()` static method — same synthetic `pk=<value>` trick |
|
||||
| `(p *parser) parse()` | `conf/parse.go:216` | PORTED | `NatsConfParser.cs:118` | `ParserState.Run()` |
|
||||
| `(p *parser) next()` | `conf/parse.go:238` | PORTED | `NatsConfParser.cs:142` | `ParserState.Next()` |
|
||||
| `(p *parser) pushContext()` | `conf/parse.go:242` | PORTED | `NatsConfParser.cs:152` | `PushContext()` |
|
||||
| `(p *parser) popContext()` | `conf/parse.go:247` | PORTED | `NatsConfParser.cs:158` | `PopContext()` |
|
||||
| `(p *parser) pushKey()` | `conf/parse.go:258` | PORTED | `NatsConfParser.cs:171` | `PushKey()` |
|
||||
| `(p *parser) popKey()` | `conf/parse.go:262` | PORTED | `NatsConfParser.cs:173` | `PopKey()` |
|
||||
| `(p *parser) pushItemKey()` | `conf/parse.go:272` | MISSING | — | Pedantic-mode only; no .NET equivalent |
|
||||
| `(p *parser) popItemKey()` | `conf/parse.go:276` | MISSING | — | Pedantic-mode only; no .NET equivalent |
|
||||
| `(p *parser) processItem()` | `conf/parse.go:286` | PORTED | `NatsConfParser.cs:205` | `ProcessItem()` — handles all token types including variable, include, map, array |
|
||||
| `(p *parser) lookupVariable()` | `conf/parse.go:462` | PORTED | `NatsConfParser.cs:344` | `ResolveVariable()` — block scoping, env var lookup, cycle detection, bcrypt prefix all ported |
|
||||
| `(p *parser) setValue()` | `conf/parse.go:500` | PORTED | `NatsConfParser.cs:185` | `SetValue()` — array and map context handling |
|
||||
| `pkey` constant | `conf/parse.go:452` | PORTED | `NatsConfParser.cs:77` | Used in `ParseEnvValue` synthetic input (`"pk={value}"`) |
|
||||
| `bcryptPrefix` constant | `conf/parse.go:455` | PARTIAL | `NatsConfParser.cs:20` | Go checks prefix `"2a$"`; .NET checks both `"2a$"` and `"2b$"` — .NET is a superset (handles both bcrypt variants) |
|
||||
|
||||
---
|
||||
|
||||
### golang/nats-server/conf/token.go (does not exist)
|
||||
|
||||
The `token.go` file listed in the gap analysis instructions does not exist in the Go source tree. The `item` and `itemType` declarations live in `lex.go`. The `token` struct (pedantic wrapper) lives in `parse.go`. The .NET `NatsConfToken.cs` consolidates both the token type enum (`TokenType`) and the token value record (`Token`).
|
||||
|
||||
---
|
||||
|
||||
### .NET-only Configuration Files (no direct Go counterpart in conf/)
|
||||
|
||||
These .NET files port functionality from `golang/nats-server/server/opts.go` and `golang/nats-server/server/reload.go` which are tracked under the Core Server category, but the implementations live in the Configuration folder:
|
||||
|
||||
| .NET File | Go Reference | Status | Notes |
|
||||
|-----------|:-------------|--------|-------|
|
||||
| `src/NATS.Server/Configuration/ConfigProcessor.cs` | `server/opts.go` (processConfigFileLine, ~line 1050) | PORTED | Maps parsed dict to `NatsOptions`. Covers: listen, port, host, logging, limits, monitoring, TLS, auth, cluster, gateway, leafnode, JetStream, MQTT config keys |
|
||||
| `src/NATS.Server/Configuration/ConfigReloader.cs` | `server/reload.go` | PORTED | `Diff()`, `Validate()`, `MergeCliOverrides()`, `ApplyDiff()`, `ReloadAsync()`, `ReloadFromOptionsAsync()`, `ApplyClusterConfigChanges()`, `ApplyLoggingChanges()`, `PropagateAuthChanges()`, `ReloadTlsCertificates()`, `ReloadTlsCertificate()`, `ApplyJetStreamConfigChanges()` |
|
||||
| `src/NATS.Server/Configuration/IConfigChange.cs` | `server/reload.go:42–74` | PORTED | `IConfigChange` interface + `ConfigChange` implementation |
|
||||
| `src/NATS.Server/Configuration/ClusterOptions.cs` | `server/opts.go` (ClusterOpts) | PORTED | Cluster options struct |
|
||||
| `src/NATS.Server/Configuration/GatewayOptions.cs` | `server/opts.go` (GatewayOpts, RemoteGatewayOpts) | PORTED | Gateway options + `RemoteGatewayOptions` |
|
||||
| `src/NATS.Server/Configuration/LeafNodeOptions.cs` | `server/opts.go` (LeafNodeOpts, RemoteLeafOpts) | PORTED | LeafNode options + `RemoteLeafOptions` |
|
||||
| `src/NATS.Server/Configuration/JetStreamOptions.cs` | `server/opts.go` (JetStreamConfig) | PORTED | JetStream options struct |
|
||||
| `src/NATS.Server/Configuration/RouteCompression.cs` | `server/opts.go` (Compression enum) | PORTED | `RouteCompression` enum (None, S2) |
|
||||
| `src/NATS.Server/Configuration/SignalHandler.cs` | `server/signal_unix.go` | PORTED | SIGHUP handler via `PosixSignalRegistration` |
|
||||
|
||||
---
|
||||
|
||||
### conf/fuzz.go
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `Fuzz()` | `conf/fuzz.go:18` | NOT_APPLICABLE | — | Go fuzz test entry point (`//go:build gofuzz` build tag). .NET has its own fuzzing infrastructure (SharpFuzz / libFuzzer) but no equivalent fuzz target exists. Low priority. |
|
||||
|
||||
---
|
||||
|
||||
## 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/Configuration/ -name '*.cs' -type f -exec cat {} + | wc -l
|
||||
# Re-count .NET test LOC for this module
|
||||
find tests/NATS.Server.Tests/Configuration/ -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-25 | File created with LLM analysis instructions | auto |
|
||||
| 2026-02-25 | Full gap inventory completed: read all Go source files (lex.go, parse.go) and all .NET Configuration files; classified 100+ symbols | claude-sonnet-4-6 |
|
||||
386
gaps/core-server.md
Normal file
386
gaps/core-server.md
Normal file
@@ -0,0 +1,386 @@
|
||||
# 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 | PARTIAL | src/NATS.Server.Host/Program.cs | No dedicated --help usage string; CLI flags are handled inline |
|
||||
| **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 | MISSING | — | Not implemented; no /ports output support |
|
||||
| Compression constants | server.go:437-446 | MISSING | — | S2 compression mode constants not defined in core server |
|
||||
| CompressionOpts struct | server.go:97 (opts.go) | MISSING | — | No compression options type in .NET |
|
||||
| **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 | PARTIAL | src/NATS.Server/NatsServer.cs:110 (ClusterListen) | Only listen endpoint; no cluster name getter |
|
||||
| (s) ClientURL() | server.go:1086 | MISSING | — | No dedicated method to return client connect URL |
|
||||
| (s) WebsocketURL() | server.go:1100 | MISSING | — | No dedicated websocket URL getter |
|
||||
| (s) NumClients() | server.go:3810 | PORTED | src/NATS.Server/NatsServer.cs:103 (ClientCount) | — |
|
||||
| (s) NumRoutes() | server.go:3773 | PARTIAL | src/NATS.Server/ServerStats.cs:14 (Routes field) | Stats counter exists; no lock-safe method like Go |
|
||||
| (s) NumRemotes() | server.go:3790 | MISSING | — | — |
|
||||
| (s) NumLeafNodes() | server.go:3803 | PARTIAL | src/NATS.Server/ServerStats.cs:16 (Leafs field) | Stats counter; no lock-safe count method |
|
||||
| (s) NumSubscriptions() | server.go:3836 | MISSING | — | No aggregated subscription count method |
|
||||
| (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 | MISSING | — | No config time tracking exposed |
|
||||
| (s) Addr() | server.go:3917 | PARTIAL | src/NATS.Server/NatsServer.cs:104 (Port) | Port exposed but not full net.Addr |
|
||||
| (s) MonitorAddr() | server.go:3927 | MISSING | — | Monitoring managed by MonitorServer separately |
|
||||
| (s) ClusterAddr() | server.go:3937 | PARTIAL | src/NATS.Server/NatsServer.cs:110 (ClusterListen string) | String, not TCPAddr |
|
||||
| (s) ProfilerAddr() | server.go:3947 | MISSING | — | No profiler address getter |
|
||||
| (s) ActivePeers() | server.go:1577 | MISSING | — | Cluster peer enumeration not in core |
|
||||
| (s) NumActiveAccounts() | server.go:1716 | MISSING | — | No active account count method |
|
||||
| (s) NumLoadedAccounts() | server.go:1744 | PARTIAL | src/NATS.Server/NatsServer.cs:123 (GetAccounts) | Enumerable, no count method |
|
||||
| (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 | MISSING | — | No built-in profiler; .NET uses dotnet-trace/counters |
|
||||
| (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 | MISSING | — | No per-client disconnect by ID |
|
||||
| (s) LDMClientByID() | server.go:4757 | MISSING | — | No per-client lame duck by ID |
|
||||
| (s) PortsInfo() | server.go:4247 | MISSING | — | No Ports struct output |
|
||||
| (s) String() | server.go:4050 | MISSING | — | No server string representation |
|
||||
| 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 | Logs port at startup; no ports file |
|
||||
| (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 | MISSING | — | No dynamic INFO update broadcast to existing clients |
|
||||
| (s) getConnectURLs() | server.go:4120 | MISSING | — | No connect URL resolution for clustering |
|
||||
| (s) getNonLocalIPsIfHostIsIPAny() | server.go:4159 | MISSING | — | IP enumeration for advertise not implemented |
|
||||
| (s) portFile() | server.go:4307 | MISSING | — | No ports file creation |
|
||||
| (s) logPid() | server.go:1704 | PARTIAL | src/NATS.Server/NatsServer.cs | PID file support present in options but write logic minimal |
|
||||
| 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 | PARTIAL | src/NATS.Server/NatsClient.cs:107 (IsWebSocket) | WebSocket bool exists; no explicit MQTT/NATS/NON_CLIENT sub-type enum |
|
||||
| ClientProtoZero/ClientProtoInfo | client.go:82-88 | MISSING | — | Client protocol version constants not defined |
|
||||
| **golang/nats-server/server/client.go — Exported Methods** | | | | |
|
||||
| (c) String() | client.go:547 | MISSING | — | No formatted string representation |
|
||||
| (c) GetNonce() | client.go:557 | PARTIAL | src/NATS.Server/NatsClient.cs:43 (_nonce field) | Field exists but no public getter |
|
||||
| (c) GetName() | client.go:565 | PARTIAL | src/NATS.Server/NatsClient.cs:58 (ClientOpts?.Name) | Via ClientOpts property |
|
||||
| (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 | PARTIAL | src/NATS.Server/NatsClient.cs:107 (IsWebSocket) | Bool for WS; no MQTT/NATS sub-type dispatch |
|
||||
| (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 | MISSING | — | External auth callout configuration |
|
||||
| JSLimitOpts struct | opts.go:289 | MISSING | — | Per-account JetStream limit options |
|
||||
| JSTpmOpts struct | opts.go:300 | NOT_APPLICABLE | — | TPM (Trusted Platform Module) not applicable to .NET |
|
||||
| ProxiesConfig struct | opts.go:832 | MISSING | — | Proxy configuration |
|
||||
| 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 | MISSING | — | Parse comma-separated route URLs |
|
||||
| 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 | MISSING | — | Config parsing error control |
|
||||
| **golang/nats-server/server/opts.go — Exported Options Methods** | | | | |
|
||||
| (o) Clone() | opts.go:715 | MISSING | — | Deep copy of Options not implemented |
|
||||
| (o) ProcessConfigFile() | opts.go:974 | PORTED | src/NATS.Server/Configuration/ConfigProcessor.cs:17 | — |
|
||||
| (o) ProcessConfigString() | opts.go:990 | MISSING | — | Parse config from string |
|
||||
| (o) ConfigDigest() | opts.go:1000 | MISSING | — | Config file digest |
|
||||
| **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 | 123 |
|
||||
| PARTIAL | 30 |
|
||||
| MISSING | 55 |
|
||||
| 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-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 |
|
||||
410
gaps/events.md
Normal file
410
gaps/events.md
Normal file
@@ -0,0 +1,410 @@
|
||||
# Events — Gap Analysis
|
||||
|
||||
> This file tracks what has and hasn't been ported from Go to .NET for the **Events** 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 Events
|
||||
|
||||
- System events publish on `$SYS.ACCOUNT.<id>.CONNECT`, `$SYS.ACCOUNT.<id>.DISCONNECT`, etc.
|
||||
- Events include client info, server info, and reason codes.
|
||||
- Message tracing allows tracking a specific message through local delivery, routes, gateways, and leaf nodes.
|
||||
- The system account (`$SYS`) is used for internal server communication.
|
||||
|
||||
---
|
||||
|
||||
## Go Reference Files (Source)
|
||||
|
||||
- `golang/nats-server/server/events.go` — System event generation/publishing (~4,080 lines). Connect/disconnect events, auth events, account events. Publishes on `$SYS.` subjects.
|
||||
- `golang/nats-server/server/msgtrace.go` — Message tracing (trace a message through the system)
|
||||
|
||||
## Go Reference Files (Tests)
|
||||
|
||||
- `golang/nats-server/server/events_test.go`
|
||||
- `golang/nats-server/server/msgtrace_test.go`
|
||||
|
||||
## .NET Implementation Files (Source)
|
||||
|
||||
- `src/NATS.Server/Events/` (all files)
|
||||
|
||||
## .NET Implementation Files (Tests)
|
||||
|
||||
- `tests/NATS.Server.Tests/Events/`
|
||||
|
||||
---
|
||||
|
||||
## Gap Inventory
|
||||
|
||||
<!-- After analysis, fill in this table. Group rows by Go source file. -->
|
||||
|
||||
### events.go — Exported Types / Structs
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `TypedEvent` | `golang/nats-server/server/events.go:468` | PORTED | `src/NATS.Server/Events/EventTypes.cs` | Fields inlined directly into each event class (ConnectEventMsg, DisconnectEventMsg, etc.) rather than embedded; same JSON shape |
|
||||
| `ServerStatsMsg` | `golang/nats-server/server/events.go:150` | PORTED | `src/NATS.Server/Events/EventTypes.cs:383` | Full field parity |
|
||||
| `ConnectEventMsg` | `golang/nats-server/server/events.go:155` | PORTED | `src/NATS.Server/Events/EventTypes.cs:191` | Full field parity including schema type constant |
|
||||
| `DisconnectEventMsg` | `golang/nats-server/server/events.go:167` | PORTED | `src/NATS.Server/Events/EventTypes.cs:211` | Full field parity including schema type constant |
|
||||
| `OCSPPeerRejectEventMsg` | `golang/nats-server/server/events.go:182` | PARTIAL | `src/NATS.Server/Events/EventTypes.cs:520` | Go has `Peer certidp.CertInfo` with Subject/Issuer/Fingerprint/Raw; .NET `OcspPeerRejectEventMsg` omits the `Peer` CertInfo sub-object entirely — only Kind+Reason present |
|
||||
| `OCSPPeerChainlinkInvalidEventMsg` | `golang/nats-server/server/events.go:196` | MISSING | — | No .NET equivalent. Go struct has Link+Peer CertInfo objects. .NET has a different `OcspChainValidationEvent` that does not match the Go shape or advisory type string (`io.nats.server.advisory.v1.ocsp_peer_link_invalid`) |
|
||||
| `AccountNumConns` | `golang/nats-server/server/events.go:210` | PORTED | `src/NATS.Server/Events/EventTypes.cs:245` | Full field parity including schema type constant |
|
||||
| `AccountStat` | `golang/nats-server/server/events.go:217` | PORTED | `src/NATS.Server/Events/EventTypes.cs:245` | Fields embedded inline in `AccountNumConns` — matches Go embedding pattern |
|
||||
| `ServerInfo` | `golang/nats-server/server/events.go:249` | PORTED | `src/NATS.Server/Events/EventTypes.cs:9` | `EventServerInfo` — all fields present; `Flags` typed as `ulong` (Go `ServerCapability uint64`) |
|
||||
| `ServerID` | `golang/nats-server/server/events.go:239` | MISSING | — | Simple struct with Name/Host/ID used in `idzReq` response; no .NET equivalent |
|
||||
| `ServerCapability` (type + consts) | `golang/nats-server/server/events.go:246` | MISSING | — | `JetStreamEnabled`, `BinaryStreamSnapshot`, `AccountNRG` capability flags; .NET has the `Flags ulong` field but no typed enum/const for capability bits |
|
||||
| `ClientInfo` | `golang/nats-server/server/events.go:308` | PORTED | `src/NATS.Server/Events/EventTypes.cs:62` | `EventClientInfo` — all fields present; RTT stored as `long RttNanos` vs Go `time.Duration` |
|
||||
| `ServerStats` | `golang/nats-server/server/events.go:364` | PORTED | `src/NATS.Server/Events/EventTypes.cs:395` | `ServerStatsData` — full field parity; .NET adds `InMsgs/OutMsgs/InBytes/OutBytes` compat fields not in Go (extra, not missing) |
|
||||
| `RouteStat` | `golang/nats-server/server/events.go:390` | PORTED | `src/NATS.Server/Events/EventTypes.cs:303` | Full field parity |
|
||||
| `GatewayStat` | `golang/nats-server/server/events.go:398` | PORTED | `src/NATS.Server/Events/EventTypes.cs:326` | Full field parity |
|
||||
| `MsgBytes` | `golang/nats-server/server/events.go:407` | PORTED | `src/NATS.Server/Events/EventTypes.cs:181` | `MsgBytesStats` — same fields |
|
||||
| `DataStats` | `golang/nats-server/server/events.go:412` | PORTED | `src/NATS.Server/Events/EventTypes.cs:156` | Full field parity |
|
||||
| `EventFilterOptions` | `golang/nats-server/server/events.go:1946` | MISSING | — | No .NET equivalent; used for server-side request filtering by name/cluster/host/tags/domain |
|
||||
| `StatszEventOptions` | `golang/nats-server/server/events.go:1956` | MISSING | — | No .NET equivalent |
|
||||
| `AccInfoEventOptions` | `golang/nats-server/server/events.go:1962` | MISSING | — | No .NET equivalent |
|
||||
| `ConnzEventOptions` | `golang/nats-server/server/events.go:1968` | MISSING | — | No .NET equivalent |
|
||||
| `RoutezEventOptions` | `golang/nats-server/server/events.go:1974` | MISSING | — | No .NET equivalent |
|
||||
| `SubszEventOptions` | `golang/nats-server/server/events.go:1980` | MISSING | — | No .NET equivalent |
|
||||
| `VarzEventOptions` | `golang/nats-server/server/events.go:1986` | MISSING | — | No .NET equivalent |
|
||||
| `GatewayzEventOptions` | `golang/nats-server/server/events.go:1992` | MISSING | — | No .NET equivalent |
|
||||
| `LeafzEventOptions` | `golang/nats-server/server/events.go:1997` | MISSING | — | No .NET equivalent |
|
||||
| `AccountzEventOptions` | `golang/nats-server/server/events.go:2004` | MISSING | — | No .NET equivalent |
|
||||
| `AccountStatzEventOptions` | `golang/nats-server/server/events.go:2010` | MISSING | — | No .NET equivalent |
|
||||
| `JszEventOptions` | `golang/nats-server/server/events.go:2016` | MISSING | — | No .NET equivalent |
|
||||
| `HealthzEventOptions` | `golang/nats-server/server/events.go:2022` | MISSING | — | No .NET equivalent |
|
||||
| `ProfilezEventOptions` | `golang/nats-server/server/events.go:2028` | MISSING | — | No .NET equivalent |
|
||||
| `ExpvarzEventOptions` | `golang/nats-server/server/events.go:2034` | MISSING | — | No .NET equivalent |
|
||||
| `IpqueueszEventOptions` | `golang/nats-server/server/events.go:2039` | MISSING | — | No .NET equivalent |
|
||||
| `RaftzEventOptions` | `golang/nats-server/server/events.go:2045` | MISSING | — | No .NET equivalent |
|
||||
| `ServerAPIResponse` | `golang/nats-server/server/events.go:2092` | MISSING | — | Generic request-reply envelope; no .NET equivalent |
|
||||
| `ServerAPIConnzResponse` | `golang/nats-server/server/events.go:2106` | MISSING | — | Typed response wrappers for Z endpoints; no .NET equivalent |
|
||||
| `ServerAPIRoutezResponse` | `golang/nats-server/server/events.go:2113` | MISSING | — | No .NET equivalent |
|
||||
| `ServerAPIGatewayzResponse` | `golang/nats-server/server/events.go:2119` | MISSING | — | No .NET equivalent |
|
||||
| `ServerAPIJszResponse` | `golang/nats-server/server/events.go:2126` | MISSING | — | No .NET equivalent |
|
||||
| `ServerAPIHealthzResponse` | `golang/nats-server/server/events.go:2133` | MISSING | — | No .NET equivalent |
|
||||
| `ServerAPIVarzResponse` | `golang/nats-server/server/events.go:2141` | MISSING | — | No .NET equivalent |
|
||||
| `ServerAPISubszResponse` | `golang/nats-server/server/events.go:2148` | MISSING | — | No .NET equivalent |
|
||||
| `ServerAPILeafzResponse` | `golang/nats-server/server/events.go:2155` | MISSING | — | No .NET equivalent |
|
||||
| `ServerAPIAccountzResponse` | `golang/nats-server/server/events.go:2162` | MISSING | — | No .NET equivalent |
|
||||
| `ServerAPIExpvarzResponse` | `golang/nats-server/server/events.go:2169` | MISSING | — | No .NET equivalent |
|
||||
| `ServerAPIpqueueszResponse` | `golang/nats-server/server/events.go:2175` | MISSING | — | No .NET equivalent |
|
||||
| `ServerAPIRaftzResponse` | `golang/nats-server/server/events.go:2183` | MISSING | — | No .NET equivalent |
|
||||
| `KickClientReq` | `golang/nats-server/server/events.go:3180` | MISSING | — | No .NET equivalent |
|
||||
| `LDMClientReq` | `golang/nats-server/server/events.go:3184` | MISSING | — | No .NET equivalent |
|
||||
| `UserInfo` | `golang/nats-server/server/events.go:1500` | MISSING | — | No .NET equivalent |
|
||||
| `SlowConsumersStats` | `golang/nats-server/server/events.go:377` | PORTED | `src/NATS.Server/Events/EventTypes.cs:344` | Full field parity |
|
||||
| `StaleConnectionStats` | `golang/nats-server/server/events.go:379` | PORTED | `src/NATS.Server/Events/EventTypes.cs:363` | Full field parity |
|
||||
|
||||
### events.go — Unexported Types (Important Helpers)
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `internal` struct | `golang/nats-server/server/events.go:124` | PORTED | `src/NATS.Server/Events/InternalEventSystem.cs:83` | `InternalEventSystem` class — same responsibilities: send/receive queues, sequence counter, SID counter, callback registry, server hash |
|
||||
| `inSysMsg` struct | `golang/nats-server/server/events.go:112` | PORTED | `src/NATS.Server/Events/InternalEventSystem.cs:29` | `InternalSystemMessage` sealed class — same fields |
|
||||
| `pubMsg` struct | `golang/nats-server/server/events.go:421` | PORTED | `src/NATS.Server/Events/InternalEventSystem.cs:15` | `PublishMessage` sealed class — same fields |
|
||||
| `sysMsgHandler` type | `golang/nats-server/server/events.go:109` | PORTED | `src/NATS.Server/Events/EventSubjects.cs:67` | `SystemMessageHandler` delegate — same signature shape |
|
||||
| `serverUpdate` struct | `golang/nats-server/server/events.go:461` | MISSING | — | Tracks seq + ltime for remote server heartbeat ordering; no .NET equivalent |
|
||||
| `accNumConnsReq` struct | `golang/nats-server/server/events.go:233` | PORTED | `src/NATS.Server/Events/EventTypes.cs:797` | `AccNumConnsReq` — full field parity |
|
||||
| `accNumSubsReq` struct | `golang/nats-server/server/events.go:2966` | MISSING | — | Used for debug subscriber count requests; no .NET equivalent |
|
||||
| `compressionType` + consts | `golang/nats-server/server/events.go:2082` | PARTIAL | `src/NATS.Server/Events/EventCompressor.cs` | `EventCompressor` handles snappy compression; no gzip compression path; `compressionType` enum itself not present as typed enum — only Snappy supported |
|
||||
| `msgHandler` type | `golang/nats-server/server/events.go:2751` | NOT_APPLICABLE | — | Go internal callback type merging header+body bytes; .NET uses the `SystemMessageHandler` delegate with separate header/body params |
|
||||
|
||||
### events.go — Exported Methods on ServerInfo
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `(*ServerInfo).SetJetStreamEnabled()` | `golang/nats-server/server/events.go:274` | MISSING | — | No .NET method; `EventServerInfo` has the raw `Flags` field but no typed capability methods |
|
||||
| `(*ServerInfo).JetStreamEnabled()` bool | `golang/nats-server/server/events.go:281` | MISSING | — | No .NET equivalent |
|
||||
| `(*ServerInfo).SetBinaryStreamSnapshot()` | `golang/nats-server/server/events.go:287` | MISSING | — | No .NET equivalent |
|
||||
| `(*ServerInfo).BinaryStreamSnapshot()` bool | `golang/nats-server/server/events.go:292` | MISSING | — | No .NET equivalent |
|
||||
| `(*ServerInfo).SetAccountNRG()` | `golang/nats-server/server/events.go:297` | MISSING | — | No .NET equivalent |
|
||||
| `(*ServerInfo).AccountNRG()` bool | `golang/nats-server/server/events.go:302` | MISSING | — | No .NET equivalent |
|
||||
|
||||
### events.go — Unexported Methods on ClientInfo
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `(*ClientInfo).forAssignmentSnap()` | `golang/nats-server/server/events.go:334` | NOT_APPLICABLE | — | Internal helper for RAFT assignment snapshots; no JetStream clustering yet |
|
||||
| `(*ClientInfo).forProposal()` | `golang/nats-server/server/events.go:343` | NOT_APPLICABLE | — | Internal helper for RAFT proposals; no JetStream clustering yet |
|
||||
| `(*ClientInfo).forAdvisory()` | `golang/nats-server/server/events.go:353` | MISSING | — | Strips JWT/Alternates for JS advisory events; no .NET equivalent |
|
||||
|
||||
### events.go — Server Methods (Core Event System)
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `(*Server).EventsEnabled()` | `golang/nats-server/server/events.go:807` | PARTIAL | `src/NATS.Server/Events/InternalEventSystem.cs:125` | `InternalEventSystem.Start()` activates the system; no public boolean property equivalent on NatsServer |
|
||||
| `(*Server).TrackedRemoteServers()` | `golang/nats-server/server/events.go:821` | MISSING | — | No .NET equivalent |
|
||||
| `(*Server).Node()` | `golang/nats-server/server/events.go:1157` | PARTIAL | `src/NATS.Server/Events/InternalEventSystem.cs:103` | `ServerHash` property computes SHA-256(8-char) server hash; not exposed as `Node()` on server |
|
||||
| `(*Server).internalSendLoop()` | `golang/nats-server/server/events.go:495` | PORTED | `src/NATS.Server/Events/InternalEventSystem.cs:363` | `InternalSendLoopAsync` — Channel-based; handles JSON serialization and SubList delivery |
|
||||
| `(*Server).internalReceiveLoop()` | `golang/nats-server/server/events.go:476` | PORTED | `src/NATS.Server/Events/InternalEventSystem.cs:431` | `InternalReceiveLoopAsync` — Channel-based dispatch |
|
||||
| `(*Server).sendLDMShutdownEventLocked()` | `golang/nats-server/server/events.go:674` | MISSING | — | No .NET equivalent |
|
||||
| `(*Server).sendShutdownEvent()` | `golang/nats-server/server/events.go:684` | MISSING | — | No .NET equivalent; `LameDuckEventMsg` / `ShutdownEventMsg` types exist but publishing method does not |
|
||||
| `(*Server).sendInternalAccountMsg()` | `golang/nats-server/server/events.go:703` | MISSING | — | No .NET equivalent |
|
||||
| `(*Server).sendInternalAccountMsgWithReply()` | `golang/nats-server/server/events.go:708` | MISSING | — | No .NET equivalent |
|
||||
| `(*Server).sendInternalAccountSysMsg()` | `golang/nats-server/server/events.go:732` | MISSING | — | No .NET equivalent |
|
||||
| `(*Server).sendInternalMsgLocked()` | `golang/nats-server/server/events.go:750` | MISSING | — | No .NET equivalent |
|
||||
| `(*Server).sendInternalMsg()` | `golang/nats-server/server/events.go:758` | MISSING | — | No .NET equivalent |
|
||||
| `(*Server).sendInternalResponse()` | `golang/nats-server/server/events.go:766` | MISSING | — | No .NET equivalent |
|
||||
| `(*Server).initEventTracking()` | `golang/nats-server/server/events.go:1172` | PARTIAL | `src/NATS.Server/Events/InternalEventSystem.cs:150` | `InitEventTracking` registers VARZ/HEALTHZ/SUBSZ/STATSZ/IDZ; missing: account-level services (CONNZ, LEAFZ, JSZ, STATZ, CONNS, INFO), latency tracking, reload/kick/LDM services, NSUBS, debug subscribers |
|
||||
| `(*Server).sendStatsz()` | `golang/nats-server/server/events.go:891` | PARTIAL | `src/NATS.Server/Events/InternalEventSystem.cs:188` | `PublishServerStats()` — publishes stats; missing: interest check before sending, gzip/snappy compression on response, JetStream stats section |
|
||||
| `(*Server).limitStatsz()` | `golang/nats-server/server/events.go:1068` | MISSING | — | Rate-limit for statsz broadcasts; no .NET equivalent |
|
||||
| `(*Server).heartbeatStatsz()` | `golang/nats-server/server/events.go:1098` | PARTIAL | `src/NATS.Server/Events/InternalEventSystem.cs:134` | Periodic timer in `Start()` fires every 10s; Go ramps from 250ms to statsz interval |
|
||||
| `(*Server).resetLastStatsz()` | `golang/nats-server/server/events.go:1115` | MISSING | — | No .NET equivalent |
|
||||
| `(*Server).sendStatszUpdate()` | `golang/nats-server/server/events.go:1119` | PORTED | `src/NATS.Server/Events/InternalEventSystem.cs:188` | Effectively `PublishServerStats()` |
|
||||
| `(*Server).startStatszTimer()` | `golang/nats-server/server/events.go:1124` | PARTIAL | `src/NATS.Server/Events/InternalEventSystem.cs:134` | Timer started in `Start()`; Go does exponential backoff ramp-up; .NET is fixed 10s interval |
|
||||
| `(*Server).startRemoteServerSweepTimer()` | `golang/nats-server/server/events.go:1133` | MISSING | — | Orphan remote server sweep; no .NET equivalent |
|
||||
| `(*Server).checkRemoteServers()` | `golang/nats-server/server/events.go:832` | MISSING | — | No .NET equivalent |
|
||||
| `(*Server).updateServerUsage()` | `golang/nats-server/server/events.go:848` | PARTIAL | `src/NATS.Server/Events/InternalEventSystem.cs:193` | CPU via `Process.GetCurrentProcess()`; Go uses `pse.ProcUsage()` for actual CPU%; .NET does not populate CPU |
|
||||
| `(*Server).sendAccConnsUpdate()` | `golang/nats-server/server/events.go:2407` | MISSING | — | No .NET equivalent |
|
||||
| `(*Server).accConnsUpdate()` | `golang/nats-server/server/events.go:2506` | MISSING | — | No .NET equivalent |
|
||||
| `(*Server).nextEventID()` | `golang/nats-server/server/events.go:2516` | PARTIAL | `src/NATS.Server/Events/EventTypes.cs:943` | `EventBuilder.GenerateEventId()` uses `Guid.NewGuid()`; Go uses nuid (faster nano-ID generator) |
|
||||
| `(*Server).accountConnectEvent()` | `golang/nats-server/server/events.go:2522` | PARTIAL | `src/NATS.Server/Events/InternalEventSystem.cs:303` | `SendConnectEvent()` exists; missing: JWT/IssuerKey/Tags/NameTag/Kind/ClientType/MQTTClientID fields in ConnectEventDetail |
|
||||
| `(*Server).accountDisconnectEvent()` | `golang/nats-server/server/events.go:2569` | PARTIAL | `src/NATS.Server/Events/InternalEventSystem.cs:329` | `SendDisconnectEvent()` exists; missing: RTT/JWT/IssuerKey/Tags/NameTag/Kind/ClientType/MQTTClientID fields in DisconnectEventDetail |
|
||||
| `(*Server).sendAuthErrorEvent()` | `golang/nats-server/server/events.go:2631` | PARTIAL | `src/NATS.Server/Events/InternalEventSystem.cs:277` | `SendAuthErrorEvent()` exists; Go uses `DisconnectEventMsg` shape for auth errors (surprising but correct); .NET uses `AuthErrorEventMsg` with different schema type |
|
||||
| `(*Server).sendAccountAuthErrorEvent()` | `golang/nats-server/server/events.go:2690` | MISSING | — | Account-level auth error event to account subject; no .NET equivalent |
|
||||
| `(*Server).sendOCSPPeerRejectEvent()` | `golang/nats-server/server/events.go:3267` | PARTIAL | `src/NATS.Server/Events/EventTypes.cs:612` | `OcspEventBuilder.BuildPeerReject()` helper exists; no publishing method on server; missing `Peer` CertInfo payload |
|
||||
| `(*Server).sendOCSPPeerChainlinkInvalidEvent()` | `golang/nats-server/server/events.go:3300` | MISSING | — | No .NET equivalent at all; `OcspChainValidationEvent` is a different/new type with non-matching Go advisory shape |
|
||||
| `(*Server).filterRequest()` | `golang/nats-server/server/events.go:2052` | MISSING | — | Server-side filter by name/host/cluster/tags/domain for Z-endpoint requests; no .NET equivalent |
|
||||
| `(*Server).zReq()` | `golang/nats-server/server/events.go:2252` | MISSING | — | Generic Z request handler (parse options, filter, call respf, encode response); no .NET equivalent |
|
||||
| `(*Server).statszReq()` | `golang/nats-server/server/events.go:2190` | PARTIAL | `src/NATS.Server/Events/InternalEventSystem.cs:159` | `HandleStatszRequest` registered but implementation thin — no filter options parsing |
|
||||
| `(*Server).idzReq()` | `golang/nats-server/server/events.go:2217` | PARTIAL | `src/NATS.Server/Events/InternalEventSystem.cs:156` | `HandleIdzRequest` registered; no implementation visible in analyzed files |
|
||||
| `(*Server).remoteConnsUpdate()` | `golang/nats-server/server/events.go:2283` | MISSING | — | No .NET equivalent |
|
||||
| `(*Server).remoteServerUpdate()` | `golang/nats-server/server/events.go:1704` | MISSING | — | No .NET equivalent |
|
||||
| `(*Server).remoteServerShutdown()` | `golang/nats-server/server/events.go:1665` | MISSING | — | No .NET equivalent |
|
||||
| `(*Server).updateRemoteServer()` | `golang/nats-server/server/events.go:1763` | MISSING | — | No .NET equivalent |
|
||||
| `(*Server).processRemoteServerShutdown()` | `golang/nats-server/server/events.go:1642` | MISSING | — | No .NET equivalent |
|
||||
| `(*Server).processNewServer()` | `golang/nats-server/server/events.go:1781` | MISSING | — | No .NET equivalent |
|
||||
| `(*Server).updateNRGAccountStatus()` | `golang/nats-server/server/events.go:1816` | NOT_APPLICABLE | — | JetStream NRG (RAFT) account status; not yet ported |
|
||||
| `(*Server).ensureGWsInterestOnlyForLeafNodes()` | `golang/nats-server/server/events.go:1838` | NOT_APPLICABLE | — | Gateway + leaf node coordination; not yet ported |
|
||||
| `(*Server).shutdownEventing()` | `golang/nats-server/server/events.go:1852` | PARTIAL | `src/NATS.Server/Events/InternalEventSystem.cs:453` | `DisposeAsync()` cancels loops and drains queues; missing: clearing accounts' eventing state, sending shutdown event before draining |
|
||||
| `(*Server).connsRequest()` | `golang/nats-server/server/events.go:1886` | MISSING | — | No .NET equivalent |
|
||||
| `(*Server).leafNodeConnected()` | `golang/nats-server/server/events.go:1925` | NOT_APPLICABLE | — | Leaf node gateway interest mode; not yet ported |
|
||||
| `(*Server).addSystemAccountExports()` | `golang/nats-server/server/events.go:1556` | NOT_APPLICABLE | — | Multi-tenant account exports; not yet ported |
|
||||
| `(*Server).registerSystemImports()` | `golang/nats-server/server/events.go:2331` | NOT_APPLICABLE | — | Multi-tenant account imports; not yet ported |
|
||||
| `(*Server).registerSystemImportsForExisting()` | `golang/nats-server/server/events.go:1532` | NOT_APPLICABLE | — | Multi-tenant; not yet ported |
|
||||
| `(*Server).enableAccountTracking()` | `golang/nats-server/server/events.go:2366` | MISSING | — | No .NET equivalent |
|
||||
| `(*Server).sendLeafNodeConnect()` | `golang/nats-server/server/events.go:2383` | NOT_APPLICABLE | — | Leaf node; not yet ported |
|
||||
| `(*Server).sendLeafNodeConnectMsg()` | `golang/nats-server/server/events.go:2398` | NOT_APPLICABLE | — | Leaf node; not yet ported |
|
||||
| `(*Server).accountClaimUpdate()` | `golang/nats-server/server/events.go:1608` | NOT_APPLICABLE | — | JWT account claim update; not yet ported |
|
||||
| `(*Server).userInfoReq()` | `golang/nats-server/server/events.go:1508` | MISSING | — | No .NET equivalent |
|
||||
| `(*Server).debugSubscribers()` | `golang/nats-server/server/events.go:3001` | MISSING | — | No .NET equivalent |
|
||||
| `(*Server).nsubsRequest()` | `golang/nats-server/server/events.go:3129` | MISSING | — | No .NET equivalent |
|
||||
| `(*Server).reloadConfig()` | `golang/nats-server/server/events.go:3168` | MISSING | — | No .NET equivalent |
|
||||
| `(*Server).kickClient()` | `golang/nats-server/server/events.go:3188` | MISSING | — | No .NET equivalent |
|
||||
| `(*Server).ldmClient()` | `golang/nats-server/server/events.go:3206` | MISSING | — | No .NET equivalent |
|
||||
| `(*Server).sysSubscribe()` | `golang/nats-server/server/events.go:2796` | PORTED | `src/NATS.Server/Events/InternalEventSystem.cs:231` | `SysSubscribe()` — same purpose |
|
||||
| `(*Server).sysSubscribeQ()` | `golang/nats-server/server/events.go:2801` | MISSING | — | Queue-group variant; no .NET equivalent |
|
||||
| `(*Server).sysSubscribeInternal()` | `golang/nats-server/server/events.go:2806` | MISSING | — | Internal-only sub (no interest propagation); no .NET equivalent |
|
||||
| `(*Server).systemSubscribe()` | `golang/nats-server/server/events.go:2810` | PARTIAL | `src/NATS.Server/Events/InternalEventSystem.cs:231` | `SysSubscribe()` combines subscribe + callback registration; Go version supports queue groups and internal-only flag |
|
||||
| `(*Server).sysUnsubscribe()` | `golang/nats-server/server/events.go:2842` | MISSING | — | No .NET equivalent |
|
||||
| `(*Server).inboxReply()` | `golang/nats-server/server/events.go:2928` | MISSING | — | WC inbox reply dispatcher; no .NET equivalent |
|
||||
| `(*Server).newRespInbox()` | `golang/nats-server/server/events.go:2953` | MISSING | — | No .NET equivalent |
|
||||
| `(*Server).remoteLatencyUpdate()` | `golang/nats-server/server/events.go:2870` | NOT_APPLICABLE | — | Service latency tracking; not yet ported |
|
||||
| `(*Server).noInlineCallback()` | `golang/nats-server/server/events.go:2760` | NOT_APPLICABLE | — | Routes callback through recvq to avoid blocking route/GW threads; .NET Channel already provides this decoupling |
|
||||
| `(*Server).noInlineCallbackStatsz()` | `golang/nats-server/server/events.go:2766` | NOT_APPLICABLE | — | Same — statsz queue variant |
|
||||
| `(*Server).wrapChk()` | `golang/nats-server/server/events.go:3253` | NOT_APPLICABLE | — | Lock + eventsEnabled guard wrapper; idiom not needed in async .NET code |
|
||||
| `(*Server).sameDomain()` | `golang/nats-server/server/events.go:1660` | MISSING | — | No .NET equivalent |
|
||||
|
||||
### events.go — Standalone / Package-Level Functions
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `getHash()` | `golang/nats-server/server/events.go:1141` | PORTED | `src/NATS.Server/Events/InternalEventSystem.cs:118` | SHA-256 8-char hash; same algorithm |
|
||||
| `getHashSize()` | `golang/nats-server/server/events.go:1146` | MISSING | — | Parameterized size variant; only 8-char version ported |
|
||||
| `routeStat()` | `golang/nats-server/server/events.go:859` | NOT_APPLICABLE | — | Route stat collection; clustering not yet ported |
|
||||
| `newPubMsg()` | `golang/nats-server/server/events.go:435` | NOT_APPLICABLE | — | Pool-based pubMsg factory; .NET uses `PublishMessage` record without pooling |
|
||||
| `(*pubMsg).returnToPool()` | `golang/nats-server/server/events.go:452` | NOT_APPLICABLE | — | Pool return; .NET has no pool |
|
||||
| `accForClient()` | `golang/nats-server/server/events.go:3224` | MISSING | — | No .NET equivalent |
|
||||
| `issuerForClient()` | `golang/nats-server/server/events.go:3232` | MISSING | — | No .NET equivalent |
|
||||
| `clearTimer()` | `golang/nats-server/server/events.go:3244` | NOT_APPLICABLE | — | Go timer management idiom; .NET uses `PeriodicTimer` / `CancellationToken` |
|
||||
| `totalSubs()` | `golang/nats-server/server/events.go:2973` | MISSING | — | No .NET equivalent |
|
||||
| `remoteLatencySubjectForResponse()` | `golang/nats-server/server/events.go:2860` | NOT_APPLICABLE | — | Latency tracking; not yet ported |
|
||||
| `getAcceptEncoding()` | `golang/nats-server/server/events.go:2238` | MISSING | — | Parses Accept-Encoding header for compression type; no .NET equivalent |
|
||||
| `(*Account).statz()` | `golang/nats-server/server/events.go:2446` | NOT_APPLICABLE | — | Account stats snapshot; Account class not yet in Events module |
|
||||
|
||||
### events.go — Constants / Subjects
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `connectEventSubj` | `golang/nats-server/server/events.go:49` | PORTED | `src/NATS.Server/Events/EventSubjects.cs:13` | `ConnectEvent` — matching subject pattern |
|
||||
| `disconnectEventSubj` | `golang/nats-server/server/events.go:50` | PORTED | `src/NATS.Server/Events/EventSubjects.cs:14` | `DisconnectEvent` — matching |
|
||||
| `accConnsEventSubjNew` | `golang/nats-server/server/events.go:58` | PORTED | `src/NATS.Server/Events/EventSubjects.cs:15` | `AccountConnsNew` — matching |
|
||||
| `accConnsEventSubjOld` | `golang/nats-server/server/events.go:59` | PORTED | `src/NATS.Server/Events/EventSubjects.cs:16` | `AccountConnsOld` — matching |
|
||||
| `serverStatsSubj` | `golang/nats-server/server/events.go:66` | PORTED | `src/NATS.Server/Events/EventSubjects.cs:19` | `ServerStats` — matching |
|
||||
| `shutdownEventSubj` | `golang/nats-server/server/events.go:61` | PORTED | `src/NATS.Server/Events/EventSubjects.cs:20` | `ServerShutdown` — matching |
|
||||
| `lameDuckEventSubj` | `golang/nats-server/server/events.go:60` | PORTED | `src/NATS.Server/Events/EventSubjects.cs:21` | `ServerLameDuck` — matching |
|
||||
| `authErrorEventSubj` | `golang/nats-server/server/events.go:64` | PORTED | `src/NATS.Server/Events/EventSubjects.cs:22` | `AuthError` — matching |
|
||||
| `authErrorAccountEventSubj` | `golang/nats-server/server/events.go:65` | PORTED | `src/NATS.Server/Events/EventSubjects.cs:23` | `AuthErrorAccount` — matching |
|
||||
| `serverDirectReqSubj` | `golang/nats-server/server/events.go:67` | PORTED | `src/NATS.Server/Events/EventSubjects.cs:32` | `ServerReq` — matching |
|
||||
| `serverPingReqSubj` | `golang/nats-server/server/events.go:68` | PORTED | `src/NATS.Server/Events/EventSubjects.cs:35` | `ServerPing` — matching |
|
||||
| `accDirectReqSubj` | `golang/nats-server/server/events.go:51` | PORTED | `src/NATS.Server/Events/EventSubjects.cs:38` | `AccountReq` — matching |
|
||||
| `inboxRespSubj` | `golang/nats-server/server/events.go:73` | PORTED | `src/NATS.Server/Events/EventSubjects.cs:41` | `InboxResponse` — matching |
|
||||
| `ocspPeerRejectEventSubj` | `golang/nats-server/server/events.go:95` | PARTIAL | `src/NATS.Server/Events/EventSubjects.cs:45` | Go: `$SYS.SERVER.%s.OCSP.PEER.CONN.REJECT`; .NET: `$SYS.SERVER.{0}.OCSP.PEER.REJECT` — different path segment |
|
||||
| `ocspPeerChainlinkInvalidEventSubj` | `golang/nats-server/server/events.go:96` | MISSING | — | Go: `$SYS.SERVER.%s.OCSP.PEER.LINK.INVALID`; .NET `OcspChainValidation` uses `$SYS.SERVER.{0}.OCSP.CHAIN.VALIDATION` — different subject |
|
||||
| `leafNodeConnectEventSubj` | `golang/nats-server/server/events.go:71` | PARTIAL | `src/NATS.Server/Events/EventSubjects.cs:28` | .NET subject `$SYS.SERVER.{0}.LEAFNODE.CONNECT` differs from Go `$SYS.ACCOUNT.%s.LEAFNODE.CONNECT` |
|
||||
| `remoteLatencyEventSubj` | `golang/nats-server/server/events.go:72` | MISSING | — | No .NET equivalent |
|
||||
| `userDirectInfoSubj` | `golang/nats-server/server/events.go:76` | MISSING | — | No .NET equivalent |
|
||||
| `userDirectReqSubj` | `golang/nats-server/server/events.go:77` | MISSING | — | No .NET equivalent |
|
||||
| `accNumSubsReqSubj` | `golang/nats-server/server/events.go:81` | MISSING | — | No .NET equivalent |
|
||||
| `accSubsSubj` | `golang/nats-server/server/events.go:84` | MISSING | — | No .NET equivalent |
|
||||
| `clientKickReqSubj` | `golang/nats-server/server/events.go:62` | MISSING | — | No .NET equivalent |
|
||||
| `clientLDMReqSubj` | `golang/nats-server/server/events.go:63` | MISSING | — | No .NET equivalent |
|
||||
| `serverStatsPingReqSubj` | `golang/nats-server/server/events.go:69` | MISSING | — | No .NET equivalent |
|
||||
| `serverReloadReqSubj` | `golang/nats-server/server/events.go:70` | MISSING | — | No .NET equivalent |
|
||||
| `accPingReqSubj` | `golang/nats-server/server/events.go:52` | MISSING | — | No .NET equivalent |
|
||||
| `connsRespSubj` | `golang/nats-server/server/events.go:57` | MISSING | — | No .NET equivalent |
|
||||
| `accLookupReqSubj` / JWT subjects | `golang/nats-server/server/events.go:43` | NOT_APPLICABLE | — | JWT operator resolver subjects; not yet ported |
|
||||
| `InboxPrefix` | `golang/nats-server/server/events.go:2946` | MISSING | — | No .NET equivalent |
|
||||
| `acceptEncodingHeader` / `contentEncodingHeader` | `golang/nats-server/server/events.go:2232` | MISSING | — | Header name constants for compression negotiation; no .NET equivalent |
|
||||
| `ConnectEventMsgType` | `golang/nats-server/server/events.go:163` | PORTED | `src/NATS.Server/Events/EventTypes.cs:193` | `ConnectEventMsg.EventType` constant — matching value |
|
||||
| `DisconnectEventMsgType` | `golang/nats-server/server/events.go:177` | PORTED | `src/NATS.Server/Events/EventTypes.cs:214` | `DisconnectEventMsg.EventType` — matching value |
|
||||
| `OCSPPeerRejectEventMsgType` | `golang/nats-server/server/events.go:191` | PORTED | `src/NATS.Server/Events/EventTypes.cs:522` | `OcspPeerRejectEventMsg.EventType` — matching value |
|
||||
| `OCSPPeerChainlinkInvalidEventMsgType` | `golang/nats-server/server/events.go:205` | MISSING | — | No .NET equivalent with matching type string `io.nats.server.advisory.v1.ocsp_peer_link_invalid` |
|
||||
| `AccountNumConnsMsgType` | `golang/nats-server/server/events.go:229` | PORTED | `src/NATS.Server/Events/EventTypes.cs:247` | `AccountNumConns.EventType` — matching value |
|
||||
|
||||
---
|
||||
|
||||
### msgtrace.go — Exported Types
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `MsgTraceType` | `golang/nats-server/server/msgtrace.go:50` | MISSING | — | No .NET equivalent |
|
||||
| `MsgTraceEvent` | `golang/nats-server/server/msgtrace.go:63` | MISSING | — | Top-level trace event container; no .NET equivalent |
|
||||
| `MsgTraceRequest` | `golang/nats-server/server/msgtrace.go:70` | MISSING | — | No .NET equivalent |
|
||||
| `MsgTraceEvents` | `golang/nats-server/server/msgtrace.go:76` | MISSING | — | Slice type with custom UnmarshalJSON; no .NET equivalent |
|
||||
| `MsgTrace` | `golang/nats-server/server/msgtrace.go:78` | MISSING | — | Interface; no .NET equivalent |
|
||||
| `MsgTraceBase` | `golang/nats-server/server/msgtrace.go:83` | MISSING | — | No .NET equivalent |
|
||||
| `MsgTraceIngress` | `golang/nats-server/server/msgtrace.go:88` | MISSING | — | No .NET equivalent |
|
||||
| `MsgTraceSubjectMapping` | `golang/nats-server/server/msgtrace.go:98` | MISSING | — | No .NET equivalent |
|
||||
| `MsgTraceStreamExport` | `golang/nats-server/server/msgtrace.go:103` | MISSING | — | No .NET equivalent |
|
||||
| `MsgTraceServiceImport` | `golang/nats-server/server/msgtrace.go:109` | MISSING | — | No .NET equivalent |
|
||||
| `MsgTraceJetStream` | `golang/nats-server/server/msgtrace.go:116` | MISSING | — | No .NET equivalent |
|
||||
| `MsgTraceEgress` | `golang/nats-server/server/msgtrace.go:124` | MISSING | — | No .NET equivalent |
|
||||
|
||||
### msgtrace.go — Exported Constants
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `MsgTraceDest` | `golang/nats-server/server/msgtrace.go:28` | MISSING | — | Header name constant; no .NET equivalent |
|
||||
| `MsgTraceDestDisabled` | `golang/nats-server/server/msgtrace.go:29` | MISSING | — | No .NET equivalent |
|
||||
| `MsgTraceHop` | `golang/nats-server/server/msgtrace.go:30` | MISSING | — | No .NET equivalent |
|
||||
| `MsgTraceOriginAccount` | `golang/nats-server/server/msgtrace.go:31` | MISSING | — | No .NET equivalent |
|
||||
| `MsgTraceOnly` | `golang/nats-server/server/msgtrace.go:32` | MISSING | — | No .NET equivalent |
|
||||
| `MsgTraceIngressType` | `golang/nats-server/server/msgtrace.go:55` | MISSING | — | No .NET equivalent |
|
||||
| `MsgTraceSubjectMappingType` | `golang/nats-server/server/msgtrace.go:56` | MISSING | — | No .NET equivalent |
|
||||
| `MsgTraceStreamExportType` | `golang/nats-server/server/msgtrace.go:57` | MISSING | — | No .NET equivalent |
|
||||
| `MsgTraceServiceImportType` | `golang/nats-server/server/msgtrace.go:58` | MISSING | — | No .NET equivalent |
|
||||
| `MsgTraceJetStreamType` | `golang/nats-server/server/msgtrace.go:59` | MISSING | — | No .NET equivalent |
|
||||
| `MsgTraceEgressType` | `golang/nats-server/server/msgtrace.go:60` | MISSING | — | No .NET equivalent |
|
||||
|
||||
### msgtrace.go — Exported Methods on MsgTraceEvent
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `(*MsgTraceEvent).Ingress()` | `golang/nats-server/server/msgtrace.go:193` | MISSING | — | No .NET equivalent |
|
||||
| `(*MsgTraceEvent).SubjectMapping()` | `golang/nats-server/server/msgtrace.go:200` | MISSING | — | No .NET equivalent |
|
||||
| `(*MsgTraceEvent).StreamExports()` | `golang/nats-server/server/msgtrace.go:209` | MISSING | — | No .NET equivalent |
|
||||
| `(*MsgTraceEvent).ServiceImports()` | `golang/nats-server/server/msgtrace.go:219` | MISSING | — | No .NET equivalent |
|
||||
| `(*MsgTraceEvent).JetStream()` | `golang/nats-server/server/msgtrace.go:229` | MISSING | — | No .NET equivalent |
|
||||
| `(*MsgTraceEvent).Egresses()` | `golang/nats-server/server/msgtrace.go:238` | MISSING | — | No .NET equivalent |
|
||||
| `(*MsgTraceEvents).UnmarshalJSON()` | `golang/nats-server/server/msgtrace.go:160` | MISSING | — | Polymorphic JSON deserialization of trace event list; no .NET equivalent |
|
||||
|
||||
### msgtrace.go — Unexported Core Functions (>20 lines)
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `(*client).initMsgTrace()` | `golang/nats-server/server/msgtrace.go:332` | MISSING | — | Trace header parsing and msgTrace struct initialization; no .NET equivalent |
|
||||
| `(*client).isMsgTraceEnabled()` | `golang/nats-server/server/msgtrace.go:285` | MISSING | — | No .NET equivalent |
|
||||
| `(*client).msgTraceSupport()` | `golang/nats-server/server/msgtrace.go:295` | MISSING | — | No .NET equivalent |
|
||||
| `(*client).initAndSendIngressErrEvent()` | `golang/nats-server/server/msgtrace.go:596` | MISSING | — | No .NET equivalent |
|
||||
| `(*msgTrace).traceOnly()` | `golang/nats-server/server/msgtrace.go:626` | MISSING | — | No .NET equivalent |
|
||||
| `(*msgTrace).setOriginAccountHeaderIfNeeded()` | `golang/nats-server/server/msgtrace.go:630` | MISSING | — | No .NET equivalent |
|
||||
| `(*msgTrace).setHopHeader()` | `golang/nats-server/server/msgtrace.go:646` | MISSING | — | No .NET equivalent |
|
||||
| `(*msgTrace).setIngressError()` | `golang/nats-server/server/msgtrace.go:657` | MISSING | — | No .NET equivalent |
|
||||
| `(*msgTrace).addSubjectMappingEvent()` | `golang/nats-server/server/msgtrace.go:663` | MISSING | — | No .NET equivalent |
|
||||
| `(*msgTrace).addEgressEvent()` | `golang/nats-server/server/msgtrace.go:676` | MISSING | — | No .NET equivalent |
|
||||
| `(*msgTrace).addStreamExportEvent()` | `golang/nats-server/server/msgtrace.go:713` | MISSING | — | No .NET equivalent |
|
||||
| `(*msgTrace).addServiceImportEvent()` | `golang/nats-server/server/msgtrace.go:730` | MISSING | — | No .NET equivalent |
|
||||
| `(*msgTrace).addJetStreamEvent()` | `golang/nats-server/server/msgtrace.go:745` | MISSING | — | No .NET equivalent |
|
||||
| `(*msgTrace).updateJetStreamEvent()` | `golang/nats-server/server/msgtrace.go:759` | MISSING | — | No .NET equivalent |
|
||||
| `(*msgTrace).sendEventFromJetStream()` | `golang/nats-server/server/msgtrace.go:774` | MISSING | — | No .NET equivalent |
|
||||
| `(*msgTrace).sendEvent()` | `golang/nats-server/server/msgtrace.go:788` | MISSING | — | No .NET equivalent |
|
||||
| `genHeaderMapIfTraceHeadersPresent()` | `golang/nats-server/server/msgtrace.go:509` | MISSING | — | Parses raw NATS header bytes for trace headers; no .NET equivalent |
|
||||
| `getConnName()` | `golang/nats-server/server/msgtrace.go:300` | MISSING | — | No .NET equivalent |
|
||||
| `getCompressionType()` | `golang/nats-server/server/msgtrace.go:318` | MISSING | — | No .NET equivalent |
|
||||
| `sample()` | `golang/nats-server/server/msgtrace.go:494` | MISSING | — | Probabilistic sampling for external trace; no .NET equivalent |
|
||||
|
||||
---
|
||||
|
||||
## 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/Events/ -name '*.cs' -type f -exec cat {} + | wc -l
|
||||
# Re-count .NET test LOC for this module
|
||||
find tests/NATS.Server.Tests/Events/ -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-25 | File created with LLM analysis instructions | auto |
|
||||
| 2026-02-25 | Full gap inventory populated from Go source analysis | auto |
|
||||
251
gaps/gateways.md
Normal file
251
gaps/gateways.md
Normal file
@@ -0,0 +1,251 @@
|
||||
# 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:94–111 | 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:49–58 | PARTIAL | `src/NATS.Server/Gateways/ReplyMapper.cs:12` | `GatewayReplyPrefix = "_GR_."` is present. Hash length and byte-offset arithmetic constants are missing; .NET uses string-segment parsing instead of fixed-width offsets. |
|
||||
| `oldGWReplyPrefix` / `oldGWReplyPrefixLen` / `oldGWReplyStart` | gateway.go:43–46 | MISSING | — | Old `$GR.` reply prefix for backward-compat with pre-v2.9 servers is not represented in .NET. `ReplyMapper.TryRestoreGatewayReply` handles it partially via numeric-hash detection but has no dedicated constant. |
|
||||
| `gatewayTLSInsecureWarning` | gateway.go:71 | MISSING | — | TLS insecure warning string; no TLS gateway support yet. |
|
||||
|
||||
#### 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`) | `RemoteGatewayOptions` covers name and URLs. Missing: `hash`/`oldHash` byte arrays, `implicit` flag, `connAttempts` counter, `tlsName`, `varzUpdateURLs`, URL management methods (`addURLs`, `updateURLs`, `getURLs`, `saveTLSHostname`), and per-remote TLS config. |
|
||||
| `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 | MISSING | — | Validates gateway config (name, port, remote URLs). No validation equivalent in .NET. |
|
||||
| `getGWHash` (standalone) | gateway.go:335 | MISSING | — | Computes 6-char hash for gateway name. Used for reply prefix construction. Not ported. |
|
||||
| `getOldHash` (standalone) | gateway.go:339 | MISSING | — | SHA-256-based 4-char hash for old `$GR.` prefix. Not ported. |
|
||||
| `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 | MISSING | — | Checks if an inbound connection for a named gateway exists. Not implemented. |
|
||||
| `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 | MISSING | — | Public test-facing count of outbound connections. No exact equivalent. |
|
||||
| `Server.numOutboundGateways` | gateway.go:1506 | PARTIAL | `src/NATS.Server/Gateways/GatewayManager.cs:284` (`GetConnectedGatewayCount`) | Returns connected count from registrations. Does not distinguish inbound vs outbound. |
|
||||
| `Server.numInboundGateways` | gateway.go:1514 | MISSING | — | Count of inbound gateway connections. No equivalent. |
|
||||
| `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 | MISSING | — | Test helper to increment connection attempts counter. |
|
||||
| `gatewayCfg.getConnAttempts` | gateway.go:1537 | MISSING | — | Test helper to read connection attempts. |
|
||||
| `gatewayCfg.resetConnAttempts` | gateway.go:1545 | MISSING | — | Test helper to reset connection attempts. |
|
||||
| `gatewayCfg.isImplicit` | gateway.go:1552 | MISSING | — | Returns whether a gateway config was discovered (implicit) vs configured (explicit). |
|
||||
| `gatewayCfg.getURLs` | gateway.go:1561 | MISSING | — | Returns randomly shuffled URL slice for connection attempts. |
|
||||
| `gatewayCfg.getURLsAsStrings` | gateway.go:1576 | MISSING | — | Returns URL host strings for gossip INFO. |
|
||||
| `gatewayCfg.updateURLs` | gateway.go:1588 | MISSING | — | Rebuilds URL map from config + INFO-discovered URLs. |
|
||||
| `gatewayCfg.saveTLSHostname` | gateway.go:1612 | MISSING | — | Saves TLS ServerName from a URL hostname. TLS not implemented. |
|
||||
| `gatewayCfg.addURLs` | gateway.go:1621 | MISSING | — | Adds newly discovered URLs into the URL map. |
|
||||
| `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 | MISSING | — | Returns this server's gateway listen URL string. |
|
||||
| `Server.getGatewayName` | gateway.go:1697 | MISSING | — | Returns this server's 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 | MISSING | — | Returns outbound connection by name. No named lookup of outbound connections. |
|
||||
| `Server.getOutboundGatewayConnections` | gateway.go:1752 | MISSING | — | Returns all outbound connections in RTT order. |
|
||||
| `Server.getInboundGatewayConnections` | gateway.go:1778 | MISSING | — | Returns all inbound connections. |
|
||||
| `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 | MISSING | — | Returns `*net.TCPAddr` for the gateway listener. No equivalent (only `ListenEndpoint` string). |
|
||||
| `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 | PARTIAL | `src/NATS.Server/Gateways/ReplyMapper.cs:17` | `HasGatewayReplyPrefix` checks new prefix only. Old `$GR.` prefix detection missing. |
|
||||
| `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 | MISSING | — | Deep-copies a `RemoteGatewayOpts` including TLS config clone. No clone method on `RemoteGatewayOptions`. |
|
||||
|
||||
#### 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-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 |
|
||||
341
gaps/instructions.md
Normal file
341
gaps/instructions.md
Normal file
@@ -0,0 +1,341 @@
|
||||
# Gap Analysis — Orchestrator Instructions
|
||||
|
||||
> Instructions for an LLM to launch parallel subagents that analyze and maintain the gap inventory across all 19 categories.
|
||||
|
||||
## Overview
|
||||
|
||||
The `gaps/` directory tracks what has been ported from the Go NATS server to the .NET port. Each category has its own `<category>.md` file with:
|
||||
- Go reference file paths
|
||||
- .NET implementation file paths
|
||||
- A Gap Inventory table (populated or empty)
|
||||
- Instructions for how to analyze the gap
|
||||
|
||||
The orchestrator's job is to launch one subagent per category, collect results, and update `stillmissing.md` with fresh LOC numbers.
|
||||
|
||||
---
|
||||
|
||||
## Mode Detection
|
||||
|
||||
Before launching subagents, determine the mode:
|
||||
|
||||
```
|
||||
IF the Gap Inventory table in a category file has only the empty template row → INITIAL mode
|
||||
IF the Gap Inventory table has populated rows → UPDATE mode
|
||||
```
|
||||
|
||||
You can check all files at once:
|
||||
|
||||
```bash
|
||||
for f in gaps/*.md; do
|
||||
[ "$f" = "gaps/stillmissing.md" ] && continue
|
||||
[ "$f" = "gaps/instructions.md" ] && continue
|
||||
rows=$(grep -c '^|[^|]*|[^|]*|' "$f" 2>/dev/null)
|
||||
name=$(basename "$f" .md)
|
||||
if [ "$rows" -le 3 ]; then
|
||||
echo "INITIAL: $name"
|
||||
else
|
||||
echo "UPDATE: $name"
|
||||
fi
|
||||
done
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Category Registry
|
||||
|
||||
Each category file and its complexity tier (determines model selection and context budget):
|
||||
|
||||
| Category File | Tier | Model | Go Source LOC | Notes |
|
||||
|---------------|------|-------|-------------:|-------|
|
||||
| `core-server.md` | LARGE | opus | 21,223 | client.go alone is ~6,700 lines |
|
||||
| `protocol.md` | SMALL | sonnet | 1,829 | 3 files, well-scoped |
|
||||
| `subscriptions.md` | SMALL | sonnet | 2,416 | 2 files, well-scoped |
|
||||
| `auth-and-accounts.md` | LARGE | opus | 7,260 | accounts.go is ~4,100 lines |
|
||||
| `configuration.md` | SMALL | sonnet | 1,871 | 3 files |
|
||||
| `routes.md` | MEDIUM | sonnet | 3,314 | 1 file |
|
||||
| `gateways.md` | MEDIUM | sonnet | 3,426 | 1 file |
|
||||
| `leaf-nodes.md` | MEDIUM | sonnet | 3,470 | 1 file |
|
||||
| `jetstream.md` | X-LARGE | opus | 55,228 | Must be split into sub-passes |
|
||||
| `raft.md` | MEDIUM | opus | 5,037 | 1 file, complex consensus logic |
|
||||
| `mqtt.md` | MEDIUM | opus | 5,882 | 1 file |
|
||||
| `websocket.md` | SMALL | sonnet | 1,550 | 1 file, near-complete (99%) |
|
||||
| `monitoring.md` | MEDIUM | sonnet | 4,396 | 2 files |
|
||||
| `events.md` | MEDIUM | sonnet | 4,133 | 2 files |
|
||||
| `tls-security.md` | MEDIUM | opus | 4,207 | Multiple files + subdirs |
|
||||
| `internal-ds.md` | MEDIUM | sonnet | 4,020 | Already at 105% parity |
|
||||
| `logging.md` | TRIVIAL | haiku | 936 | Likely all NOT_APPLICABLE |
|
||||
| `utilities-and-other.md` | MEDIUM | sonnet | 3,282 | Mixed bag of small files |
|
||||
| `misc-uncategorized.md` | SMALL | haiku | 660 | Remainder items |
|
||||
|
||||
---
|
||||
|
||||
## Launching Subagents
|
||||
|
||||
### Parallelism Strategy
|
||||
|
||||
Launch all subagents in a **single message** using multiple `Task` tool calls. All 19 categories are independent — they read different Go files and write to different output files.
|
||||
|
||||
**Exception: JetStream (X-LARGE)**. JetStream has 55,228 Go source LOC across 15 files. A single subagent cannot read all files in one context window. Launch it as a **foreground** task and handle it specially (see JetStream section below).
|
||||
|
||||
### Subagent Tool Configuration
|
||||
|
||||
For each category, use the `Task` tool with:
|
||||
|
||||
```
|
||||
subagent_type: "general-purpose"
|
||||
model: <from Category Registry table>
|
||||
description: "Analyze <category> gaps"
|
||||
```
|
||||
|
||||
### Prompt Templates
|
||||
|
||||
#### INITIAL Mode Prompt
|
||||
|
||||
Use this prompt when the Gap Inventory table is empty:
|
||||
|
||||
```
|
||||
You are analyzing the Go-to-.NET porting gap for the {CATEGORY_NAME} module of the NATS server.
|
||||
|
||||
## Your Task
|
||||
|
||||
Read the category file at `gaps/{CATEGORY_FILE}` — it contains:
|
||||
- Detailed analysis instructions (Steps 1-5)
|
||||
- Go reference file paths
|
||||
- .NET implementation file paths
|
||||
- Key porting notes specific to this category
|
||||
|
||||
Follow the instructions in that file exactly. Specifically:
|
||||
|
||||
1. Read each Go source file listed under "Go Reference Files (Source)"
|
||||
2. For each file, extract all exported types, methods, and standalone functions
|
||||
3. Read the .NET files listed under ".NET Implementation Files (Source)"
|
||||
4. For each Go symbol, search for its .NET equivalent
|
||||
5. Classify each item as PORTED, PARTIAL, MISSING, NOT_APPLICABLE, or DEFERRED
|
||||
6. Write the results into the Gap Inventory table in `gaps/{CATEGORY_FILE}`
|
||||
|
||||
## Output Format
|
||||
|
||||
Edit `gaps/{CATEGORY_FILE}` to populate the Gap Inventory table. Each row must have:
|
||||
- **Go Symbol**: The type or function name (e.g., `Server.Start`, `processMsg`, `RouteInfo`)
|
||||
- **Go File:Line**: Path relative to repo root with line number (e.g., `golang/nats-server/server/route.go:142`)
|
||||
- **Status**: One of PORTED, PARTIAL, MISSING, NOT_APPLICABLE, DEFERRED
|
||||
- **NET Equivalent**: Path to .NET file:line if it exists (e.g., `src/NATS.Server/Routes/RouteManager.cs:55`)
|
||||
- **Notes**: Brief explanation, especially for PARTIAL (what's missing) and NOT_APPLICABLE (why)
|
||||
|
||||
Group rows by Go source file. Use markdown sub-headers within the table comments if helpful.
|
||||
|
||||
## Important Rules
|
||||
|
||||
- Do NOT modify any source code (.go or .cs files) — this is a read-only analysis
|
||||
- Do NOT modify `gaps/stillmissing.md` — the orchestrator handles LOC updates
|
||||
- DO update the Change Log at the bottom of the category file
|
||||
- Focus on **exported/public API surface** first, then important unexported helpers
|
||||
- For large files (>2,000 lines), work through them methodically — top to bottom, section by section
|
||||
- Skip trivial getters/setters unless they contain logic
|
||||
```
|
||||
|
||||
#### UPDATE Mode Prompt
|
||||
|
||||
Use this prompt when the Gap Inventory table already has data:
|
||||
|
||||
```
|
||||
You are updating the gap analysis for the {CATEGORY_NAME} module of the NATS server.
|
||||
|
||||
## Your Task
|
||||
|
||||
The file `gaps/{CATEGORY_FILE}` already has a populated Gap Inventory table from a previous analysis. Your job is to:
|
||||
|
||||
1. Read the current Gap Inventory in `gaps/{CATEGORY_FILE}`
|
||||
2. For each item marked MISSING or PARTIAL:
|
||||
- Search the .NET codebase to check if it has been ported since the last analysis
|
||||
- If now ported, update the status to PORTED and fill in the .NET Equivalent path
|
||||
- If partially ported, update notes with current state
|
||||
3. Check for NEW Go symbols that were added since the last analysis:
|
||||
- Read the Go source files listed in the category file
|
||||
- Compare against the existing inventory
|
||||
- Add rows for any new symbols not yet tracked
|
||||
4. Check for NEW .NET files that may cover previously MISSING items:
|
||||
- Glob for new .cs files in the .NET directories listed in the category file
|
||||
- Cross-reference against MISSING items
|
||||
5. Update the Change Log at the bottom
|
||||
|
||||
## Output Format
|
||||
|
||||
Edit `gaps/{CATEGORY_FILE}` in place:
|
||||
- Update existing rows (change status, add .NET paths)
|
||||
- Add new rows for newly discovered symbols
|
||||
- Do NOT remove existing rows — even if the Go symbol was removed, mark it as NOT_APPLICABLE with a note
|
||||
- Update the Change Log with today's date and a summary
|
||||
|
||||
## After Updating the Inventory
|
||||
|
||||
Run the LOC re-count commands from the "Keeping This File Updated" section of the category file and report the new numbers in your output so the orchestrator can update stillmissing.md.
|
||||
|
||||
## Important Rules
|
||||
|
||||
- Do NOT modify any source code — read-only analysis
|
||||
- Do NOT modify `gaps/stillmissing.md` — report numbers, the orchestrator updates it
|
||||
- Preserve all existing inventory rows
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## JetStream Special Handling
|
||||
|
||||
JetStream is too large for a single subagent pass. Split it into 5 sub-passes, launched sequentially or with careful scoping:
|
||||
|
||||
| Sub-pass | Go Files | Focus |
|
||||
|----------|----------|-------|
|
||||
| JS-1: Core | `jetstream.go`, `jetstream_api.go`, `jetstream_events.go`, `jetstream_errors.go`, `jetstream_versioning.go`, `jetstream_batching.go` | Orchestration, API handlers |
|
||||
| JS-2: Stream | `stream.go`, `store.go` | Stream lifecycle, retention policies |
|
||||
| JS-3: Consumer | `consumer.go` | Consumer state machine, delivery modes |
|
||||
| JS-4: Storage | `filestore.go`, `memstore.go`, `dirstore.go`, `disk_avail.go` | Persistent storage, compression, encryption |
|
||||
| JS-5: Cluster | `jetstream_cluster.go` | Clustered JetStream coordination |
|
||||
|
||||
Launch JS-1 through JS-5 as separate subagents (can run in parallel). Each writes to `gaps/jetstream.md` — but since they write different sections of the Gap Inventory table, use **section headers** within the table to avoid conflicts:
|
||||
|
||||
```
|
||||
<!-- === JS-1: Core === -->
|
||||
| JetStream.enableJetStream | jetstream.go:245 | PORTED | ... | ... |
|
||||
...
|
||||
<!-- === JS-2: Stream === -->
|
||||
| stream.processInboundJetStreamMsg | stream.go:112 | MISSING | | ... |
|
||||
...
|
||||
```
|
||||
|
||||
Alternatively, launch them sequentially (JS-1 first, then resume the agent for JS-2, etc.) to avoid write conflicts.
|
||||
|
||||
---
|
||||
|
||||
## After All Subagents Complete
|
||||
|
||||
### Step 1: Collect LOC Numbers
|
||||
|
||||
Each subagent in UPDATE mode should report its re-counted LOC. If running INITIAL mode, the orchestrator runs LOC counts itself:
|
||||
|
||||
```bash
|
||||
cd /Users/dohertj2/Desktop/natsdotnet
|
||||
|
||||
echo "=== .NET Source LOC by Category ==="
|
||||
echo "core-server|$(find src/NATS.Server/ -maxdepth 1 -name '*.cs' -type f -exec cat {} + 2>/dev/null | wc -l)"
|
||||
echo "protocol|$(find src/NATS.Server/Protocol/ -name '*.cs' -type f -exec cat {} + 2>/dev/null | wc -l)"
|
||||
echo "subscriptions|$(find src/NATS.Server/Subscriptions/ -name '*.cs' -type f -exec cat {} + 2>/dev/null | wc -l)"
|
||||
echo "auth-and-accounts|$(find src/NATS.Server/Auth/ src/NATS.Server/Imports/ -name '*.cs' -type f -exec cat {} + 2>/dev/null | wc -l)"
|
||||
echo "configuration|$(find src/NATS.Server/Configuration/ -name '*.cs' -type f -exec cat {} + 2>/dev/null | wc -l)"
|
||||
echo "routes|$(find src/NATS.Server/Routes/ -name '*.cs' -type f -exec cat {} + 2>/dev/null | wc -l)"
|
||||
echo "gateways|$(find src/NATS.Server/Gateways/ -name '*.cs' -type f -exec cat {} + 2>/dev/null | wc -l)"
|
||||
echo "leaf-nodes|$(find src/NATS.Server/LeafNodes/ -name '*.cs' -type f -exec cat {} + 2>/dev/null | wc -l)"
|
||||
echo "jetstream|$(find src/NATS.Server/JetStream/ -name '*.cs' -type f -exec cat {} + 2>/dev/null | wc -l)"
|
||||
echo "raft|$(find src/NATS.Server/Raft/ -name '*.cs' -type f -exec cat {} + 2>/dev/null | wc -l)"
|
||||
echo "mqtt|$(find src/NATS.Server/Mqtt/ -name '*.cs' -type f -exec cat {} + 2>/dev/null | wc -l)"
|
||||
echo "websocket|$(find src/NATS.Server/WebSocket/ -name '*.cs' -type f -exec cat {} + 2>/dev/null | wc -l)"
|
||||
echo "monitoring|$(find src/NATS.Server/Monitoring/ -name '*.cs' -type f -exec cat {} + 2>/dev/null | wc -l)"
|
||||
echo "events|$(find src/NATS.Server/Events/ -name '*.cs' -type f -exec cat {} + 2>/dev/null | wc -l)"
|
||||
echo "tls-security|$(find src/NATS.Server/Tls/ -name '*.cs' -type f -exec cat {} + 2>/dev/null | wc -l)"
|
||||
echo "internal-ds|$(find src/NATS.Server/Internal/ -name '*.cs' -type f -exec cat {} + 2>/dev/null | wc -l)"
|
||||
echo "logging|0"
|
||||
echo "utilities-and-other|$(find src/NATS.Server/IO/ src/NATS.Server/Server/ -name '*.cs' -type f -exec cat {} + 2>/dev/null | wc -l)"
|
||||
|
||||
echo ""
|
||||
echo "=== .NET Source TOTAL ==="
|
||||
find src/ -name '*.cs' -type f -exec cat {} + 2>/dev/null | wc -l
|
||||
|
||||
echo ""
|
||||
echo "=== .NET Test TOTAL ==="
|
||||
find tests/ -name '*.cs' -type f -exec cat {} + 2>/dev/null | wc -l
|
||||
|
||||
echo ""
|
||||
echo "=== .NET File Counts ==="
|
||||
echo "source|$(find src/ -name '*.cs' -type f | wc -l)"
|
||||
echo "test|$(find tests/ -name '*.cs' -type f | wc -l)"
|
||||
```
|
||||
|
||||
### Step 2: Update stillmissing.md
|
||||
|
||||
Compare the new LOC numbers against the values in `gaps/stillmissing.md`. For each category where the .NET LOC changed:
|
||||
|
||||
1. Edit the row in the "Source Code by Functional Area" table
|
||||
2. Recalculate the `.NET/Go` percentage: `round(.NET LOC / Go LOC * 100)`
|
||||
3. Update the `.NET Files` count if new files were added
|
||||
4. Update the Summary table totals
|
||||
5. Update the "Generated" date in the header
|
||||
|
||||
**Do NOT change Go LOC numbers** unless the Go reference has been updated (rare).
|
||||
|
||||
### Step 3: Generate Summary Report
|
||||
|
||||
After all subagents complete, produce a summary for the user:
|
||||
|
||||
```
|
||||
## Gap Analysis Summary — {DATE}
|
||||
|
||||
### Status Counts (across all categories)
|
||||
- PORTED: {n}
|
||||
- PARTIAL: {n}
|
||||
- MISSING: {n}
|
||||
- NOT_APPLICABLE: {n}
|
||||
- DEFERRED: {n}
|
||||
|
||||
### Categories with Most MISSING Items
|
||||
1. {category}: {n} missing
|
||||
2. {category}: {n} missing
|
||||
3. {category}: {n} missing
|
||||
|
||||
### LOC Changes Since Last Run
|
||||
| Category | Previous .NET LOC | Current .NET LOC | Delta |
|
||||
|----------|------------------:|-----------------:|------:|
|
||||
| ... | ... | ... | ... |
|
||||
|
||||
### Categories Needing Attention
|
||||
- {category}: {reason}
|
||||
```
|
||||
|
||||
Count statuses by grepping the inventory tables:
|
||||
|
||||
```bash
|
||||
for f in gaps/*.md; do
|
||||
[ "$f" = "gaps/stillmissing.md" ] && continue
|
||||
[ "$f" = "gaps/instructions.md" ] && continue
|
||||
name=$(basename "$f" .md)
|
||||
ported=$(grep -c '| PORTED |' "$f" 2>/dev/null || echo 0)
|
||||
partial=$(grep -c '| PARTIAL |' "$f" 2>/dev/null || echo 0)
|
||||
missing=$(grep -c '| MISSING |' "$f" 2>/dev/null || echo 0)
|
||||
na=$(grep -c '| NOT_APPLICABLE |' "$f" 2>/dev/null || echo 0)
|
||||
deferred=$(grep -c '| DEFERRED |' "$f" 2>/dev/null || echo 0)
|
||||
echo "$name|$ported|$partial|$missing|$na|$deferred"
|
||||
done
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Quick-Start: Full Initial Run
|
||||
|
||||
Copy-paste orchestration sequence:
|
||||
|
||||
1. **Check mode** — run the mode detection script above
|
||||
2. **Launch all subagents** — use the Task tool 19 times in a single message (see parallelism strategy)
|
||||
3. **Handle JetStream** — if INITIAL mode, launch 5 JS sub-passes
|
||||
4. **Wait for completion** — all subagents run in background; you're notified on completion
|
||||
5. **Collect LOC** — run the LOC counting script
|
||||
6. **Update stillmissing.md** — edit the table with new numbers
|
||||
7. **Generate summary** — run the status counting script and format the report
|
||||
|
||||
## Quick-Start: Incremental Update
|
||||
|
||||
After porting work has been done:
|
||||
|
||||
1. **Identify changed categories** — check git diff for which `src/` directories were modified:
|
||||
```bash
|
||||
git diff --name-only HEAD~10 -- src/ | sed 's|src/NATS.Server/||' | cut -d/ -f1 | sort -u
|
||||
```
|
||||
2. **Launch subagents only for changed categories** — no need to re-analyze unchanged modules
|
||||
3. **Collect LOC and update stillmissing.md** — same as full run but only for changed categories
|
||||
|
||||
---
|
||||
|
||||
## Error Handling
|
||||
|
||||
- **Subagent hits context limit**: This typically happens with LARGE/X-LARGE categories. If a subagent reports it couldn't finish, resume it with the `resume` parameter and instruct it to continue from where it left off.
|
||||
- **Subagent finds no .NET files**: The category may not be ported yet. Mark all items as MISSING and note the empty state.
|
||||
- **Conflicting writes to jetstream.md**: If running JS sub-passes in parallel, use the section-header approach described above. If conflicts occur, run sub-passes sequentially instead.
|
||||
- **Go files changed upstream**: If the Go reference was updated (`golang/nats-server/`), run a full INITIAL analysis to catch new symbols. Check with: `git log --oneline -5 -- golang/`
|
||||
402
gaps/internal-ds.md
Normal file
402
gaps/internal-ds.md
Normal file
@@ -0,0 +1,402 @@
|
||||
# Internal Data Structures — Gap Analysis
|
||||
|
||||
> This file tracks what has and hasn't been ported from Go to .NET for the **Internal Data Structures** 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 Internal Data Structures
|
||||
|
||||
- This category is at **105% LOC parity** — above Go.
|
||||
- The **subject tree** (stree) uses adaptive node sizing (4→10→16→48→256 children) for memory efficiency.
|
||||
- The **AVL tree** is used for tracking acknowledged sequences in JetStream consumers.
|
||||
- The **time hash wheel** provides O(1) TTL expiration checks.
|
||||
- **PSE** (platform-specific extensions) queries process RSS/CPU — may use `System.Diagnostics.Process` in .NET.
|
||||
- **sysmem** queries total system memory — may use `GC.GetGCMemoryInfo()` or P/Invoke in .NET.
|
||||
- Many Go files in pse/ and sysmem/ are platform-specific build-tagged files — classify platform-irrelevant ones as NOT_APPLICABLE.
|
||||
|
||||
---
|
||||
|
||||
## Go Reference Files (Source)
|
||||
|
||||
- `golang/nats-server/server/avl/seqset.go` — AVL tree for sparse sequence sets (JetStream ack tracking)
|
||||
- `golang/nats-server/server/stree/` — Subject tree with adaptive nodes (node4, node10, node16, node48, node256) for per-subject state in streams. 11 files.
|
||||
- `golang/nats-server/server/thw/thw.go` — Time hash wheel for efficient TTL expiration
|
||||
- `golang/nats-server/server/gsl/gsl.go` — Generic subject list, optimized trie variant
|
||||
- `golang/nats-server/server/pse/` — Platform-specific extensions (proc info: RSS, CPU). 12 files across platforms.
|
||||
- `golang/nats-server/server/sysmem/` — System memory queries. 8 files across platforms.
|
||||
|
||||
## Go Reference Files (Tests)
|
||||
|
||||
- `golang/nats-server/server/avl/seqset_test.go`
|
||||
- `golang/nats-server/server/stree/stree_test.go`
|
||||
- `golang/nats-server/server/thw/thw_test.go`
|
||||
- `golang/nats-server/server/gsl/gsl_test.go`
|
||||
|
||||
## .NET Implementation Files (Source)
|
||||
|
||||
- `src/NATS.Server/Internal/` (all files including subdirectories)
|
||||
- `src/NATS.Server/Internal/Avl/`
|
||||
- `src/NATS.Server/Internal/Gsl/`
|
||||
- `src/NATS.Server/Internal/SubjectTree/`
|
||||
- `src/NATS.Server/Internal/TimeHashWheel/`
|
||||
|
||||
## .NET Implementation Files (Tests)
|
||||
|
||||
- `tests/NATS.Server.Tests/Internal/` (all subdirectories)
|
||||
|
||||
---
|
||||
|
||||
## Gap Inventory
|
||||
|
||||
<!-- After analysis, fill in this table. Group rows by Go source file. -->
|
||||
|
||||
### avl/seqset.go
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `SequenceSet` (struct) | `golang/nats-server/server/avl/seqset.go:33` | PORTED | `src/NATS.Server/Internal/Avl/SequenceSet.cs:26` | Full class with all fields |
|
||||
| `SequenceSet.Insert` | `golang/nats-server/server/avl/seqset.go:44` | PORTED | `src/NATS.Server/Internal/Avl/SequenceSet.cs:52` | |
|
||||
| `SequenceSet.Exists` | `golang/nats-server/server/avl/seqset.go:52` | PORTED | `src/NATS.Server/Internal/Avl/SequenceSet.cs:63` | |
|
||||
| `SequenceSet.SetInitialMin` | `golang/nats-server/server/avl/seqset.go:69` | PORTED | `src/NATS.Server/Internal/Avl/SequenceSet.cs:89` | Go returns error; .NET throws |
|
||||
| `SequenceSet.Delete` | `golang/nats-server/server/avl/seqset.go:80` | PORTED | `src/NATS.Server/Internal/Avl/SequenceSet.cs:103` | |
|
||||
| `SequenceSet.Size` | `golang/nats-server/server/avl/seqset.go:97` | PORTED | `src/NATS.Server/Internal/Avl/SequenceSet.cs:43` | Property in .NET |
|
||||
| `SequenceSet.Nodes` | `golang/nats-server/server/avl/seqset.go:102` | PORTED | `src/NATS.Server/Internal/Avl/SequenceSet.cs:46` | Property in .NET |
|
||||
| `SequenceSet.Empty` | `golang/nats-server/server/avl/seqset.go:107` | PORTED | `src/NATS.Server/Internal/Avl/SequenceSet.cs:127` | |
|
||||
| `SequenceSet.IsEmpty` | `golang/nats-server/server/avl/seqset.go:114` | PORTED | `src/NATS.Server/Internal/Avl/SequenceSet.cs:49` | Property in .NET |
|
||||
| `SequenceSet.Range` | `golang/nats-server/server/avl/seqset.go:124` | PORTED | `src/NATS.Server/Internal/Avl/SequenceSet.cs:138` | |
|
||||
| `SequenceSet.Heights` | `golang/nats-server/server/avl/seqset.go:129` | PORTED | `src/NATS.Server/Internal/Avl/SequenceSet.cs:141` | Returns tuple in .NET |
|
||||
| `SequenceSet.State` | `golang/nats-server/server/avl/seqset.go:143` | PORTED | `src/NATS.Server/Internal/Avl/SequenceSet.cs:153` | Returns named tuple |
|
||||
| `SequenceSet.MinMax` | `golang/nats-server/server/avl/seqset.go:152` | PORTED | `src/NATS.Server/Internal/Avl/SequenceSet.cs:166` | |
|
||||
| `clone` (unexported) | `golang/nats-server/server/avl/seqset.go:169` | PORTED | `src/NATS.Server/Internal/Avl/SequenceSet.cs:424` | `CloneNode` private static method |
|
||||
| `SequenceSet.Clone` | `golang/nats-server/server/avl/seqset.go:180` | PORTED | `src/NATS.Server/Internal/Avl/SequenceSet.cs:195` | |
|
||||
| `SequenceSet.Union` (method) | `golang/nats-server/server/avl/seqset.go:191` | PORTED | `src/NATS.Server/Internal/Avl/SequenceSet.cs:203` | |
|
||||
| `Union` (function) | `golang/nats-server/server/avl/seqset.go:208` | PORTED | `src/NATS.Server/Internal/Avl/SequenceSet.cs:228` | `SequenceSet.CreateUnion` static method |
|
||||
| `SequenceSet.EncodeLen` | `golang/nats-server/server/avl/seqset.go:238` | PORTED | `src/NATS.Server/Internal/Avl/SequenceSet.cs:252` | `EncodeLength()` in .NET |
|
||||
| `SequenceSet.Encode` | `golang/nats-server/server/avl/seqset.go:242` | PARTIAL | `src/NATS.Server/Internal/Avl/SequenceSet.cs:255` | Go signature takes optional buf to reuse; .NET always allocates new. Behavior equivalent. |
|
||||
| `ErrBadEncoding` | `golang/nats-server/server/avl/seqset.go:276` | PORTED | `src/NATS.Server/Internal/Avl/SequenceSet.cs:288` | Thrown as `InvalidOperationException` |
|
||||
| `ErrBadVersion` | `golang/nats-server/server/avl/seqset.go:277` | PORTED | `src/NATS.Server/Internal/Avl/SequenceSet.cs:295` | Thrown as `InvalidOperationException` |
|
||||
| `ErrSetNotEmpty` | `golang/nats-server/server/avl/seqset.go:278` | PORTED | `src/NATS.Server/Internal/Avl/SequenceSet.cs:93` | Thrown as `InvalidOperationException` |
|
||||
| `Decode` | `golang/nats-server/server/avl/seqset.go:282` | PORTED | `src/NATS.Server/Internal/Avl/SequenceSet.cs:285` | Static method, same v1/v2 dispatch |
|
||||
| `decodev2` (unexported) | `golang/nats-server/server/avl/seqset.go:298` | PORTED | `src/NATS.Server/Internal/Avl/SequenceSet.cs:300` | `DecodeV2` private static |
|
||||
| `decodev1` (unexported) | `golang/nats-server/server/avl/seqset.go:329` | PORTED | `src/NATS.Server/Internal/Avl/SequenceSet.cs:338` | `DecodeV1` private static |
|
||||
| `SequenceSet.insertNode` (unexported) | `golang/nats-server/server/avl/seqset.go:376` | PORTED | `src/NATS.Server/Internal/Avl/SequenceSet.cs:389` | `InsertNode` private method |
|
||||
| `node` (struct) | `golang/nats-server/server/avl/seqset.go:407` | PORTED | `src/NATS.Server/Internal/Avl/SequenceSet.cs:439` | `Node` nested class |
|
||||
| `node.set` | `golang/nats-server/server/avl/seqset.go:418` | PORTED | `src/NATS.Server/Internal/Avl/SequenceSet.cs:448` | `SetBit` |
|
||||
| `node.insert` | `golang/nats-server/server/avl/seqset.go:428` | PORTED | `src/NATS.Server/Internal/Avl/SequenceSet.cs:521` | `Node.Insert` static |
|
||||
| `node.rotateL` | `golang/nats-server/server/avl/seqset.go:464` | PORTED | `src/NATS.Server/Internal/Avl/SequenceSet.cs:672` | `RotateLeft` private static |
|
||||
| `node.rotateR` | `golang/nats-server/server/avl/seqset.go:478` | PORTED | `src/NATS.Server/Internal/Avl/SequenceSet.cs:692` | `RotateRight` private static |
|
||||
| `balanceF` | `golang/nats-server/server/avl/seqset.go:492` | PORTED | `src/NATS.Server/Internal/Avl/SequenceSet.cs:711` | `BalanceFactor` internal static |
|
||||
| `maxH` | `golang/nats-server/server/avl/seqset.go:506` | PORTED | `src/NATS.Server/Internal/Avl/SequenceSet.cs:724` | `MaxHeight` internal static |
|
||||
| `node.clear` | `golang/nats-server/server/avl/seqset.go:526` | PORTED | `src/NATS.Server/Internal/Avl/SequenceSet.cs:461` | `ClearBit` |
|
||||
| `node.delete` | `golang/nats-server/server/avl/seqset.go:542` | PORTED | `src/NATS.Server/Internal/Avl/SequenceSet.cs:571` | `Node.Delete` static |
|
||||
| `node.insertNodePrev` | `golang/nats-server/server/avl/seqset.go:588` | PORTED | `src/NATS.Server/Internal/Avl/SequenceSet.cs:635` | `InsertNodePrev` private static |
|
||||
| `node.exists` | `golang/nats-server/server/avl/seqset.go:613` | PORTED | `src/NATS.Server/Internal/Avl/SequenceSet.cs:484` | `ExistsBit` |
|
||||
| `node.min` | `golang/nats-server/server/avl/seqset.go:622` | PORTED | `src/NATS.Server/Internal/Avl/SequenceSet.cs:493` | `Min()` |
|
||||
| `node.max` | `golang/nats-server/server/avl/seqset.go:635` | PORTED | `src/NATS.Server/Internal/Avl/SequenceSet.cs:508` | `Max()` |
|
||||
| `node.nodeIter` | `golang/nats-server/server/avl/seqset.go:647` | PORTED | `src/NATS.Server/Internal/Avl/SequenceSet.cs:738` | `NodeIter` internal static |
|
||||
| `node.iter` | `golang/nats-server/server/avl/seqset.go:658` | PORTED | `src/NATS.Server/Internal/Avl/SequenceSet.cs:751` | `Iter` internal static |
|
||||
|
||||
---
|
||||
|
||||
### stree/stree.go
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `SubjectTree[T]` (struct) | `golang/nats-server/server/stree/stree.go:28` | PORTED | `src/NATS.Server/Internal/SubjectTree/SubjectTree.cs:9` | |
|
||||
| `NewSubjectTree[T]` | `golang/nats-server/server/stree/stree.go:34` | PORTED | `src/NATS.Server/Internal/SubjectTree/SubjectTree.cs:9` | In .NET, `new SubjectTree<T>()` used directly |
|
||||
| `SubjectTree.Size` | `golang/nats-server/server/stree/stree.go:39` | PORTED | `src/NATS.Server/Internal/SubjectTree/SubjectTree.cs:17` | |
|
||||
| `SubjectTree.Empty` | `golang/nats-server/server/stree/stree.go:47` | PORTED | `src/NATS.Server/Internal/SubjectTree/SubjectTree.cs:22` | |
|
||||
| `SubjectTree.Insert` | `golang/nats-server/server/stree/stree.go:56` | PORTED | `src/NATS.Server/Internal/SubjectTree/SubjectTree.cs:33` | Signature uses `ReadOnlySpan<byte>` |
|
||||
| `SubjectTree.Find` | `golang/nats-server/server/stree/stree.go:74` | PORTED | `src/NATS.Server/Internal/SubjectTree/SubjectTree.cs:52` | |
|
||||
| `SubjectTree.Delete` | `golang/nats-server/server/stree/stree.go:106` | PORTED | `src/NATS.Server/Internal/SubjectTree/SubjectTree.cs:97` | |
|
||||
| `SubjectTree.Match` | `golang/nats-server/server/stree/stree.go:119` | PORTED | `src/NATS.Server/Internal/SubjectTree/SubjectTree.cs:115` | |
|
||||
| `SubjectTree.MatchUntil` | `golang/nats-server/server/stree/stree.go:137` | PORTED | `src/NATS.Server/Internal/SubjectTree/SubjectTree.cs:135` | |
|
||||
| `SubjectTree.IterOrdered` | `golang/nats-server/server/stree/stree.go:149` | PORTED | `src/NATS.Server/Internal/SubjectTree/SubjectTree.cs:149` | |
|
||||
| `SubjectTree.IterFast` | `golang/nats-server/server/stree/stree.go:158` | PORTED | `src/NATS.Server/Internal/SubjectTree/SubjectTree.cs:158` | |
|
||||
| `SubjectTree.insert` (internal) | `golang/nats-server/server/stree/stree.go:169` | PORTED | `src/NATS.Server/Internal/SubjectTree/SubjectTree.cs:170` | `InsertInternal` |
|
||||
| `SubjectTree.delete` (internal) | `golang/nats-server/server/stree/stree.go:253` | PORTED | `src/NATS.Server/Internal/SubjectTree/SubjectTree.cs:286` | `DeleteInternal` |
|
||||
| `SubjectTree.match` (internal) | `golang/nats-server/server/stree/stree.go:318` | PORTED | `src/NATS.Server/Internal/SubjectTree/SubjectTree.cs:383` | `MatchInternal` |
|
||||
| `SubjectTree.iter` (internal) | `golang/nats-server/server/stree/stree.go:418` | PORTED | `src/NATS.Server/Internal/SubjectTree/SubjectTree.cs:512` | `IterInternal` |
|
||||
| `LazyIntersect[TL,TR]` | `golang/nats-server/server/stree/stree.go:463` | PORTED | `src/NATS.Server/Internal/SubjectTree/SubjectTree.cs:584` | `SubjectTreeHelper.LazyIntersect` static |
|
||||
| `IntersectGSL[T,SL]` | `golang/nats-server/server/stree/stree.go:488` | MISSING | — | No .NET equivalent. Intersects stree with gsl.GenericSublist. Used in JetStream consumer NumPending. |
|
||||
| `_intersectGSL` (internal) | `golang/nats-server/server/stree/stree.go:496` | MISSING | — | Helper for IntersectGSL |
|
||||
| `hasInterestForTokens` (internal) | `golang/nats-server/server/stree/stree.go:521` | MISSING | — | Token-boundary interest check for GSL intersection |
|
||||
| `bytesToString` (internal) | `golang/nats-server/server/stree/stree.go:534` | NOT_APPLICABLE | — | Go `unsafe` zero-copy string conversion. In .NET, `System.Text.Encoding.UTF8.GetString` or `MemoryMarshal.Cast` used instead |
|
||||
|
||||
---
|
||||
|
||||
### stree/node.go
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `node` (interface) | `golang/nats-server/server/stree/node.go:17` | PORTED | `src/NATS.Server/Internal/SubjectTree/Nodes.cs:7` | `INode` interface |
|
||||
| `meta` (struct) | `golang/nats-server/server/stree/node.go:35` | PORTED | `src/NATS.Server/Internal/SubjectTree/Nodes.cs:46` | `NodeMeta` class |
|
||||
| `meta.isLeaf` | `golang/nats-server/server/stree/node.go:40` | PORTED | `src/NATS.Server/Internal/SubjectTree/Nodes.cs:9` | Via `INode.IsLeaf` |
|
||||
| `meta.base` | `golang/nats-server/server/stree/node.go:41` | PORTED | `src/NATS.Server/Internal/SubjectTree/Nodes.cs:10` | Via `INode.Base` |
|
||||
| `meta.setPrefix` | `golang/nats-server/server/stree/node.go:43` | PORTED | `src/NATS.Server/Internal/SubjectTree/Nodes.cs:11` | Via `INode.SetPrefix` |
|
||||
| `meta.numChildren` | `golang/nats-server/server/stree/node.go:47` | PORTED | `src/NATS.Server/Internal/SubjectTree/Nodes.cs:26` | Via `INode.NumChildren` |
|
||||
| `meta.path` | `golang/nats-server/server/stree/node.go:48` | PORTED | `src/NATS.Server/Internal/SubjectTree/Nodes.cs:27` | Via `INode.Path` |
|
||||
| `meta.matchParts` | `golang/nats-server/server/stree/node.go:51` | PORTED | `src/NATS.Server/Internal/SubjectTree/Nodes.cs:22` | Via `INode.MatchParts` |
|
||||
|
||||
---
|
||||
|
||||
### stree/leaf.go
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `leaf[T]` (struct) | `golang/nats-server/server/stree/leaf.go:23` | PORTED | `src/NATS.Server/Internal/SubjectTree/Nodes.cs:58` | `Leaf<T>` class |
|
||||
| `newLeaf[T]` | `golang/nats-server/server/stree/leaf.go:30` | PORTED | `src/NATS.Server/Internal/SubjectTree/Nodes.cs:63` | `new Leaf<T>(suffix, value)` constructor |
|
||||
| `leaf.isLeaf` | `golang/nats-server/server/stree/leaf.go:34` | PORTED | `src/NATS.Server/Internal/SubjectTree/Nodes.cs:69` | |
|
||||
| `leaf.base` | `golang/nats-server/server/stree/leaf.go:34` | PORTED | `src/NATS.Server/Internal/SubjectTree/Nodes.cs:70` | Returns null |
|
||||
| `leaf.match` | `golang/nats-server/server/stree/leaf.go:36` | PORTED | `src/NATS.Server/Internal/SubjectTree/Nodes.cs:75` | |
|
||||
| `leaf.setSuffix` | `golang/nats-server/server/stree/leaf.go:37` | PORTED | `src/NATS.Server/Internal/SubjectTree/Nodes.cs:77` | |
|
||||
| `leaf.matchParts` | `golang/nats-server/server/stree/leaf.go:39` | PORTED | `src/NATS.Server/Internal/SubjectTree/Nodes.cs:85` | |
|
||||
| `leaf.iter` | `golang/nats-server/server/stree/leaf.go:40` | PORTED | `src/NATS.Server/Internal/SubjectTree/Nodes.cs:83` | No-op on leaf |
|
||||
| `leaf.children` | `golang/nats-server/server/stree/leaf.go:41` | PORTED | `src/NATS.Server/Internal/SubjectTree/Nodes.cs:81` | Returns empty array |
|
||||
| `leaf.numChildren` | `golang/nats-server/server/stree/leaf.go:42` | PORTED | `src/NATS.Server/Internal/SubjectTree/Nodes.cs:72` | Returns 0 |
|
||||
| `leaf.path` | `golang/nats-server/server/stree/leaf.go:43` | PORTED | `src/NATS.Server/Internal/SubjectTree/Nodes.cs:79` | Returns suffix |
|
||||
|
||||
---
|
||||
|
||||
### stree/node4.go
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `node4` (struct) | `golang/nats-server/server/stree/node4.go:18` | PORTED | `src/NATS.Server/Internal/SubjectTree/Nodes.cs:105` | `Node4` class |
|
||||
| `newNode4` | `golang/nats-server/server/stree/node4.go:24` | PORTED | `src/NATS.Server/Internal/SubjectTree/Nodes.cs:111` | Constructor |
|
||||
| `node4.addChild` | `golang/nats-server/server/stree/node4.go:31` | PORTED | `src/NATS.Server/Internal/SubjectTree/Nodes.cs:128` | |
|
||||
| `node4.findChild` | `golang/nats-server/server/stree/node4.go:40` | PORTED | `src/NATS.Server/Internal/SubjectTree/Nodes.cs:136` | Returns `ChildRef?` wrapper |
|
||||
| `node4.isFull` | `golang/nats-server/server/stree/node4.go:49` | PORTED | `src/NATS.Server/Internal/SubjectTree/Nodes.cs:119` | |
|
||||
| `node4.grow` | `golang/nats-server/server/stree/node4.go:51` | PORTED | `src/NATS.Server/Internal/SubjectTree/Nodes.cs:174` | |
|
||||
| `node4.deleteChild` | `golang/nats-server/server/stree/node4.go:60` | PORTED | `src/NATS.Server/Internal/SubjectTree/Nodes.cs:149` | |
|
||||
| `node4.shrink` | `golang/nats-server/server/stree/node4.go:80` | PORTED | `src/NATS.Server/Internal/SubjectTree/Nodes.cs:184` | |
|
||||
| `node4.iter` | `golang/nats-server/server/stree/node4.go:88` | PORTED | `src/NATS.Server/Internal/SubjectTree/Nodes.cs:190` | |
|
||||
| `node4.children` | `golang/nats-server/server/stree/node4.go:96` | PORTED | `src/NATS.Server/Internal/SubjectTree/Nodes.cs:198` | |
|
||||
|
||||
---
|
||||
|
||||
### stree/node10.go
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `node10` (struct) | `golang/nats-server/server/stree/node10.go:20` | PORTED | `src/NATS.Server/Internal/SubjectTree/Nodes.cs:217` | `Node10` class |
|
||||
| `newNode10` | `golang/nats-server/server/stree/node10.go:26` | PORTED | `src/NATS.Server/Internal/SubjectTree/Nodes.cs:223` | Constructor |
|
||||
| `node10.addChild` | `golang/nats-server/server/stree/node10.go:34` | PORTED | `src/NATS.Server/Internal/SubjectTree/Nodes.cs:240` | |
|
||||
| `node10.findChild` | `golang/nats-server/server/stree/node10.go:43` | PORTED | `src/NATS.Server/Internal/SubjectTree/Nodes.cs:248` | |
|
||||
| `node10.isFull` | `golang/nats-server/server/stree/node10.go:52` | PORTED | `src/NATS.Server/Internal/SubjectTree/Nodes.cs:231` | |
|
||||
| `node10.grow` | `golang/nats-server/server/stree/node10.go:54` | PORTED | `src/NATS.Server/Internal/SubjectTree/Nodes.cs:286` | |
|
||||
| `node10.deleteChild` | `golang/nats-server/server/stree/node10.go:63` | PORTED | `src/NATS.Server/Internal/SubjectTree/Nodes.cs:258` | |
|
||||
| `node10.shrink` | `golang/nats-server/server/stree/node10.go:83` | PORTED | `src/NATS.Server/Internal/SubjectTree/Nodes.cs:295` | |
|
||||
| `node10.iter` | `golang/nats-server/server/stree/node10.go:95` | PORTED | `src/NATS.Server/Internal/SubjectTree/Nodes.cs:307` | |
|
||||
| `node10.children` | `golang/nats-server/server/stree/node10.go:103` | PORTED | `src/NATS.Server/Internal/SubjectTree/Nodes.cs:315` | |
|
||||
|
||||
---
|
||||
|
||||
### stree/node16.go, stree/node48.go, stree/node256.go
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `node16` (struct) | `golang/nats-server/server/stree/node16.go` | PORTED | `src/NATS.Server/Internal/SubjectTree/Nodes.cs:334` | `Node16` class |
|
||||
| `node48` (struct) | `golang/nats-server/server/stree/node48.go` | PORTED | `src/NATS.Server/Internal/SubjectTree/Nodes.cs:451` | `Node48` class |
|
||||
| `node256` (struct) | `golang/nats-server/server/stree/node256.go` | PORTED | `src/NATS.Server/Internal/SubjectTree/Nodes.cs:569` | `Node256` class |
|
||||
|
||||
---
|
||||
|
||||
### stree/parts.go
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `genParts` | `golang/nats-server/server/stree/parts.go:23` | PORTED | `src/NATS.Server/Internal/SubjectTree/Parts.cs:57` | `Parts.GenParts` static method |
|
||||
| `matchParts` | `golang/nats-server/server/stree/parts.go:78` | PORTED | `src/NATS.Server/Internal/SubjectTree/Parts.cs:145` | `Parts.MatchPartsAgainstFragment` |
|
||||
|
||||
---
|
||||
|
||||
### stree/util.go
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `pwc`, `fwc`, `tsep` constants | `golang/nats-server/server/stree/util.go:17` | PORTED | `src/NATS.Server/Internal/SubjectTree/Parts.cs:10` | `Parts.Pwc`, `Parts.Fwc`, `Parts.Tsep` |
|
||||
| `commonPrefixLen` | `golang/nats-server/server/stree/util.go:24` | PORTED | `src/NATS.Server/Internal/SubjectTree/Parts.cs:33` | `Parts.CommonPrefixLen` |
|
||||
| `copyBytes` | `golang/nats-server/server/stree/util.go:36` | PORTED | `src/NATS.Server/Internal/SubjectTree/Parts.cs:47` | `Parts.CopyBytes` |
|
||||
| `position` (interface) | `golang/nats-server/server/stree/util.go:45` | NOT_APPLICABLE | — | Go generic constraint on numeric types; not needed in .NET |
|
||||
| `noPivot` constant | `golang/nats-server/server/stree/util.go:48` | PORTED | `src/NATS.Server/Internal/SubjectTree/Parts.cs:17` | `Parts.NoPivot` |
|
||||
| `pivot` | `golang/nats-server/server/stree/util.go:52` | PORTED | `src/NATS.Server/Internal/SubjectTree/Parts.cs:23` | `Parts.Pivot` |
|
||||
|
||||
---
|
||||
|
||||
### stree/dump.go
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `SubjectTree.Dump` | `golang/nats-server/server/stree/dump.go:23` | MISSING | — | Debug utility for printing tree structure to an `io.Writer`. No .NET equivalent. Low priority — debug/diagnostic only. |
|
||||
| `SubjectTree.dump` (internal) | `golang/nats-server/server/stree/dump.go:29` | MISSING | — | Internal helper for Dump |
|
||||
| `dumpPre` | `golang/nats-server/server/stree/dump.go:59` | MISSING | — | Indentation helper for Dump |
|
||||
| `leaf.kind`, `node4.kind`, etc. | `golang/nats-server/server/stree/dump.go:51` | PARTIAL | `src/NATS.Server/Internal/SubjectTree/Nodes.cs` | `INode.Kind` property exists on each node type, but `Dump` method is not implemented |
|
||||
|
||||
---
|
||||
|
||||
### thw/thw.go
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `ErrTaskNotFound` | `golang/nats-server/server/thw/thw.go:25` | PORTED | `src/NATS.Server/Internal/TimeHashWheel/HashWheel.cs:91` | Returned as `false`/exception instead of error |
|
||||
| `ErrInvalidVersion` | `golang/nats-server/server/thw/thw.go:28` | PORTED | `src/NATS.Server/Internal/TimeHashWheel/HashWheel.cs:289` | Thrown as `InvalidOperationException` |
|
||||
| `slot` (struct) | `golang/nats-server/server/thw/thw.go:39` | PORTED | `src/NATS.Server/Internal/TimeHashWheel/HashWheel.cs:406` | `Slot` internal class |
|
||||
| `HashWheel` (struct) | `golang/nats-server/server/thw/thw.go:45` | PORTED | `src/NATS.Server/Internal/TimeHashWheel/HashWheel.cs:17` | `HashWheel` class |
|
||||
| `HashWheelEntry` (struct) | `golang/nats-server/server/thw/thw.go:52` | MISSING | — | Go uses this struct for entry representation in some contexts; .NET uses `(ulong, long)` tuples inline instead |
|
||||
| `NewHashWheel` | `golang/nats-server/server/thw/thw.go:58` | PORTED | `src/NATS.Server/Internal/TimeHashWheel/HashWheel.cs:34` | Constructor in .NET |
|
||||
| `HashWheel.getPosition` (internal) | `golang/nats-server/server/thw/thw.go:66` | PORTED | `src/NATS.Server/Internal/TimeHashWheel/HashWheel.cs:50` | `GetPosition` private static |
|
||||
| `newSlot` (internal) | `golang/nats-server/server/thw/thw.go:71` | PORTED | `src/NATS.Server/Internal/TimeHashWheel/HashWheel.cs:406` | Inline slot initialization in .NET |
|
||||
| `HashWheel.Add` | `golang/nats-server/server/thw/thw.go:79` | PORTED | `src/NATS.Server/Internal/TimeHashWheel/HashWheel.cs:60` | Return type `void` vs `error` (never errors in practice) |
|
||||
| `HashWheel.Remove` | `golang/nats-server/server/thw/thw.go:103` | PORTED | `src/NATS.Server/Internal/TimeHashWheel/HashWheel.cs:91` | Returns `bool` instead of `error` |
|
||||
| `HashWheel.Update` | `golang/nats-server/server/thw/thw.go:123` | PORTED | `src/NATS.Server/Internal/TimeHashWheel/HashWheel.cs:122` | |
|
||||
| `HashWheel.ExpireTasks` | `golang/nats-server/server/thw/thw.go:133` | PORTED | `src/NATS.Server/Internal/TimeHashWheel/HashWheel.cs:135` | |
|
||||
| `HashWheel.expireTasks` (internal) | `golang/nats-server/server/thw/thw.go:138` | PORTED | `src/NATS.Server/Internal/TimeHashWheel/HashWheel.cs:148` | `ExpireTasksInternal` — public in .NET for testability |
|
||||
| `HashWheel.GetNextExpiration` | `golang/nats-server/server/thw/thw.go:182` | PORTED | `src/NATS.Server/Internal/TimeHashWheel/HashWheel.cs:219` | |
|
||||
| `HashWheel.Count` | `golang/nats-server/server/thw/thw.go:190` | PORTED | `src/NATS.Server/Internal/TimeHashWheel/HashWheel.cs:44` | Property in .NET |
|
||||
| `HashWheel.Encode` | `golang/nats-server/server/thw/thw.go:197` | PORTED | `src/NATS.Server/Internal/TimeHashWheel/HashWheel.cs:235` | |
|
||||
| `HashWheel.Decode` | `golang/nats-server/server/thw/thw.go:216` | PORTED | `src/NATS.Server/Internal/TimeHashWheel/HashWheel.cs:282` | Returns `(highSeq, bytesRead)` tuple; Go returns `(uint64, error)` |
|
||||
|
||||
---
|
||||
|
||||
### gsl/gsl.go
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `ErrInvalidSubject` | `golang/nats-server/server/gsl/gsl.go:41` | PORTED | `src/NATS.Server/Internal/Gsl/GenericSubjectList.cs:11` | `GslErrors.InvalidSubject` |
|
||||
| `ErrNotFound` | `golang/nats-server/server/gsl/gsl.go:42` | PORTED | `src/NATS.Server/Internal/Gsl/GenericSubjectList.cs:12` | `GslErrors.NotFound` |
|
||||
| `ErrNilChan` | `golang/nats-server/server/gsl/gsl.go:43` | NOT_APPLICABLE | — | Go channel-specific error; channels don't apply to .NET pattern |
|
||||
| `ErrAlreadyRegistered` | `golang/nats-server/server/gsl/gsl.go:44` | NOT_APPLICABLE | — | Used by notification channels in Go; no notification channel pattern in .NET port |
|
||||
| `SimpleSublist` (type alias) | `golang/nats-server/server/gsl/gsl.go:49` | PARTIAL | `src/NATS.Server/Internal/Gsl/GenericSubjectList.cs:650` | `SimpleSubjectList` uses `int` instead of `struct{}`. Functionally equivalent but not a true zero-allocation alias |
|
||||
| `NewSimpleSublist` | `golang/nats-server/server/gsl/gsl.go:52` | PORTED | `src/NATS.Server/Internal/Gsl/GenericSubjectList.cs:650` | `new SimpleSubjectList()` |
|
||||
| `GenericSublist[T]` (struct) | `golang/nats-server/server/gsl/gsl.go:57` | PORTED | `src/NATS.Server/Internal/Gsl/GenericSubjectList.cs:76` | `GenericSubjectList<T>` |
|
||||
| `node[T]` (struct) | `golang/nats-server/server/gsl/gsl.go:64` | PORTED | `src/NATS.Server/Internal/Gsl/GenericSubjectList.cs:52` | `Node<T>` |
|
||||
| `level[T]` (struct) | `golang/nats-server/server/gsl/gsl.go:71` | PORTED | `src/NATS.Server/Internal/Gsl/GenericSubjectList.cs:19` | `Level<T>` |
|
||||
| `newNode[T]` | `golang/nats-server/server/gsl/gsl.go:77` | PORTED | `src/NATS.Server/Internal/Gsl/GenericSubjectList.cs:52` | Inline `new Node<T>()` |
|
||||
| `newLevel[T]` | `golang/nats-server/server/gsl/gsl.go:82` | PORTED | `src/NATS.Server/Internal/Gsl/GenericSubjectList.cs:19` | Inline `new Level<T>()` |
|
||||
| `NewSublist[T]` | `golang/nats-server/server/gsl/gsl.go:87` | PORTED | `src/NATS.Server/Internal/Gsl/GenericSubjectList.cs:76` | `new GenericSubjectList<T>()` constructor |
|
||||
| `GenericSublist.Insert` | `golang/nats-server/server/gsl/gsl.go:92` | PORTED | `src/NATS.Server/Internal/Gsl/GenericSubjectList.cs:110` | Throws instead of returning error |
|
||||
| `GenericSublist.Match` | `golang/nats-server/server/gsl/gsl.go:150` | PORTED | `src/NATS.Server/Internal/Gsl/GenericSubjectList.cs:205` | |
|
||||
| `GenericSublist.MatchBytes` | `golang/nats-server/server/gsl/gsl.go:156` | PORTED | `src/NATS.Server/Internal/Gsl/GenericSubjectList.cs:214` | |
|
||||
| `GenericSublist.HasInterest` | `golang/nats-server/server/gsl/gsl.go:162` | PORTED | `src/NATS.Server/Internal/Gsl/GenericSubjectList.cs:225` | |
|
||||
| `GenericSublist.NumInterest` | `golang/nats-server/server/gsl/gsl.go:168` | PORTED | `src/NATS.Server/Internal/Gsl/GenericSubjectList.cs:234` | |
|
||||
| `GenericSublist.match` (internal) | `golang/nats-server/server/gsl/gsl.go:173` | PORTED | `src/NATS.Server/Internal/Gsl/GenericSubjectList.cs:274` | `MatchInternal` |
|
||||
| `GenericSublist.hasInterest` (internal) | `golang/nats-server/server/gsl/gsl.go:198` | PORTED | `src/NATS.Server/Internal/Gsl/GenericSubjectList.cs:295` | `HasInterestInternal` |
|
||||
| `matchLevelForAny[T]` | `golang/nats-server/server/gsl/gsl.go:223` | PORTED | `src/NATS.Server/Internal/Gsl/GenericSubjectList.cs:400` | `MatchLevelForAny` private static |
|
||||
| `callbacksForResults[T]` | `golang/nats-server/server/gsl/gsl.go:266` | PORTED | `src/NATS.Server/Internal/Gsl/GenericSubjectList.cs:448` | `CallbacksForResults` private static |
|
||||
| `matchLevel[T]` | `golang/nats-server/server/gsl/gsl.go:273` | PORTED | `src/NATS.Server/Internal/Gsl/GenericSubjectList.cs:371` | `MatchLevel` private static |
|
||||
| `lnt[T]` (struct) | `golang/nats-server/server/gsl/gsl.go:301` | PORTED | `src/NATS.Server/Internal/Gsl/GenericSubjectList.cs:68` | `Lnt<T>` record struct |
|
||||
| `GenericSublist.remove` (internal) | `golang/nats-server/server/gsl/gsl.go:308` | PORTED | `src/NATS.Server/Internal/Gsl/GenericSubjectList.cs:458` | `RemoveInternal` |
|
||||
| `GenericSublist.Remove` | `golang/nats-server/server/gsl/gsl.go:368` | PORTED | `src/NATS.Server/Internal/Gsl/GenericSubjectList.cs:188` | |
|
||||
| `GenericSublist.HasInterestStartingIn` | `golang/nats-server/server/gsl/gsl.go:373` | PORTED | `src/NATS.Server/Internal/Gsl/GenericSubjectList.cs:245` | |
|
||||
| `hasInterestStartingIn[T]` (internal) | `golang/nats-server/server/gsl/gsl.go:381` | PORTED | `src/NATS.Server/Internal/Gsl/GenericSubjectList.cs:537` | `HasInterestStartingInLevel` private static |
|
||||
| `level.pruneNode` | `golang/nats-server/server/gsl/gsl.go:403` | PORTED | `src/NATS.Server/Internal/Gsl/GenericSubjectList.cs:37` | `Level<T>.PruneNode` |
|
||||
| `node.isEmpty` | `golang/nats-server/server/gsl/gsl.go:418` | PORTED | `src/NATS.Server/Internal/Gsl/GenericSubjectList.cs:61` | `Node<T>.IsEmpty()` |
|
||||
| `level.numNodes` | `golang/nats-server/server/gsl/gsl.go:423` | PORTED | `src/NATS.Server/Internal/Gsl/GenericSubjectList.cs:25` | `Level<T>.NumNodes()` |
|
||||
| `GenericSublist.removeFromNode` (internal) | `golang/nats-server/server/gsl/gsl.go:438` | PORTED | `src/NATS.Server/Internal/Gsl/GenericSubjectList.cs:527` | `RemoveFromNode` private static |
|
||||
| `GenericSublist.Count` | `golang/nats-server/server/gsl/gsl.go:449` | PORTED | `src/NATS.Server/Internal/Gsl/GenericSubjectList.cs:90` | |
|
||||
| `GenericSublist.numLevels` (internal) | `golang/nats-server/server/gsl/gsl.go:457` | PORTED | `src/NATS.Server/Internal/Gsl/GenericSubjectList.cs:264` | `NumLevels()` internal |
|
||||
| `visitLevel[T]` | `golang/nats-server/server/gsl/gsl.go:463` | PORTED | `src/NATS.Server/Internal/Gsl/GenericSubjectList.cs:557` | `VisitLevel` private static |
|
||||
| `tokenizeSubjectIntoSlice` | `golang/nats-server/server/gsl/gsl.go:496` | PORTED | `src/NATS.Server/Internal/Gsl/GenericSubjectList.cs:348` | `TokenizeSubjectIntoSpan` |
|
||||
|
||||
---
|
||||
|
||||
### pse/ (Platform-Specific Extensions)
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `ProcUsage` (darwin) | `golang/nats-server/server/pse/pse_darwin.go:83` | PORTED | `src/NATS.Server/Monitoring/VarzHandler.cs:35` | CPU via `Process.TotalProcessorTime`; RSS via `proc.WorkingSet64`. Equivalent behavior in `/varz` reporting. |
|
||||
| `ProcUsage` (linux) | `golang/nats-server/server/pse/pse_linux.go` | PORTED | `src/NATS.Server/Monitoring/VarzHandler.cs:35` | Same cross-platform .NET equivalent |
|
||||
| `ProcUsage` (windows) | `golang/nats-server/server/pse/pse_windows.go` | PORTED | `src/NATS.Server/Monitoring/VarzHandler.cs:35` | Same cross-platform .NET equivalent |
|
||||
| `ProcUsage` (freebsd/netbsd/openbsd/dragonfly/solaris/zos) | `golang/nats-server/server/pse/pse_*.go` | NOT_APPLICABLE | — | Platform-specific Go build-tagged files. .NET runtime abstracts these OS differences. |
|
||||
| `ProcUsage` (wasm/rumprun) | `golang/nats-server/server/pse/pse_wasm.go` | NOT_APPLICABLE | — | Stub/no-op implementations for unsupported platforms; not needed in .NET |
|
||||
| `updateUsage` (darwin, internal) | `golang/nats-server/server/pse/pse_darwin.go:56` | PORTED | `src/NATS.Server/Monitoring/VarzHandler.cs:39` | CPU sampling logic in VarzHandler |
|
||||
| `periodic` (darwin, internal) | `golang/nats-server/server/pse/pse_darwin.go:76` | PARTIAL | `src/NATS.Server/Monitoring/VarzHandler.cs:39` | Go runs periodic background timer; .NET samples on each `/varz` request with 1s cache. Semantics slightly different. |
|
||||
|
||||
---
|
||||
|
||||
### sysmem/ (System Memory Queries)
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `Memory` (darwin) | `golang/nats-server/server/sysmem/mem_darwin.go:18` | MISSING | — | Queries total physical RAM via `hw.memsize` sysctl. No .NET equivalent in codebase. Used by JetStream for sizing decisions. |
|
||||
| `Memory` (linux) | `golang/nats-server/server/sysmem/mem_linux.go:20` | MISSING | — | Queries via `syscall.Sysinfo`. No .NET equivalent found. |
|
||||
| `Memory` (windows) | `golang/nats-server/server/sysmem/mem_windows.go` | MISSING | — | No .NET equivalent found. Can be implemented via `GC.GetGCMemoryInfo().TotalAvailableMemoryBytes`. |
|
||||
| `Memory` (bsd/solaris/wasm/zos) | `golang/nats-server/server/sysmem/mem_bsd.go` etc. | NOT_APPLICABLE | — | Platform-specific stubs; .NET runtime abstracts these. `GCMemoryInfo` is the cross-platform equivalent. |
|
||||
| `sysctlInt64` | `golang/nats-server/server/sysmem/sysctl.go:23` | NOT_APPLICABLE | — | Darwin/BSD internal helper using unsafe sysctl; .NET abstracts this entirely |
|
||||
|
||||
---
|
||||
|
||||
## 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/Internal/ -name '*.cs' -type f -exec cat {} + | wc -l
|
||||
# Re-count .NET test LOC for this module
|
||||
find tests/NATS.Server.Tests/Internal/ -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-25 | File created with LLM analysis instructions | auto |
|
||||
| 2026-02-25 | Full gap inventory populated: 157 PORTED, 4 PARTIAL, 10 MISSING, 8 NOT_APPLICABLE, 0 DEFERRED | auto |
|
||||
1935
gaps/jetstream.md
Normal file
1935
gaps/jetstream.md
Normal file
File diff suppressed because it is too large
Load Diff
280
gaps/leaf-nodes.md
Normal file
280
gaps/leaf-nodes.md
Normal file
@@ -0,0 +1,280 @@
|
||||
# Leaf Nodes — Gap Analysis
|
||||
|
||||
> This file tracks what has and hasn't been ported from Go to .NET for the **Leaf Nodes** 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 Leaf Nodes
|
||||
|
||||
- Leaf nodes only share subscription interest with the hub — no full mesh.
|
||||
- Loop detection uses the `$LDS.` subject prefix.
|
||||
- Leaf connections use `ClientKind = LEAF`.
|
||||
- Leaf nodes can connect through WebSocket and support TLS.
|
||||
|
||||
---
|
||||
|
||||
## Go Reference Files (Source)
|
||||
|
||||
- `golang/nats-server/server/leafnode.go` — Hub-and-spoke topology for edge deployments (~3,470 lines). Only subscribed subjects shared with hub. Loop detection via `$LDS.` prefix.
|
||||
|
||||
## Go Reference Files (Tests)
|
||||
|
||||
- `golang/nats-server/server/leafnode_test.go`
|
||||
- `golang/nats-server/server/leafnode_proxy_test.go`
|
||||
- `golang/nats-server/test/leafnode_test.go` (integration)
|
||||
|
||||
## .NET Implementation Files (Source)
|
||||
|
||||
- `src/NATS.Server/LeafNodes/` (all files)
|
||||
|
||||
## .NET Implementation Files (Tests)
|
||||
|
||||
- `tests/NATS.Server.Tests/LeafNodes/`
|
||||
|
||||
---
|
||||
|
||||
## Gap Inventory
|
||||
|
||||
<!-- After analysis, fill in this table. Group rows by Go source file. -->
|
||||
|
||||
### golang/nats-server/server/leafnode.go
|
||||
|
||||
#### Constants and Types
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `leafnodeTLSInsecureWarning` (const) | `golang/nats-server/server/leafnode.go:47` | PORTED | `src/NATS.Server/LeafNodes/LeafNodeManager.cs` | Warning logged in `DisableLeafConnect`; same semantic intent, no separate constant |
|
||||
| `leafNodeReconnectDelayAfterLoopDetected` (const) | `golang/nats-server/server/leafnode.go:50` | MISSING | — | 30s reconnect delay after loop detection. .NET loop detector (`LeafLoopDetector`) detects but does not enforce the delay on reconnect |
|
||||
| `leafNodeReconnectAfterPermViolation` (const) | `golang/nats-server/server/leafnode.go:54` | MISSING | — | 30s reconnect delay after permission violation. No .NET equivalent enforced |
|
||||
| `leafNodeReconnectDelayAfterClusterNameSame` (const) | `golang/nats-server/server/leafnode.go:57` | MISSING | — | 30s delay when same cluster name detected. No .NET equivalent |
|
||||
| `leafNodeLoopDetectionSubjectPrefix` (const `"$LDS."`) | `golang/nats-server/server/leafnode.go:60` | PORTED | `src/NATS.Server/LeafNodes/LeafLoopDetector.cs:5` | `LeafLoopPrefix = "$LDS."` |
|
||||
| `leafNodeWSPath` (const `"/leafnode"`) | `golang/nats-server/server/leafnode.go:64` | PORTED | `src/NATS.Server/LeafNodes/WebSocketStreamAdapter.cs` | Path constant is implicit in the WS adapter; not a named constant in .NET |
|
||||
| `leafNodeWaitBeforeClose` (const 5s) | `golang/nats-server/server/leafnode.go:68` | MISSING | — | Minimum version wait-before-close timer. Not ported |
|
||||
| `leaf` (unexported struct) | `golang/nats-server/server/leafnode.go:71` | PARTIAL | `src/NATS.Server/LeafNodes/LeafConnection.cs` | `LeafConnection` covers `remote`, `isSpoke`, `remoteCluster`, `remoteServer`, `remoteDomain`. Missing: `isolated`, `smap`, `tsub/tsubt` (transient sub map), `compression`, `gwSub` |
|
||||
| `leafNodeCfg` (unexported struct) | `golang/nats-server/server/leafnode.go:107` | PARTIAL | `src/NATS.Server/Configuration/LeafNodeOptions.cs` | `RemoteLeafOptions` covers URLs, credentials, local account. Missing: `curURL`, `tlsName`, `username/password` (runtime fields), `perms`, `connDelay`, `jsMigrateTimer` |
|
||||
| `leafConnectInfo` (unexported struct) | `golang/nats-server/server/leafnode.go:2001` | MISSING | — | JSON CONNECT payload for leaf solicited connections (JWT, Nkey, Sig, Hub, Cluster, Headers, JetStream, Compression, RemoteAccount, Proto). Not represented in .NET |
|
||||
|
||||
#### Methods on `client` (receiver functions)
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `(c *client) isSolicitedLeafNode()` | `golang/nats-server/server/leafnode.go:121` | MISSING | — | No `client` type in .NET; `LeafConnection` does not track solicited vs. accepted role |
|
||||
| `(c *client) isSpokeLeafNode()` | `golang/nats-server/server/leafnode.go:127` | MISSING | — | Hub/spoke role tracking missing in .NET |
|
||||
| `(c *client) isHubLeafNode()` | `golang/nats-server/server/leafnode.go:131` | MISSING | — | Hub role helper missing in .NET |
|
||||
| `(c *client) isIsolatedLeafNode()` | `golang/nats-server/server/leafnode.go:135` | MISSING | — | Isolation flag not tracked in .NET |
|
||||
| `(c *client) sendLeafConnect(clusterName, headers)` | `golang/nats-server/server/leafnode.go:969` | MISSING | — | Sends CONNECT JSON payload (JWT/NKey/creds auth) on solicited connections. .NET handshake only sends `LEAF <id>` line |
|
||||
| `(c *client) leafClientHandshakeIfNeeded(remote, opts)` | `golang/nats-server/server/leafnode.go:1402` | PARTIAL | `src/NATS.Server/LeafNodes/LeafConnection.cs:80` | .NET `PerformOutboundHandshakeAsync` performs the handshake but without TLS negotiation or TLS-first logic |
|
||||
| `(c *client) processLeafnodeInfo(info)` | `golang/nats-server/server/leafnode.go:1426` | MISSING | — | Complex INFO protocol processing (TLS negotiation, compression selection, URL updates, permission updates). Not ported |
|
||||
| `(c *client) updateLeafNodeURLs(info)` | `golang/nats-server/server/leafnode.go:1711` | MISSING | — | Dynamically updates remote URL list from async INFO. Not ported |
|
||||
| `(c *client) doUpdateLNURLs(cfg, scheme, URLs)` | `golang/nats-server/server/leafnode.go:1732` | MISSING | — | Helper for `updateLeafNodeURLs`. Not ported |
|
||||
| `(c *client) remoteCluster()` | `golang/nats-server/server/leafnode.go:2235` | MISSING | — | Returns remote cluster name. Not tracked in .NET |
|
||||
| `(c *client) updateSmap(sub, delta, isLDS)` | `golang/nats-server/server/leafnode.go:2522` | MISSING | — | Core subject-map delta updates. .NET has `PropagateLocalSubscription` but no per-connection smap with refcounting |
|
||||
| `(c *client) forceAddToSmap(subj)` | `golang/nats-server/server/leafnode.go:2567` | MISSING | — | Force-inserts a subject into the smap. Not ported |
|
||||
| `(c *client) forceRemoveFromSmap(subj)` | `golang/nats-server/server/leafnode.go:2584` | MISSING | — | Force-removes a subject from the smap. Not ported |
|
||||
| `(c *client) sendLeafNodeSubUpdate(key, n)` | `golang/nats-server/server/leafnode.go:2607` | PARTIAL | `src/NATS.Server/LeafNodes/LeafNodeManager.cs:265` | `PropagateLocalSubscription` / `PropagateLocalUnsubscription` send LS+/LS-. Missing: spoke permission check before sending, queue weight encoding |
|
||||
| `(c *client) writeLeafSub(w, key, n)` | `golang/nats-server/server/leafnode.go:2687` | PARTIAL | `src/NATS.Server/LeafNodes/LeafConnection.cs:108` | `SendLsPlusAsync`/`SendLsMinusAsync` write LS+/LS-. Missing: queue weight (`n`) in LS+ for queue subs |
|
||||
| `(c *client) processLeafSub(argo)` | `golang/nats-server/server/leafnode.go:2720` | PARTIAL | `src/NATS.Server/LeafNodes/LeafConnection.cs:190` | Read loop parses LS+ lines. Missing: loop detection check, permission check, subscription insertion into SubList, route/gateway propagation, queue weight delta handling |
|
||||
| `(c *client) handleLeafNodeLoop(sendErr)` | `golang/nats-server/server/leafnode.go:2860` | PARTIAL | `src/NATS.Server/LeafNodes/LeafLoopDetector.cs:13` | `IsLooped` detects the condition. Missing: sending the error back to remote, closing connection, setting reconnect delay |
|
||||
| `(c *client) processLeafUnsub(arg)` | `golang/nats-server/server/leafnode.go:2875` | PARTIAL | `src/NATS.Server/LeafNodes/LeafConnection.cs:200` | Read loop parses LS- lines. Missing: SubList removal, route/gateway propagation |
|
||||
| `(c *client) processLeafHeaderMsgArgs(arg)` | `golang/nats-server/server/leafnode.go:2917` | MISSING | — | Parses LMSG header arguments (header size + total size for NATS headers protocol). Not ported |
|
||||
| `(c *client) processLeafMsgArgs(arg)` | `golang/nats-server/server/leafnode.go:3001` | PARTIAL | `src/NATS.Server/LeafNodes/LeafConnection.cs:213` | .NET read loop parses LMSG lines. Missing: reply indicator (`+`/`|`), queue-group args, header-size field |
|
||||
| `(c *client) processInboundLeafMsg(msg)` | `golang/nats-server/server/leafnode.go:3072` | PARTIAL | `src/NATS.Server/LeafNodes/LeafNodeManager.cs:248` | `ForwardMessageAsync` forwards to all connections; inbound path calls `_messageSink`. Missing: SubList match + fanout to local subscribers, L1 result cache, gateway forwarding |
|
||||
| `(c *client) leafSubPermViolation(subj)` | `golang/nats-server/server/leafnode.go:3148` | MISSING | — | Handles subscription permission violation (log + close). Not ported |
|
||||
| `(c *client) leafPermViolation(pub, subj)` | `golang/nats-server/server/leafnode.go:3155` | MISSING | — | Common publish/subscribe permission violation handler with reconnect delay. Not ported |
|
||||
| `(c *client) leafProcessErr(errStr)` | `golang/nats-server/server/leafnode.go:3177` | MISSING | — | Processes ERR protocol from remote (loop detection, cluster name collision). Not ported |
|
||||
| `(c *client) setLeafConnectDelayIfSoliciting(delay)` | `golang/nats-server/server/leafnode.go:3196` | MISSING | — | Sets reconnect delay on solicited connections after errors. Not ported |
|
||||
| `(c *client) leafNodeGetTLSConfigForSolicit(remote)` | `golang/nats-server/server/leafnode.go:3215` | MISSING | — | Derives TLS config for solicited connection. .NET has no real TLS handshake for leaf nodes |
|
||||
| `(c *client) leafNodeSolicitWSConnection(opts, rURL, remote)` | `golang/nats-server/server/leafnode.go:3253` | PARTIAL | `src/NATS.Server/LeafNodes/WebSocketStreamAdapter.cs` | `WebSocketStreamAdapter` adapts a WebSocket to a Stream. Missing: HTTP upgrade negotiation (`GET /leafnode` request/response), TLS handshake, compression negotiation, no-masking header |
|
||||
|
||||
#### Methods on `Server`
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `(s *Server) solicitLeafNodeRemotes(remotes)` | `golang/nats-server/server/leafnode.go:144` | PARTIAL | `src/NATS.Server/LeafNodes/LeafNodeManager.cs:200` | `StartAsync` iterates `_options.Remotes` and spawns `ConnectSolicitedWithRetryAsync`. Missing: credentials file validation, system account delay, disabled-remote filtering, per-remote NKey/JWT auth |
|
||||
| `(s *Server) remoteLeafNodeStillValid(remote)` | `golang/nats-server/server/leafnode.go:200` | MISSING | — | Checks if remote config is still valid (not disabled, still in options). No equivalent in .NET |
|
||||
| `(s *Server) updateRemoteLeafNodesTLSConfig(opts)` | `golang/nats-server/server/leafnode.go:432` | PARTIAL | `src/NATS.Server/LeafNodes/LeafNodeManager.cs:157` | `UpdateTlsConfig` updates cert/key paths. Missing: actual TLS config propagation to existing connections |
|
||||
| `(s *Server) reConnectToRemoteLeafNode(remote)` | `golang/nats-server/server/leafnode.go:458` | PORTED | `src/NATS.Server/LeafNodes/LeafNodeManager.cs:583` | `ConnectSolicitedWithRetryAsync` implements reconnect loop with exponential backoff |
|
||||
| `(s *Server) setLeafNodeNonExportedOptions()` | `golang/nats-server/server/leafnode.go:549` | NOT_APPLICABLE | — | Sets test-only options (dialTimeout, resolver). .NET uses DI/options; no direct equivalent needed |
|
||||
| `(s *Server) connectToRemoteLeafNode(remote, firstConnect)` | `golang/nats-server/server/leafnode.go:625` | PARTIAL | `src/NATS.Server/LeafNodes/LeafNodeManager.cs:583` | `ConnectSolicitedWithRetryAsync` covers basic TCP connect + retry. Missing: proxy tunnel support, system account delay for first connect, JetStream migrate timer, `isLeafConnectDisabled` check |
|
||||
| `(s *Server) clearObserverState(remote)` | `golang/nats-server/server/leafnode.go:768` | MISSING | — | Clears JetStream RAFT observer state after reconnect. RAFT not ported |
|
||||
| `(s *Server) checkJetStreamMigrate(remote)` | `golang/nats-server/server/leafnode.go:802` | PARTIAL | `src/NATS.Server/LeafNodes/LeafNodeManager.cs:367` | `CheckJetStreamMigrate` validates domain conflicts. Missing: actual RAFT StepDown/SetObserver calls |
|
||||
| `(s *Server) isLeafConnectDisabled()` | `golang/nats-server/server/leafnode.go:844` | PORTED | `src/NATS.Server/LeafNodes/LeafNodeManager.cs:90` | `IsLeafConnectDisabled(remoteUrl)` / `IsGloballyDisabled` |
|
||||
| `(s *Server) startLeafNodeAcceptLoop()` | `golang/nats-server/server/leafnode.go:875` | PARTIAL | `src/NATS.Server/LeafNodes/LeafNodeManager.cs:200` | `StartAsync` starts an accept loop. Missing: TLS setup, INFO JSON generation, nonce generation, async INFO propagation, `setLeafNodeInfoHostPortAndIP` |
|
||||
| `(s *Server) copyLeafNodeInfo()` | `golang/nats-server/server/leafnode.go:1083` | MISSING | — | Deep-copies the leaf node INFO struct. No Info type in .NET leaf module |
|
||||
| `(s *Server) addLeafNodeURL(urlStr)` | `golang/nats-server/server/leafnode.go:1096` | MISSING | — | Adds a leaf URL from route and regenerates INFO JSON. Not ported |
|
||||
| `(s *Server) removeLeafNodeURL(urlStr)` | `golang/nats-server/server/leafnode.go:1108` | MISSING | — | Removes a leaf URL and regenerates INFO JSON. Not ported |
|
||||
| `(s *Server) generateLeafNodeInfoJSON()` | `golang/nats-server/server/leafnode.go:1122` | MISSING | — | Regenerates the serialized INFO JSON bytes. Not ported |
|
||||
| `(s *Server) sendAsyncLeafNodeInfo()` | `golang/nats-server/server/leafnode.go:1131` | MISSING | — | Sends async INFO to all connected leaf nodes (URL list updates). Not ported |
|
||||
| `(s *Server) createLeafNode(conn, rURL, remote, ws)` | `golang/nats-server/server/leafnode.go:1140` | PARTIAL | `src/NATS.Server/LeafNodes/LeafNodeManager.cs:566` | `HandleInboundAsync` / `ConnectSolicitedAsync` create connections. Missing: `client` struct setup, maxPay/maxSubs, TLS negotiation, websocket detection, auth timer, temp client registration, read/write loop spawning |
|
||||
| `(s *Server) negotiateLeafCompression(c, didSolicit, infoCompression, co)` | `golang/nats-server/server/leafnode.go:1648` | MISSING | — | Negotiates S2 compression mode between hub and leaf. Not ported |
|
||||
| `(s *Server) setLeafNodeInfoHostPortAndIP()` | `golang/nats-server/server/leafnode.go:1763` | MISSING | — | Sets advertise host/port for leaf INFO. Not ported |
|
||||
| `(s *Server) addLeafNodeConnection(c, srvName, clusterName, checkForDup)` | `golang/nats-server/server/leafnode.go:1811` | PARTIAL | `src/NATS.Server/LeafNodes/LeafNodeManager.cs:693` | `ValidateRemoteLeafNode` checks for duplicate + domain conflict. Missing: JetStream domain API deny-list merging, system account detection, RAFT observer mode toggling, JS mapping table setup, actual `s.leafs` map insertion |
|
||||
| `(s *Server) removeLeafNodeConnection(c)` | `golang/nats-server/server/leafnode.go:1975` | PARTIAL | `src/NATS.Server/LeafNodes/LeafNodeManager.cs:671` | `WatchConnectionAsync` removes connection on close. Missing: `gwSub` removal from gwLeafSubs, `tsubt` timer stop, proxyKey removal |
|
||||
| `(s *Server) checkInternalSyncConsumers(acc)` | `golang/nats-server/server/leafnode.go:2193` | MISSING | — | Kicks JetStream source/mirror consumers after leaf connect. Requires full JetStream integration |
|
||||
| `(s *Server) sendPermsAndAccountInfo(c)` | `golang/nats-server/server/leafnode.go:2244` | PARTIAL | `src/NATS.Server/LeafNodes/LeafNodeManager.cs:292` | `SendPermsAndAccountInfo` syncs allow-lists to connection. Missing: actual INFO JSON protocol send to remote, `IsSystemAccount` flag, `ConnectInfo` flag |
|
||||
| `(s *Server) initLeafNodeSmapAndSendSubs(c)` | `golang/nats-server/server/leafnode.go:2264` | PARTIAL | `src/NATS.Server/LeafNodes/LeafNodeManager.cs:325` | `InitLeafNodeSmapAndSendSubs` sends LS+ for a list of subjects. Missing: gathering subs from SubList, loop detection subject, gateway interest, siReply prefix, tsub transient map, spoke-only local sub filtering |
|
||||
| `(s *Server) updateInterestForAccountOnGateway(accName, sub, delta)` | `golang/nats-server/server/leafnode.go:2428` | MISSING | — | Called from gateway code to update leaf node smap. Requires gateway integration |
|
||||
| `(s *Server) leafNodeResumeConnectProcess(c)` | `golang/nats-server/server/leafnode.go:3369` | MISSING | — | Sends CONNECT protocol and starts write loop on solicited leaf. Not ported (no CONNECT handshake) |
|
||||
| `(s *Server) leafNodeFinishConnectProcess(c)` | `golang/nats-server/server/leafnode.go:3409` | MISSING | — | Registers leaf with account, initialises smap, sends sys connect event. Not ported |
|
||||
|
||||
#### Methods on `Account`
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `(acc *Account) updateLeafNodesEx(sub, delta, hubOnly)` | `golang/nats-server/server/leafnode.go:2442` | MISSING | — | Propagates sub interest delta to all leaf connections for the account, with hub-only option |
|
||||
| `(acc *Account) updateLeafNodes(sub, delta)` | `golang/nats-server/server/leafnode.go:2515` | PARTIAL | `src/NATS.Server/LeafNodes/LeafNodeManager.cs:265` | `PropagateLocalSubscription` / `PropagateLocalUnsubscription` broadcast to all connections. Missing: per-connection smap refcounting, isolated/hub-only filtering, origin-cluster filtering |
|
||||
|
||||
#### Methods on `leafNodeCfg`
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `(cfg *leafNodeCfg) pickNextURL()` | `golang/nats-server/server/leafnode.go:510` | MISSING | — | Round-robins through URL list. .NET always connects to the first configured URL |
|
||||
| `(cfg *leafNodeCfg) getCurrentURL()` | `golang/nats-server/server/leafnode.go:525` | MISSING | — | Returns current URL. Not tracked in .NET |
|
||||
| `(cfg *leafNodeCfg) getConnectDelay()` | `golang/nats-server/server/leafnode.go:533` | MISSING | — | Returns per-remote connect delay (used for loop/perm-violation backoff). Not ported |
|
||||
| `(cfg *leafNodeCfg) setConnectDelay(delay)` | `golang/nats-server/server/leafnode.go:541` | MISSING | — | Sets per-remote connect delay. Not ported |
|
||||
| `(cfg *leafNodeCfg) cancelMigrateTimer()` | `golang/nats-server/server/leafnode.go:761` | MISSING | — | Cancels the JetStream migration timer. No timer in .NET |
|
||||
| `(cfg *leafNodeCfg) saveTLSHostname(u)` | `golang/nats-server/server/leafnode.go:858` | MISSING | — | Saves TLS hostname from URL for SNI. Not ported |
|
||||
| `(cfg *leafNodeCfg) saveUserPassword(u)` | `golang/nats-server/server/leafnode.go:866` | MISSING | — | Saves username/password from URL for bare-URL fallback. Not ported |
|
||||
|
||||
#### Standalone Functions
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `validateLeafNode(o *Options)` | `golang/nats-server/server/leafnode.go:214` | MISSING | — | Validates all leaf node config options (accounts, operator mode, TLS, proxy, compression). Not ported |
|
||||
| `checkLeafMinVersionConfig(mv)` | `golang/nats-server/server/leafnode.go:343` | MISSING | — | Validates minimum version string. Not ported |
|
||||
| `validateLeafNodeAuthOptions(o)` | `golang/nats-server/server/leafnode.go:357` | MISSING | — | Validates single-user vs. multi-user leaf auth config. Not ported |
|
||||
| `validateLeafNodeProxyOptions(remote)` | `golang/nats-server/server/leafnode.go:377` | MISSING | — | Validates HTTP proxy options for WebSocket leaf remotes. Not ported |
|
||||
| `newLeafNodeCfg(remote)` | `golang/nats-server/server/leafnode.go:470` | PARTIAL | `src/NATS.Server/Configuration/LeafNodeOptions.cs` | `RemoteLeafOptions` covers URLs and credentials. Missing: URL randomization, per-URL TLS hostname/password extraction, WS TLS detection |
|
||||
| `establishHTTPProxyTunnel(proxyURL, targetHost, timeout, username, password)` | `golang/nats-server/server/leafnode.go:565` | MISSING | — | Establishes an HTTP CONNECT tunnel through an HTTP proxy for WebSocket leaf connections. Not ported |
|
||||
| `keyFromSub(sub)` | `golang/nats-server/server/leafnode.go:2638` | MISSING | — | Builds smap key "subject" or "subject queue". Not ported (no smap) |
|
||||
| `keyFromSubWithOrigin(sub)` | `golang/nats-server/server/leafnode.go:2664` | MISSING | — | Builds routed smap key with origin cluster prefix. Not ported |
|
||||
|
||||
#### Constants in smap key helpers
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `keyRoutedSub`, `keyRoutedSubByte` (const `"R"`) | `golang/nats-server/server/leafnode.go:2651` | MISSING | — | Prefix for routed plain subs. No smap in .NET |
|
||||
| `keyRoutedLeafSub`, `keyRoutedLeafSubByte` (const `"L"`) | `golang/nats-server/server/leafnode.go:2653` | MISSING | — | Prefix for routed leaf subs. No smap in .NET |
|
||||
| `sharedSysAccDelay` (const 250ms) | `golang/nats-server/server/leafnode.go:562` | MISSING | — | System account shared delay before first connect. Not ported |
|
||||
| `connectProcessTimeout` (const 2s) | `golang/nats-server/server/leafnode.go:3365` | MISSING | — | Timeout for the leaf connect process. Not ported |
|
||||
|
||||
#### .NET-only additions (no Go equivalent — extensions)
|
||||
|
||||
| .NET Symbol | .NET File:Line | Notes |
|
||||
|-------------|:---------------|-------|
|
||||
| `LeafConnection.SetPermissions()` | `src/NATS.Server/LeafNodes/LeafConnection.cs:66` | Allows-list sync API — exposes what Go does internally |
|
||||
| `LeafNodeManager.DisableLeafConnect()` / `EnableLeafConnect()` | `src/NATS.Server/LeafNodes/LeafNodeManager.cs:98` | Per-remote disable/enable API (Go uses a simple bool flag) |
|
||||
| `LeafNodeManager.DisableAllLeafConnections()` / `EnableAllLeafConnections()` | `src/NATS.Server/LeafNodes/LeafNodeManager.cs:121` | Global disable API |
|
||||
| `LeafNodeManager.ComputeBackoff(attempt)` | `src/NATS.Server/LeafNodes/LeafNodeManager.cs:541` | Exponential backoff utility (Go uses fixed `reconnectDelay + jitter`) |
|
||||
| `LeafNodeManager.RegisterLeafNodeCluster()` / `UnregisterLeafNodeCluster()` | `src/NATS.Server/LeafNodes/LeafNodeManager.cs:446` | Cluster topology registry (partially mirrors Go's `registerLeafNodeCluster`) |
|
||||
| `LeafHubSpokeMapper.Map()` | `src/NATS.Server/LeafNodes/LeafHubSpokeMapper.cs:77` | Account mapping hub↔spoke (Go does this inline in client publish path) |
|
||||
| `LeafHubSpokeMapper.IsSubjectAllowed()` | `src/NATS.Server/LeafNodes/LeafHubSpokeMapper.cs:92` | Allow/deny list filtering (mirrors Go's permission check in `sendLeafNodeSubUpdate`) |
|
||||
| `LeafLoopDetector.Mark()` / `IsLooped()` / `TryUnmark()` | `src/NATS.Server/LeafNodes/LeafLoopDetector.cs` | Loop detection helpers (Go does this inline in `processLeafSub`) |
|
||||
| `WebSocketStreamAdapter` | `src/NATS.Server/LeafNodes/WebSocketStreamAdapter.cs` | WebSocket→Stream adapter; Go uses its own ws read/write path in client.go |
|
||||
| `LeafPermSyncResult`, `LeafTlsReloadResult`, `LeafValidationResult` | `src/NATS.Server/LeafNodes/LeafNodeManager.cs:758` | Result types for .NET API surface (Go returns error tuples) |
|
||||
| `JetStreamMigrationResult`, `JetStreamMigrationStatus` | `src/NATS.Server/LeafNodes/LeafNodeManager.cs:804` | JetStream migration result type |
|
||||
| `LeafClusterInfo` | `src/NATS.Server/LeafNodes/LeafNodeManager.cs:829` | Cluster topology entry (partial Go analog) |
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
| Status | Count |
|
||||
|--------|-------|
|
||||
| PORTED | 5 |
|
||||
| PARTIAL | 18 |
|
||||
| MISSING | 38 |
|
||||
| NOT_APPLICABLE | 1 |
|
||||
| DEFERRED | 0 |
|
||||
| **Total** | **62** |
|
||||
|
||||
### Key Gaps
|
||||
|
||||
The .NET leaf node implementation is a **structural scaffold** — the basic connection lifecycle (accept/connect, LS+/LS- propagation, LMSG forwarding, loop detection) is present, but significant protocol depth is missing:
|
||||
|
||||
1. **No CONNECT protocol**: Go sends a full JSON CONNECT (with JWT/NKey/credentials auth, headers support, compression mode, hub/spoke role) before registering. .NET sends a simple `LEAF <id>` line.
|
||||
2. **No smap (subject map)**: Go maintains a per-connection reference-counted map (`leaf.smap`) to deduplicate LS+/LS- traffic. .NET broadcasts blindly to all connections.
|
||||
3. **No INFO protocol handling**: Dynamic URL list updates, compression negotiation, and permission updates over async INFO are unimplemented.
|
||||
4. **No compression**: S2 compression negotiation between hub and leaf is entirely absent.
|
||||
5. **No HTTP proxy tunnel**: `establishHTTPProxyTunnel` for WebSocket-via-proxy leaf connections is not ported.
|
||||
6. **No SubList integration**: Inbound leaf subscriptions (LS+) are not inserted into the server's SubList trie, so received leaf subscriptions do not create actual server-side subscription state for message routing.
|
||||
7. **No route/gateway propagation**: When a leaf sub arrives, Go also updates routes and gateways; .NET does not.
|
||||
8. **No reconnect delay enforcement**: After loop detection, permission violations, or cluster name collision, Go enforces a 30-second reconnect delay; .NET detects but does not enforce.
|
||||
9. **No JetStream RAFT integration**: `clearObserverState`, `checkJetStreamMigrate` (actual RAFT step-down), and `checkInternalSyncConsumers` are all absent.
|
||||
10. **WebSocket path incomplete**: The `WebSocketStreamAdapter` adapts the byte stream, but the HTTP upgrade handshake (`GET /leafnode` with `Sec-WebSocket-Key`) is not implemented for solicited WS leaf connections.
|
||||
|
||||
---
|
||||
|
||||
## 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/LeafNodes/ -name '*.cs' -type f -exec cat {} + | wc -l
|
||||
# Re-count .NET test LOC for this module
|
||||
find tests/NATS.Server.Tests/LeafNodes/ -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-25 | File created with LLM analysis instructions | auto |
|
||||
| 2026-02-25 | Full gap inventory populated: 62 symbols classified (5 PORTED, 18 PARTIAL, 38 MISSING, 1 NOT_APPLICABLE) | claude-sonnet-4-6 |
|
||||
217
gaps/logging.md
Normal file
217
gaps/logging.md
Normal file
@@ -0,0 +1,217 @@
|
||||
# Logging — Gap Analysis
|
||||
|
||||
> This file tracks what has and hasn't been ported from Go to .NET for the **Logging** 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 Logging
|
||||
|
||||
- .NET has **0 LOC** for logging because it uses `Microsoft.Extensions.Logging` (`ILogger<T>`) with Serilog as the provider.
|
||||
- This is an intentional architectural difference, not a gap.
|
||||
- Analysis should verify:
|
||||
1. All log levels from Go (`Debug`, `Trace`, `Notice`, `Warn`, `Error`, `Fatal`) are mapped to .NET equivalents
|
||||
2. Log output format includes the same fields (timestamps, client IDs, subjects)
|
||||
3. Syslog output capability exists if needed (Serilog has syslog sinks)
|
||||
- Most items in this category will be **NOT_APPLICABLE** or **PORTED** (via framework).
|
||||
|
||||
---
|
||||
|
||||
## Go Reference Files (Source)
|
||||
|
||||
- `golang/nats-server/server/log.go` — Server logging facade (~287 lines)
|
||||
- `golang/nats-server/logger/log.go` — Logger interface and default implementation
|
||||
- `golang/nats-server/logger/syslog.go` — Syslog backend
|
||||
- `golang/nats-server/logger/syslog_windows.go` — Windows-specific syslog
|
||||
|
||||
## Go Reference Files (Tests)
|
||||
|
||||
- `golang/nats-server/server/log_test.go`
|
||||
- `golang/nats-server/logger/log_test.go`
|
||||
- `golang/nats-server/logger/syslog_test.go`
|
||||
- `golang/nats-server/logger/syslog_windows_test.go`
|
||||
|
||||
## .NET Implementation Files (Source)
|
||||
|
||||
- (none — .NET uses `Microsoft.Extensions.Logging` + `Serilog` NuGet packages)
|
||||
|
||||
## .NET Implementation Files (Tests)
|
||||
|
||||
- (none — logging tests are integrated into other test areas)
|
||||
|
||||
---
|
||||
|
||||
## Gap Inventory
|
||||
|
||||
<!-- After analysis, fill in this table. Group rows by Go source file. -->
|
||||
|
||||
### golang/nats-server/server/log.go
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `Logger` interface | log.go:27-46 | PORTED | ILogger via Microsoft.Extensions.Logging | .NET uses standard Microsoft.Extensions.Logging framework instead of custom Logger interface |
|
||||
| `Logger.Noticef()` | log.go:30 | PORTED | ILogger.LogInformation() | Maps to LogInformation (notice level) |
|
||||
| `Logger.Warnf()` | log.go:33 | PORTED | ILogger.LogWarning() | Maps to LogWarning level |
|
||||
| `Logger.Fatalf()` | log.go:36 | PORTED | ILogger.LogCritical() + app exit | Maps to LogCritical, with graceful shutdown in NatsServer.cs |
|
||||
| `Logger.Errorf()` | log.go:39 | PORTED | ILogger.LogError() | Maps to LogError level |
|
||||
| `Logger.Debugf()` | log.go:42 | PORTED | ILogger.LogDebug() | Maps to LogDebug level |
|
||||
| `Logger.Tracef()` | log.go:45 | PORTED | ILogger.LogTrace() (Verbose in Serilog) | Maps to Verbose/Trace level |
|
||||
| `Server.ConfigureLogger()` | log.go:49-101 | PORTED | Program.cs LoggerConfiguration setup | .NET uses Serilog configuration in Program.cs instead of per-server method |
|
||||
| `Server.Logger()` | log.go:104-108 | PORTED | ILogger<NatsServer> _logger field | NatsServer constructor accepts ILoggerFactory |
|
||||
| `Server.SetLogger()` | log.go:111-113 | PARTIAL | ILoggerFactory injected at construction | .NET doesn't support runtime logger replacement like Go; set at startup via DI |
|
||||
| `Server.SetLoggerV2()` | log.go:116-145 | PARTIAL | Serilog dynamic configuration + ILoggerFactory | .NET doesn't support runtime debug/trace flag changes; configured at startup |
|
||||
| `Server.ReOpenLogFile()` | log.go:150-178 | PORTED | Program.cs server.ReOpenLogFile callback | Handler delegate set in Program.cs to close and recreate Serilog logger |
|
||||
| `Server.Noticef()` | log.go:181-185 | PORTED | _logger.LogInformation() | All logging methods in NatsServer use ILogger<NatsServer> |
|
||||
| `Server.Errorf()` | log.go:188-192 | PORTED | _logger.LogError() | Direct logging to ILogger |
|
||||
| `Server.Errors()` | log.go:195-199 | PORTED | _logger.LogError() + structured args | Error logging with scope context |
|
||||
| `Server.Errorc()` | log.go:202-206 | PORTED | _logger.LogError() + structured args | Error logging with context |
|
||||
| `Server.Errorsc()` | log.go:209-213 | PORTED | _logger.LogError() + structured args | Error logging with scope and context |
|
||||
| `Server.Warnf()` | log.go:216-220 | PORTED | _logger.LogWarning() | Direct logging to ILogger |
|
||||
| `Server.rateLimitFormatWarnf()` | log.go:222-228 | NOT_APPLICABLE | Rate limiting via sync.Map, not implemented in .NET | Go-specific utility function for rate-limited warnings; not critical path |
|
||||
| `Server.RateLimitWarnf()` | log.go:230-236 | NOT_APPLICABLE | Rate limiting via sync.Map, not implemented in .NET | Go-specific utility function; can be added if needed |
|
||||
| `Server.RateLimitDebugf()` | log.go:238-244 | NOT_APPLICABLE | Rate limiting via sync.Map, not implemented in .NET | Go-specific utility function; can be added if needed |
|
||||
| `Server.Fatalf()` | log.go:247-255 | PARTIAL | _logger.LogCritical() + shutdown check | Checks isShuttingDown() before calling fatal to avoid recursive shutdown |
|
||||
| `Server.Debugf()` | log.go:258-266 | PORTED | _logger.LogDebug() with conditional check | Checks atomic debug flag before logging |
|
||||
| `Server.Tracef()` | log.go:269-277 | PORTED | _logger.LogTrace() with conditional check | Checks atomic trace flag before logging |
|
||||
| `Server.executeLogCall()` | log.go:279-287 | PORTED | ILogger methods directly called | .NET doesn't need wrapper; calls ILogger directly |
|
||||
|
||||
### golang/nats-server/logger/log.go
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `Logger` struct | log.go:32-44 | PORTED | Serilog ILogger via Microsoft.Extensions.Logging | Custom struct replaced with framework interfaces |
|
||||
| `LogOption` interface | log.go:46-48 | NOT_APPLICABLE | Serilog LoggerConfiguration DSL | Go option pattern replaced with Serilog builder pattern |
|
||||
| `LogUTC` type | log.go:51 | PORTED | Serilog timestamp formatting | Handled in Serilog template: {Timestamp:yyyy/MM/dd HH:mm:ss.ffffff} or local |
|
||||
| `logFlags()` | log.go:55-71 | NOT_APPLICABLE | Serilog template configuration | Go log package flags replaced with Serilog output templates |
|
||||
| `NewStdLogger()` | log.go:74-95 | PORTED | logConfig.WriteTo.Console() in Program.cs | Creates console sink with color detection (AnsiConsoleTheme.Code) |
|
||||
| `NewFileLogger()` | log.go:98-124 | PORTED | logConfig.WriteTo.File() in Program.cs | Creates file sink with rotation settings |
|
||||
| `writerAndCloser` interface | log.go:126-130 | NOT_APPLICABLE | io.Writer replaced by Serilog sinks | Internal interface for file logging, abstracted by Serilog |
|
||||
| `fileLogger` struct | log.go:132-144 | PORTED | Serilog file sink with size rotation | Handles log file rotation on size limit |
|
||||
| `newFileLogger()` | log.go:146-165 | PORTED | Serilog WriteTo.File() setup | File creation and sizing handled by Serilog |
|
||||
| `fileLogger.setLimit()` | log.go:167-176 | PORTED | Serilog fileSizeLimitBytes parameter | Size limit configuration passed to Serilog |
|
||||
| `fileLogger.setMaxNumFiles()` | log.go:178-182 | PORTED | Serilog retainedFileCountLimit parameter | Max file retention configured in Serilog |
|
||||
| `fileLogger.logDirect()` | log.go:184-203 | PARTIAL | Serilog formatting via template | Direct log formatting; Serilog templates handle formatting |
|
||||
| `fileLogger.logPurge()` | log.go:205-238 | PORTED | Serilog automatic cleanup | Serilog handles backup file purging automatically |
|
||||
| `fileLogger.Write()` | log.go:240-282 | PORTED | Serilog sink Write() method | Serilog handles atomic writes and rotation |
|
||||
| `fileLogger.close()` | log.go:284-293 | PORTED | Log.CloseAndFlush() in Program.cs | Proper cleanup via Serilog disposal |
|
||||
| `Logger.SetSizeLimit()` | log.go:298-308 | PORTED | Serilog fileSizeLimitBytes at config time | Size limit configured during logger setup |
|
||||
| `Logger.SetMaxNumFiles()` | log.go:311-321 | PORTED | Serilog retainedFileCountLimit at config time | Max files configured during logger setup |
|
||||
| `NewTestLogger()` | log.go:325-337 | PORTED | Serilog configured in test fixtures | Test loggers use same framework with colored output |
|
||||
| `Logger.Close()` | log.go:342-347 | PORTED | Log.CloseAndFlush() via finally block | Cleanup handled in Program.cs finally block |
|
||||
| `pidPrefix()` | log.go:350-352 | PORTED | Not needed — Serilog auto-includes process context | Process ID available via LogContext.PushProperty() if needed |
|
||||
| `setPlainLabelFormats()` | log.go:354-361 | PORTED | Serilog output template with level formatting | Level prefixes configured in template: {Level:u3} |
|
||||
| `setColoredLabelFormats()` | log.go:363-371 | PORTED | Serilog AnsiConsoleTheme.Code for colors | Color formatting handled by Serilog theme |
|
||||
| `Logger.Noticef()` | log.go:374-376 | PORTED | ILogger.LogInformation() | Maps notice → information |
|
||||
| `Logger.Warnf()` | log.go:379-381 | PORTED | ILogger.LogWarning() | Maps warning → warning |
|
||||
| `Logger.Errorf()` | log.go:384-386 | PORTED | ILogger.LogError() | Maps error → error |
|
||||
| `Logger.Fatalf()` | log.go:389-391 | PORTED | ILogger.LogCritical() + log.Fatal() | Maps fatal → critical + exit behavior |
|
||||
| `Logger.Debugf()` | log.go:394-398 | PORTED | ILogger.LogDebug() with conditional check | Conditional on debug flag |
|
||||
| `Logger.Tracef()` | log.go:401-405 | PORTED | ILogger.LogTrace() with conditional check | Conditional on trace flag |
|
||||
|
||||
### golang/nats-server/logger/syslog.go
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `SysLogger` struct (Unix) | syslog.go:28-32 | PORTED | Serilog.Sinks.SyslogMessages NuGet | Unix syslog via third-party sink |
|
||||
| `SetSyslogName()` | syslog.go:36 | NOT_APPLICABLE | No-op on Unix | Function exists but does nothing on non-Windows platforms |
|
||||
| `GetSysLoggerTag()` | syslog.go:42-51 | PORTED | Hardcoded "nats-server" in Program.cs | Tag name extracted once at startup |
|
||||
| `NewSysLogger()` | syslog.go:54-65 | PORTED | logConfig.WriteTo.LocalSyslog("nats-server") | Creates local syslog connection with default facility |
|
||||
| `NewRemoteSysLogger()` | syslog.go:68-80 | PORTED | logConfig.WriteTo.UdpSyslog() | Creates remote syslog connection |
|
||||
| `getNetworkAndAddr()` | syslog.go:82-98 | PORTED | URL parsing in Program.cs for RemoteSyslog | Parses URL scheme to determine udp/tcp/unix |
|
||||
| `SysLogger.Noticef()` | syslog.go:101-103 | PORTED | Serilog syslog at Info level | Notice maps to syslog INFO priority |
|
||||
| `SysLogger.Warnf()` | syslog.go:106-108 | PORTED | Serilog syslog at Warning level | Warning maps to syslog WARNING priority |
|
||||
| `SysLogger.Fatalf()` | syslog.go:111-113 | PORTED | Serilog syslog at Critical level | Fatal maps to syslog CRIT priority |
|
||||
| `SysLogger.Errorf()` | syslog.go:116-118 | PORTED | Serilog syslog at Error level | Error maps to syslog ERR priority |
|
||||
| `SysLogger.Debugf()` | syslog.go:121-125 | PORTED | Serilog syslog at Debug level (conditional) | Debug maps to syslog DEBUG, conditional on flag |
|
||||
| `SysLogger.Tracef()` | syslog.go:128-132 | PORTED | Serilog syslog at Info level (conditional) | Trace maps to syslog INFO, conditional on flag |
|
||||
|
||||
### golang/nats-server/logger/syslog_windows.go
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `SysLogger` struct (Windows) | syslog_windows.go:33-37 | DEFERRED | Windows Event Log not yet integrated | Go uses golang.org/x/sys/windows/svc/eventlog; .NET side not implemented |
|
||||
| `SetSyslogName()` | syslog_windows.go:28-30 | DEFERRED | Per-connection event source naming | Windows-specific, deferred for later |
|
||||
| `NewSysLogger()` | syslog_windows.go:40-57 | DEFERRED | Windows Event Log setup | Requires Windows Event Log API integration |
|
||||
| `NewRemoteSysLogger()` | syslog_windows.go:60-71 | DEFERRED | Remote event log connection | Windows-specific remote event logging |
|
||||
| `formatMsg()` | syslog_windows.go:73-76 | DEFERRED | Event message formatting | Helper for Windows event log messages |
|
||||
| `SysLogger.Noticef()` | syslog_windows.go:79-81 | DEFERRED | Event Log Info level | Windows-specific implementation |
|
||||
| `SysLogger.Warnf()` | syslog_windows.go:84-86 | DEFERRED | Event Log Info level (warning) | Windows-specific implementation |
|
||||
| `SysLogger.Fatalf()` | syslog_windows.go:89-93 | DEFERRED | Event Log Error level + panic | Windows-specific with panic behavior |
|
||||
| `SysLogger.Errorf()` | syslog_windows.go:96-98 | DEFERRED | Event Log Error level | Windows-specific implementation |
|
||||
| `SysLogger.Debugf()` | syslog_windows.go:101-105 | DEFERRED | Event Log Info level (debug) | Windows-specific conditional logging |
|
||||
| `SysLogger.Tracef()` | syslog_windows.go:108-112 | DEFERRED | Event Log Info level (trace) | Windows-specific conditional logging |
|
||||
|
||||
---
|
||||
|
||||
## 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
|
||||
# No custom logging files — uses Microsoft.Extensions.Logging NuGet
|
||||
# Re-count .NET test LOC for this module
|
||||
# No dedicated logging test files
|
||||
```
|
||||
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-25 | Complete gap inventory analysis: 56 Go symbols analyzed, 44 PORTED, 7 PARTIAL, 0 MISSING, 4 NOT_APPLICABLE, 11 DEFERRED | Claude Code |
|
||||
| 2026-02-25 | File created with LLM analysis instructions | auto |
|
||||
169
gaps/misc-uncategorized.md
Normal file
169
gaps/misc-uncategorized.md
Normal file
@@ -0,0 +1,169 @@
|
||||
# Misc / Uncategorized — Gap Analysis
|
||||
|
||||
> This file tracks what has and hasn't been ported from Go to .NET for the **Misc / Uncategorized** 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 Misc / Uncategorized
|
||||
|
||||
- This file captures items that don't cleanly fit elsewhere.
|
||||
- `norace_*_test.go` files (12,009 LOC) contain long-running stress/race tests spanning multiple subsystems — map to `Stress/` tests in .NET.
|
||||
- `benchmark_*.go` files contain Go benchmarks — may map to `[Fact]` tests with timing assertions or BenchmarkDotNet.
|
||||
- The Go `test/` integration directory (35 files, 29,812 LOC) contains end-to-end tests that may overlap with .NET test subdirectories.
|
||||
- As items are identified and categorized, move them to the appropriate category file.
|
||||
|
||||
---
|
||||
|
||||
## Go Reference Files (Source)
|
||||
|
||||
- Files not fitting into other categories
|
||||
- Small Go source files that don't belong to a major subsystem
|
||||
- Platform-specific stubs or build-tag-only files
|
||||
|
||||
## Go Reference Files (Tests)
|
||||
|
||||
- `golang/nats-server/server/ping_test.go`
|
||||
- `golang/nats-server/server/closed_conns_test.go`
|
||||
- `golang/nats-server/server/norace_1_test.go`
|
||||
- `golang/nats-server/server/norace_2_test.go`
|
||||
- `golang/nats-server/server/benchmark_publish_test.go`
|
||||
- `golang/nats-server/server/core_benchmarks_test.go`
|
||||
- Various integration tests in `golang/nats-server/test/`
|
||||
|
||||
## .NET Implementation Files (Source)
|
||||
|
||||
- Any .NET source files not covered by other category files
|
||||
|
||||
## .NET Implementation Files (Tests)
|
||||
|
||||
- `tests/NATS.Server.Tests/Stress/`
|
||||
- `tests/NATS.Server.Tests/Parity/`
|
||||
- Other root-level test files
|
||||
|
||||
---
|
||||
|
||||
## Gap Inventory
|
||||
|
||||
<!-- After analysis, fill in this table. Group rows by Go source file. -->
|
||||
|
||||
### Platform-Specific Source Files
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| diskAvailable | golang/nats-server/server/disk_avail.go:1 | NOT_APPLICABLE | N/A | Base function; platform implementations below override it |
|
||||
| diskAvailable (openbsd) | golang/nats-server/server/disk_avail_openbsd.go:23 | PARTIAL | NatsServer.cs (JetStream disk checking stub) | .NET has minimal disk-space checking; full syscall.Statfs equivalent not implemented |
|
||||
| diskAvailable (netbsd) | golang/nats-server/server/disk_avail_netbsd.go:19 | NOT_APPLICABLE | N/A | NetBSD stub returning default; .NET runs on Windows/.NET platforms only |
|
||||
| diskAvailable (solaris) | golang/nats-server/server/disk_avail_solaris.go:23 | NOT_APPLICABLE | N/A | Solaris/illumos stub; not supported platform for .NET |
|
||||
| diskAvailable (wasm) | golang/nats-server/server/disk_avail_wasm.go:18 | NOT_APPLICABLE | N/A | WASM stub; .NET does not compile to WASM for NATS server |
|
||||
| diskAvailable (windows) | golang/nats-server/server/disk_avail_windows.go:19 | PARTIAL | NatsServer.cs (JetStream disk checking stub) | .NET has minimal implementation; full Windows disk space API not used |
|
||||
| SetServiceName | golang/nats-server/server/service_windows.go:34 | MISSING | N/A | .NET host app does not implement Windows service mode |
|
||||
| winServiceWrapper.Execute | golang/nats-server/server/service_windows.go:64 | MISSING | N/A | .NET uses standard .NET Worker Service abstraction instead |
|
||||
| Run (service) | golang/nats-server/server/service_windows.go:115 | MISSING | N/A | .NET app startup does not support Windows service wrapper |
|
||||
| isWindowsService | golang/nats-server/server/service_windows.go:132 | MISSING | N/A | .NET does not expose Windows service detection |
|
||||
| handleSignals (wasm) | golang/nats-server/server/signal_wasm.go:18 | NOT_APPLICABLE | N/A | WASM stub; .NET does not target WASM |
|
||||
| ProcessSignal (wasm) | golang/nats-server/server/signal_wasm.go:22 | NOT_APPLICABLE | N/A | WASM stub; .NET does not target WASM |
|
||||
| handleSignals (windows) | golang/nats-server/server/signal_windows.go:28 | PARTIAL | NatsServer.cs (event loop shutdown) | .NET uses CancellationToken instead of signal channels |
|
||||
| ProcessSignal (windows) | golang/nats-server/server/signal_windows.go:53 | MISSING | N/A | .NET does not support remote signal commands to Windows services |
|
||||
| reopenLogCode, ldmCode | golang/nats-server/server/service_windows.go:24-28 | PARTIAL | Host program configuration | .NET logging uses Serilog; log rotation not exposed as service commands |
|
||||
|
||||
### Test Files — Miscellaneous
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| TestPing | golang/nats-server/server/ping_test.go:34 | PORTED | tests/NATS.Server.Tests/ServerTests.cs:PingKeepaliveTests | Raw socket PING/PONG protocol test ported |
|
||||
| DefaultPingOptions | golang/nats-server/server/ping_test.go:26 | PORTED | tests/NATS.Server.Tests/ServerTests.cs:PingKeepaliveTests | Test options reflected in C# test setup |
|
||||
| TestClosedConnsAccounting | golang/nats-server/server/closed_conns_test.go:46 | PARTIAL | tests/NATS.Server.Tests/Monitoring/ClosedConnectionRingBufferTests.cs | Ring buffer implementation exists; not all closed-conn tracking tests ported |
|
||||
| TestClosedConnsSubsAccounting | golang/nats-server/server/closed_conns_test.go:102 | PARTIAL | tests/NATS.Server.Tests/Monitoring/ClosedConnectionRingBufferTests.cs | Subscription tracking in closed conns; basic tests exist |
|
||||
| TestClosedAuthorizationTimeout | golang/nats-server/server/closed_conns_test.go:143 | MISSING | N/A | Auth timeout closure tracking not fully tested |
|
||||
| TestClosedAuthorizationViolation | golang/nats-server/server/closed_conns_test.go:164 | PARTIAL | tests/NATS.Server.Tests/Monitoring/MonitorGoParityTests.cs | Auth violation tracking partially tested |
|
||||
| TestClosedUPAuthorizationViolation | golang/nats-server/server/closed_conns_test.go:187 | PARTIAL | tests/NATS.Server.Tests/Configuration/OptsGoParityTests.cs | Username/password auth failure tracking not fully tested |
|
||||
| TestClosedMaxPayload | golang/nats-server/server/closed_conns_test.go:219 | MISSING | N/A | Max payload violation closure tracking not tested |
|
||||
| TestClosedTLSHandshake | golang/nats-server/server/closed_conns_test.go:247 | MISSING | N/A | TLS handshake failure closure tracking not tested |
|
||||
| NoRace tests (norace_1_test.go) | golang/nats-server/server/norace_1_test.go:1 | PARTIAL | tests/NATS.Server.Tests/Stress/ (2,342 LOC vs 8,497 Go LOC) | Long-running race/concurrency tests; ~27% mapped to .NET Stress tests |
|
||||
| NoRace tests (norace_2_test.go) | golang/nats-server/server/norace_2_test.go:1 | PARTIAL | tests/NATS.Server.Tests/Stress/ | Additional race/concurrency scenarios; ~27% coverage in .NET |
|
||||
| BenchmarkPublish | golang/nats-server/server/benchmark_publish_test.go:1 | DEFERRED | N/A | Go benchmarks not directly portable; .NET uses different perf tooling (BenchmarkDotNet) |
|
||||
| CoreBenchmarks | golang/nats-server/server/core_benchmarks_test.go:1 | DEFERRED | N/A | Core server benchmarks; .NET performance profiling uses different approach |
|
||||
|
||||
### Integration Tests (test/ directory)
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| test/ping_test.go | golang/nats-server/test/ping_test.go:1 | PARTIAL | tests/NATS.Server.Tests/ServerTests.cs | Integration-level ping tests; basic coverage exists in .NET |
|
||||
| test/norace_test.go | golang/nats-server/test/norace_test.go:1 | PARTIAL | tests/NATS.Server.Tests/Stress/ | Race-condition integration tests; some mapped to Stress tests |
|
||||
| Other test/*.go (35 files total) | golang/nats-server/test/:1 | PARTIAL | tests/NATS.Server.Tests/ (spread across multiple categories) | Integration tests distributed across functional categories; not all scenarios covered |
|
||||
|
||||
---
|
||||
|
||||
## 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
|
||||
# Various files — re-count as needed
|
||||
# Re-count .NET test LOC for this module
|
||||
find tests/NATS.Server.Tests/Stress/ tests/NATS.Server.Tests/Parity/ -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-25 | Initial gap inventory analysis: 8 platform-specific source files, 14 test symbols, 35 integration tests | claude |
|
||||
| 2026-02-25 | File created with LLM analysis instructions | auto |
|
||||
314
gaps/monitoring.md
Normal file
314
gaps/monitoring.md
Normal file
@@ -0,0 +1,314 @@
|
||||
# Monitoring — Gap Analysis
|
||||
|
||||
> This file tracks what has and hasn't been ported from Go to .NET for the **Monitoring** 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 Monitoring
|
||||
|
||||
- The monitoring JSON response shapes must match the Go server for tooling compatibility (nats-top, Prometheus exporters, etc.).
|
||||
- Each endpoint has query parameters for filtering and sorting (e.g., `/connz?sort=bytes_to&limit=10`).
|
||||
- `/healthz` is used for Kubernetes liveness probes and has JetStream-aware health checks.
|
||||
- Port 8222 by default.
|
||||
|
||||
---
|
||||
|
||||
## Go Reference Files (Source)
|
||||
|
||||
- `golang/nats-server/server/monitor.go` — HTTP monitoring endpoints (~4,200 lines): `/varz`, `/connz`, `/routez`, `/gatewayz`, `/leafz`, `/subsz`, `/jsz`, `/healthz`, `/accountz`
|
||||
- `golang/nats-server/server/monitor_sort_opts.go` — Sort options for monitoring endpoints
|
||||
|
||||
## Go Reference Files (Tests)
|
||||
|
||||
- `golang/nats-server/server/monitor_test.go`
|
||||
- `golang/nats-server/test/monitor_test.go` (integration)
|
||||
|
||||
## .NET Implementation Files (Source)
|
||||
|
||||
- `src/NATS.Server/Monitoring/MonitorServer.cs`
|
||||
- `src/NATS.Server/Monitoring/Varz.cs`
|
||||
- `src/NATS.Server/Monitoring/Subsz.cs`
|
||||
- `src/NATS.Server/Monitoring/VarzHandler.cs`
|
||||
- `src/NATS.Server/Monitoring/SubszHandler.cs`
|
||||
- `src/NATS.Server/Monitoring/JszHandler.cs`
|
||||
- `src/NATS.Server/Monitoring/AccountzHandler.cs`
|
||||
- `src/NATS.Server/Monitoring/GatewayzHandler.cs`
|
||||
- `src/NATS.Server/Monitoring/LeafzHandler.cs`
|
||||
- `src/NATS.Server/Monitoring/RoutezHandler.cs`
|
||||
- `src/NATS.Server/Monitoring/PprofHandler.cs`
|
||||
- `src/NATS.Server/Monitoring/ClosedClient.cs`
|
||||
- All other files in `src/NATS.Server/Monitoring/`
|
||||
|
||||
## .NET Implementation Files (Tests)
|
||||
|
||||
- `tests/NATS.Server.Tests/Monitoring/`
|
||||
|
||||
---
|
||||
|
||||
## Gap Inventory
|
||||
|
||||
<!-- After analysis, fill in this table. Group rows by Go source file. -->
|
||||
|
||||
### monitor_sort_opts.go
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `ConnInfos` (type alias `[]*ConnInfo`) | golang/nats-server/server/monitor_sort_opts.go:21 | PORTED | src/NATS.Server/Monitoring/Connz.cs:30 | .NET uses `ConnInfo[]` arrays instead |
|
||||
| `ConnInfos.Len()` | golang/nats-server/server/monitor_sort_opts.go:25 | NOT_APPLICABLE | — | Go sort.Interface boilerplate; .NET LINQ handles sorting natively |
|
||||
| `ConnInfos.Swap()` | golang/nats-server/server/monitor_sort_opts.go:28 | NOT_APPLICABLE | — | Go sort.Interface boilerplate; .NET LINQ handles sorting natively |
|
||||
| `SortOpt` (string type) | golang/nats-server/server/monitor_sort_opts.go:31 | PORTED | src/NATS.Server/Monitoring/Connz.cs:595 | .NET has both `SortOpt` enum (internal) and `ConnzSortOption` enum (public API) |
|
||||
| `ByCid` constant | golang/nats-server/server/monitor_sort_opts.go:35 | PORTED | src/NATS.Server/Monitoring/Connz.cs:597 | `SortOpt.ByCid` |
|
||||
| `ByStart` constant | golang/nats-server/server/monitor_sort_opts.go:36 | PORTED | src/NATS.Server/Monitoring/Connz.cs:598 | `SortOpt.ByStart` |
|
||||
| `BySubs` constant | golang/nats-server/server/monitor_sort_opts.go:37 | PORTED | src/NATS.Server/Monitoring/Connz.cs:599 | `SortOpt.BySubs` |
|
||||
| `ByPending` constant | golang/nats-server/server/monitor_sort_opts.go:38 | PORTED | src/NATS.Server/Monitoring/Connz.cs:600 | `SortOpt.ByPending` |
|
||||
| `ByOutMsgs` constant | golang/nats-server/server/monitor_sort_opts.go:39 | PORTED | src/NATS.Server/Monitoring/Connz.cs:601 | `SortOpt.ByMsgsTo` |
|
||||
| `ByInMsgs` constant | golang/nats-server/server/monitor_sort_opts.go:40 | PORTED | src/NATS.Server/Monitoring/Connz.cs:602 | `SortOpt.ByMsgsFrom` |
|
||||
| `ByOutBytes` constant | golang/nats-server/server/monitor_sort_opts.go:41 | PORTED | src/NATS.Server/Monitoring/Connz.cs:603 | `SortOpt.ByBytesTo` |
|
||||
| `ByInBytes` constant | golang/nats-server/server/monitor_sort_opts.go:42 | PORTED | src/NATS.Server/Monitoring/Connz.cs:604 | `SortOpt.ByBytesFrom` |
|
||||
| `ByLast` constant | golang/nats-server/server/monitor_sort_opts.go:43 | PORTED | src/NATS.Server/Monitoring/Connz.cs:605 | `SortOpt.ByLast` |
|
||||
| `ByIdle` constant | golang/nats-server/server/monitor_sort_opts.go:44 | PORTED | src/NATS.Server/Monitoring/Connz.cs:606 | `SortOpt.ByIdle` |
|
||||
| `ByUptime` constant | golang/nats-server/server/monitor_sort_opts.go:45 | PORTED | src/NATS.Server/Monitoring/Connz.cs:607 | `SortOpt.ByUptime` |
|
||||
| `ByStop` constant | golang/nats-server/server/monitor_sort_opts.go:46 | PORTED | src/NATS.Server/Monitoring/Connz.cs:608 | `SortOpt.ByStop` |
|
||||
| `ByReason` constant | golang/nats-server/server/monitor_sort_opts.go:47 | PORTED | src/NATS.Server/Monitoring/Connz.cs:609 | `SortOpt.ByReason` |
|
||||
| `ByRTT` constant | golang/nats-server/server/monitor_sort_opts.go:48 | PORTED | src/NATS.Server/Monitoring/Connz.cs:610 | `SortOpt.ByRtt` |
|
||||
| `SortByCid` | golang/nats-server/server/monitor_sort_opts.go:53 | PORTED | src/NATS.Server/Monitoring/ConnzHandler.cs:59 | Implemented as LINQ OrderBy in the sort switch |
|
||||
| `SortBySubs` | golang/nats-server/server/monitor_sort_opts.go:58 | PORTED | src/NATS.Server/Monitoring/ConnzHandler.cs:61 | LINQ OrderByDescending |
|
||||
| `SortByPending` | golang/nats-server/server/monitor_sort_opts.go:63 | PORTED | src/NATS.Server/Monitoring/ConnzHandler.cs:62 | LINQ OrderByDescending |
|
||||
| `SortByOutMsgs` | golang/nats-server/server/monitor_sort_opts.go:68 | PORTED | src/NATS.Server/Monitoring/ConnzHandler.cs:63 | LINQ OrderByDescending |
|
||||
| `SortByInMsgs` | golang/nats-server/server/monitor_sort_opts.go:73 | PORTED | src/NATS.Server/Monitoring/ConnzHandler.cs:64 | LINQ OrderByDescending |
|
||||
| `SortByOutBytes` | golang/nats-server/server/monitor_sort_opts.go:78 | PORTED | src/NATS.Server/Monitoring/ConnzHandler.cs:65 | LINQ OrderByDescending |
|
||||
| `SortByInBytes` | golang/nats-server/server/monitor_sort_opts.go:83 | PORTED | src/NATS.Server/Monitoring/ConnzHandler.cs:66 | LINQ OrderByDescending |
|
||||
| `SortByLast` | golang/nats-server/server/monitor_sort_opts.go:88 | PORTED | src/NATS.Server/Monitoring/ConnzHandler.cs:67 | LINQ OrderByDescending |
|
||||
| `SortByIdle` | golang/nats-server/server/monitor_sort_opts.go:95 | PORTED | src/NATS.Server/Monitoring/ConnzHandler.cs:68 | LINQ OrderByDescending with now capture |
|
||||
| `SortByUptime` | golang/nats-server/server/monitor_sort_opts.go:105 | PORTED | src/NATS.Server/Monitoring/ConnzHandler.cs:69 | LINQ OrderByDescending |
|
||||
| `SortByStop` | golang/nats-server/server/monitor_sort_opts.go:128 | PORTED | src/NATS.Server/Monitoring/ConnzHandler.cs:70 | LINQ OrderByDescending |
|
||||
| `SortByReason` | golang/nats-server/server/monitor_sort_opts.go:137 | PORTED | src/NATS.Server/Monitoring/ConnzHandler.cs:71 | LINQ OrderBy |
|
||||
| `SortByRTT` | golang/nats-server/server/monitor_sort_opts.go:144 | PORTED | src/NATS.Server/Monitoring/ConnzHandler.cs:72 | LINQ OrderBy |
|
||||
| `SortOpt.IsValid()` | golang/nats-server/server/monitor_sort_opts.go:149 | PARTIAL | src/NATS.Server/Monitoring/ConnzHandler.cs:246 | Validity is implicit in the parse switch (falls through to ByCid default); no explicit IsValid method |
|
||||
|
||||
---
|
||||
|
||||
### monitor.go — Types and Structs
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `Connz` struct | golang/nats-server/server/monitor.go:48 | PORTED | src/NATS.Server/Monitoring/Connz.cs:8 | All JSON fields match |
|
||||
| `ConnzOptions` struct | golang/nats-server/server/monitor.go:59 | PORTED | src/NATS.Server/Monitoring/Connz.cs:628 | All options ported |
|
||||
| `ConnState` type | golang/nats-server/server/monitor.go:102 | PORTED | src/NATS.Server/Monitoring/Connz.cs:617 | `ConnState` enum with Open/Closed/All |
|
||||
| `ConnOpen` constant | golang/nats-server/server/monitor.go:105 | PORTED | src/NATS.Server/Monitoring/Connz.cs:619 | `ConnState.Open` |
|
||||
| `ConnClosed` constant | golang/nats-server/server/monitor.go:107 | PORTED | src/NATS.Server/Monitoring/Connz.cs:620 | `ConnState.Closed` |
|
||||
| `ConnAll` constant | golang/nats-server/server/monitor.go:109 | PORTED | src/NATS.Server/Monitoring/Connz.cs:621 | `ConnState.All` |
|
||||
| `ConnInfo` struct | golang/nats-server/server/monitor.go:114 | PARTIAL | src/NATS.Server/Monitoring/Connz.cs:36 | Missing: `Stalls` field (`stalls,omitempty`), `TLSPeerCerts []*TLSPeerCert` (replaced with single subject string `TlsPeerCertSubject`), `JWT` field, `IssuerKey` field, `NameTag` field, `Tags` field, `Proxy *ProxyInfo` (replaced with plain string) |
|
||||
| `ProxyInfo` struct | golang/nats-server/server/monitor.go:157 | PARTIAL | src/NATS.Server/Monitoring/Connz.cs:135 | .NET uses a plain `string Proxy` field instead of a struct with `Key` |
|
||||
| `TLSPeerCert` struct | golang/nats-server/server/monitor.go:163 | MISSING | — | Go has Subject, SubjectPKISha256, CertSha256; .NET `ConnInfo.TlsPeerCertSubject` only captures Subject, no SHA256 fields |
|
||||
| `DefaultConnListSize` constant | golang/nats-server/server/monitor.go:169 | PORTED | src/NATS.Server/Monitoring/Connz.cs:660 | Default 1024 on `ConnzOptions.Limit` |
|
||||
| `DefaultSubListSize` constant | golang/nats-server/server/monitor.go:172 | PORTED | src/NATS.Server/Monitoring/Subsz.cs:41 | Default 1024 on `SubszOptions.Limit` |
|
||||
| `Routez` struct | golang/nats-server/server/monitor.go:782 | PARTIAL | src/NATS.Server/Monitoring/RoutezHandler.cs:12 | Go has full Routez struct with ID, Name, Now, Import, Export, NumRoutes, Routes; .NET returns anonymous object with only `routes` and `num_routes` counts |
|
||||
| `RoutezOptions` struct | golang/nats-server/server/monitor.go:793 | MISSING | — | No options struct; handler accepts no query params |
|
||||
| `RouteInfo` struct | golang/nats-server/server/monitor.go:801 | MISSING | — | Go has full per-route details (Rid, RemoteID, RTT, etc.); .NET returns just route count |
|
||||
| `Subsz` struct | golang/nats-server/server/monitor.go:928 | PARTIAL | src/NATS.Server/Monitoring/Subsz.cs:8 | Missing `SublistStats` embedded fields; .NET has `NumSubs` and `NumCache` instead of the full `SublistStats` type |
|
||||
| `SubszOptions` struct | golang/nats-server/server/monitor.go:940 | PORTED | src/NATS.Server/Monitoring/Subsz.cs:38 | All options present |
|
||||
| `SubDetail` struct | golang/nats-server/server/monitor.go:961 | PARTIAL | src/NATS.Server/Monitoring/Connz.cs:142 | Missing `AccountTag` field; present fields match |
|
||||
| `Varz` struct | golang/nats-server/server/monitor.go:1211 | PARTIAL | src/NATS.Server/Monitoring/Varz.cs:8 | Missing: `ConfigDigest`, `TrustedOperatorsJwt`, `TrustedOperatorsClaim`, `PinnedAccountFail` (present), `OCSPResponseCache`, `StaleConnectionStats` (present), `Proxies`, `WriteTimeout` string field; `StalledClients` field missing; `Metadata` field missing |
|
||||
| `JetStreamVarz` struct | golang/nats-server/server/monitor.go:1286 | PARTIAL | src/NATS.Server/Monitoring/Varz.cs:403 | Missing `Meta *MetaClusterInfo` and `Limits *JSLimitOpts` fields |
|
||||
| `ClusterOptsVarz` struct | golang/nats-server/server/monitor.go:1294 | PARTIAL | src/NATS.Server/Monitoring/Varz.cs:251 | Missing `WriteDeadline`, `WriteTimeout`, `TLSCertNotAfter` fields |
|
||||
| `GatewayOptsVarz` struct | golang/nats-server/server/monitor.go:1310 | PARTIAL | src/NATS.Server/Monitoring/Varz.cs:285 | Missing `Gateways []RemoteGatewayOptsVarz`, `WriteDeadline`, `WriteTimeout`, `TLSCertNotAfter` fields |
|
||||
| `RemoteGatewayOptsVarz` struct | golang/nats-server/server/monitor.go:1328 | MISSING | — | No .NET equivalent |
|
||||
| `LeafNodeOptsVarz` struct | golang/nats-server/server/monitor.go:1335 | PARTIAL | src/NATS.Server/Monitoring/Varz.cs:322 | Missing `Remotes []RemoteLeafOptsVarz`, `WriteDeadline`, `WriteTimeout`, `TLSCertNotAfter` fields |
|
||||
| `DenyRules` struct | golang/nats-server/server/monitor.go:1350 | MISSING | — | No .NET equivalent |
|
||||
| `RemoteLeafOptsVarz` struct | golang/nats-server/server/monitor.go:1356 | MISSING | — | No .NET equivalent |
|
||||
| `MQTTOptsVarz` struct | golang/nats-server/server/monitor.go:1365 | PARTIAL | src/NATS.Server/Monitoring/Varz.cs:350 | Missing `TLSOCSPPeerVerify`, `TLSCertNotAfter` fields |
|
||||
| `WebsocketOptsVarz` struct | golang/nats-server/server/monitor.go:1381 | PARTIAL | src/NATS.Server/Monitoring/Varz.cs:387 | Go has many more fields (Advertise, NoAuthUser, JWTCookie, HandshakeTimeout, AuthTimeout, NoTLS, TLSMap, TLSPinnedCerts, SameOrigin, AllowedOrigins, Compression, TLSOCSPPeerVerify, TLSCertNotAfter); .NET only has Host, Port, TlsTimeout |
|
||||
| `OCSPResponseCacheVarz` struct | golang/nats-server/server/monitor.go:1399 | MISSING | — | No .NET equivalent; OCSP cache monitoring not ported |
|
||||
| `ProxiesOptsVarz` struct | golang/nats-server/server/monitor.go:1411 | MISSING | — | No .NET equivalent |
|
||||
| `ProxyOptsVarz` struct | golang/nats-server/server/monitor.go:1416 | MISSING | — | No .NET equivalent |
|
||||
| `VarzOptions` struct | golang/nats-server/server/monitor.go:1422 | PORTED | — | Empty struct, not needed in .NET since handler takes no options |
|
||||
| `SlowConsumersStats` struct | golang/nats-server/server/monitor.go:1424 | PORTED | src/NATS.Server/Monitoring/Varz.cs:213 | All four fields present |
|
||||
| `StaleConnectionStats` struct | golang/nats-server/server/monitor.go:1432 | PORTED | src/NATS.Server/Monitoring/Varz.cs:232 | All four fields present |
|
||||
| `GatewayzOptions` struct | golang/nats-server/server/monitor.go:2004 | MISSING | — | .NET GatewayzHandler has no options; returns only counts |
|
||||
| `Gatewayz` struct | golang/nats-server/server/monitor.go:2024 | PARTIAL | src/NATS.Server/Monitoring/GatewayzHandler.cs:12 | Go has full struct with OutboundGateways/InboundGateways maps; .NET returns anonymous count object |
|
||||
| `RemoteGatewayz` struct | golang/nats-server/server/monitor.go:2036 | MISSING | — | No .NET equivalent |
|
||||
| `AccountGatewayz` struct | golang/nats-server/server/monitor.go:2043 | MISSING | — | No .NET equivalent |
|
||||
| `Leafz` struct | golang/nats-server/server/monitor.go:2348 | PARTIAL | src/NATS.Server/Monitoring/LeafzHandler.cs:12 | Go has full struct with NumLeafs and Leafs array; .NET returns anonymous count object |
|
||||
| `LeafzOptions` struct | golang/nats-server/server/monitor.go:2356 | MISSING | — | No options struct; handler accepts no query params |
|
||||
| `LeafInfo` struct | golang/nats-server/server/monitor.go:2364 | MISSING | — | No .NET equivalent for per-leafnode detail |
|
||||
| `AccountStatz` struct | golang/nats-server/server/monitor.go:2471 | PARTIAL | src/NATS.Server/Monitoring/AccountzHandler.cs:27 | Go has `ID`, `Now`, `Accounts []*AccountStat`; .NET returns anonymous object with total counts |
|
||||
| `AccountStatzOptions` struct | golang/nats-server/server/monitor.go:2478 | MISSING | — | No .NET equivalent; `?unused` query param not parsed |
|
||||
| `AccountzOptions` struct | golang/nats-server/server/monitor.go:2646 | MISSING | — | `?acc=` query param not parsed for account detail |
|
||||
| `ExtImport` struct | golang/nats-server/server/monitor.go:2662 | MISSING | — | JWT import monitoring detail not ported |
|
||||
| `ExtExport` struct | golang/nats-server/server/monitor.go:2672 | MISSING | — | JWT export monitoring detail not ported |
|
||||
| `ExtVrIssues` struct | golang/nats-server/server/monitor.go:2678 | MISSING | — | JWT validation result reporting not ported |
|
||||
| `ExtMap` type | golang/nats-server/server/monitor.go:2684 | MISSING | — | Account subject mapping detail not ported |
|
||||
| `AccountInfo` struct | golang/nats-server/server/monitor.go:2686 | MISSING | — | Detailed per-account monitoring not ported |
|
||||
| `Accountz` struct | golang/nats-server/server/monitor.go:2710 | PARTIAL | src/NATS.Server/Monitoring/AccountzHandler.cs:14 | Go has ID, Now, SystemAccount, Accounts list, and Account detail; .NET returns simplified anonymous object |
|
||||
| `JSzOptions` struct | golang/nats-server/server/monitor.go:2928 | PARTIAL | — | Most options not parsed from query string; handler builds static JSZ response |
|
||||
| `HealthzOptions` struct | golang/nats-server/server/monitor.go:2943 | MISSING | — | .NET /healthz endpoint does not parse any options; returns static "ok" |
|
||||
| `ProfilezOptions` struct | golang/nats-server/server/monitor.go:2956 | PARTIAL | src/NATS.Server/Monitoring/PprofHandler.cs:24 | Only `seconds` duration from CPU profile path; no `name` or `debug` level routing |
|
||||
| `IpqueueszOptions` struct | golang/nats-server/server/monitor.go:2963 | MISSING | — | /ipqueuesz endpoint not implemented |
|
||||
| `RaftzOptions` struct | golang/nats-server/server/monitor.go:2969 | MISSING | — | /raftz endpoint not implemented |
|
||||
| `StreamDetail` struct | golang/nats-server/server/monitor.go:2975 | MISSING | — | JSZ stream detail not ported |
|
||||
| `RaftGroupDetail` struct | golang/nats-server/server/monitor.go:2991 | MISSING | — | RAFT group detail not ported |
|
||||
| `AccountDetail` struct | golang/nats-server/server/monitor.go:2996 | MISSING | — | JSZ per-account detail not ported |
|
||||
| `MetaSnapshotStats` struct | golang/nats-server/server/monitor.go:3003 | MISSING | — | RAFT meta snapshot stats not ported |
|
||||
| `MetaClusterInfo` struct | golang/nats-server/server/monitor.go:3011 | MISSING | — | RAFT meta cluster info not ported (referenced in JetStreamVarz.Meta) |
|
||||
| `JSInfo` struct | golang/nats-server/server/monitor.go:3023 | PARTIAL | src/NATS.Server/Monitoring/JszHandler.cs:39 | Go JSInfo has JetStreamStats embedded, ID, Now, Disabled, Config, Limits, Streams, StreamsLeader, Consumers, ConsumersLeader, Messages, Bytes, Meta, AccountDetails, Total; .NET JszResponse has simplified fields with different JSON names (`api_total`/`api_errors` instead of nested `api`) |
|
||||
| `HealthStatus` struct | golang/nats-server/server/monitor.go:3408 | MISSING | — | /healthz returns plain "ok" string, not the structured HealthStatus with Status/StatusCode/Error/Errors |
|
||||
| `HealthzError` struct | golang/nats-server/server/monitor.go:3415 | MISSING | — | No .NET equivalent |
|
||||
| `HealthZErrorType` type | golang/nats-server/server/monitor.go:3423 | MISSING | — | No .NET equivalent |
|
||||
| `ExpvarzStatus` struct | golang/nats-server/server/monitor.go:4019 | MISSING | — | /expvarz endpoint not implemented |
|
||||
| `ProfilezStatus` struct | golang/nats-server/server/monitor.go:4043 | MISSING | — | No structured response; CPU profile endpoint returns raw bytes |
|
||||
| `RaftzGroup` struct | golang/nats-server/server/monitor.go:4086 | MISSING | — | /raftz endpoint not implemented |
|
||||
| `RaftzGroupPeer` struct | golang/nats-server/server/monitor.go:4114 | MISSING | — | /raftz endpoint not implemented |
|
||||
| `RaftzStatus` type | golang/nats-server/server/monitor.go:4121 | MISSING | — | /raftz endpoint not implemented |
|
||||
| `IpqueueszStatusIPQ` struct | golang/nats-server/server/monitor.go:1162 | MISSING | — | /ipqueuesz endpoint not implemented |
|
||||
| `IpqueueszStatus` type | golang/nats-server/server/monitor.go:1167 | MISSING | — | /ipqueuesz endpoint not implemented |
|
||||
|
||||
---
|
||||
|
||||
### monitor.go — Methods and Functions
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `newSubsDetailList()` | golang/nats-server/server/monitor.go:176 | PORTED | src/NATS.Server/Monitoring/ConnzHandler.cs:189 | Implemented inline in BuildConnInfo |
|
||||
| `newSubsList()` | golang/nats-server/server/monitor.go:184 | PORTED | src/NATS.Server/Monitoring/ConnzHandler.cs:180 | Implemented inline in BuildConnInfo |
|
||||
| `Server.Connz()` | golang/nats-server/server/monitor.go:193 | PORTED | src/NATS.Server/Monitoring/ConnzHandler.cs:12 | `ConnzHandler.HandleConnz()` |
|
||||
| `ConnInfo.fill()` | golang/nats-server/server/monitor.go:556 | PORTED | src/NATS.Server/Monitoring/ConnzHandler.cs:144 | `BuildConnInfo()` static method |
|
||||
| `createProxyInfo()` | golang/nats-server/server/monitor.go:609 | PARTIAL | src/NATS.Server/Monitoring/ConnzHandler.cs:174 | .NET sets `Proxy` to a plain string; no ProxyInfo struct |
|
||||
| `makePeerCerts()` | golang/nats-server/server/monitor.go:616 | PARTIAL | src/NATS.Server/Monitoring/ConnzHandler.cs:170 | .NET only captures Subject, not SubjectPKISha256 or CertSha256 |
|
||||
| `client.getRTT()` | golang/nats-server/server/monitor.go:629 | PORTED | src/NATS.Server/Monitoring/ConnzHandler.cs:321 | `FormatRtt()` formats from stored `client.Rtt` |
|
||||
| `decodeBool()` | golang/nats-server/server/monitor.go:647 | PORTED | src/NATS.Server/Monitoring/ConnzHandler.cs:267 | Inline query param parsing in ParseQueryParams |
|
||||
| `decodeUint64()` | golang/nats-server/server/monitor.go:661 | PORTED | src/NATS.Server/Monitoring/ConnzHandler.cs:291 | Inline query param parsing |
|
||||
| `decodeInt()` | golang/nats-server/server/monitor.go:675 | PORTED | src/NATS.Server/Monitoring/ConnzHandler.cs:285 | Inline query param parsing |
|
||||
| `decodeState()` | golang/nats-server/server/monitor.go:689 | PORTED | src/NATS.Server/Monitoring/ConnzHandler.cs:275 | State parsing in ParseQueryParams |
|
||||
| `decodeSubs()` | golang/nats-server/server/monitor.go:709 | PORTED | src/NATS.Server/Monitoring/ConnzHandler.cs:266 | Subs + subs-detail parsing |
|
||||
| `Server.HandleConnz()` | golang/nats-server/server/monitor.go:718 | PORTED | src/NATS.Server/Monitoring/MonitorServer.cs:70 | Mapped at `/connz` route |
|
||||
| `Server.Routez()` | golang/nats-server/server/monitor.go:830 | PARTIAL | src/NATS.Server/Monitoring/RoutezHandler.cs:13 | Go returns full Routez struct with per-route RouteInfo; .NET returns only count |
|
||||
| `Server.HandleRoutez()` | golang/nats-server/server/monitor.go:904 | PARTIAL | src/NATS.Server/Monitoring/MonitorServer.cs:76 | Endpoint mapped, but subs/subscriptions_detail query params not parsed |
|
||||
| `newSubDetail()` | golang/nats-server/server/monitor.go:973 | PORTED | src/NATS.Server/Monitoring/SubszHandler.cs:51 | Implemented inline |
|
||||
| `newClientSubDetail()` | golang/nats-server/server/monitor.go:981 | PORTED | src/NATS.Server/Monitoring/ConnzHandler.cs:189 | Implemented inline |
|
||||
| `Server.Subsz()` | golang/nats-server/server/monitor.go:992 | PORTED | src/NATS.Server/Monitoring/SubszHandler.cs:12 | `SubszHandler.HandleSubsz()` |
|
||||
| `Server.HandleSubsz()` | golang/nats-server/server/monitor.go:1095 | PORTED | src/NATS.Server/Monitoring/MonitorServer.cs:91 | Mapped at `/subz` and `/subscriptionsz` |
|
||||
| `Server.HandleStacksz()` | golang/nats-server/server/monitor.go:1142 | MISSING | — | No .NET /stacksz endpoint |
|
||||
| `Server.Ipqueuesz()` | golang/nats-server/server/monitor.go:1169 | MISSING | — | No .NET /ipqueuesz endpoint |
|
||||
| `Server.HandleIPQueuesz()` | golang/nats-server/server/monitor.go:1194 | MISSING | — | No .NET /ipqueuesz endpoint |
|
||||
| `Server.updateJszVarz()` | golang/nats-server/server/monitor.go:1555 | PARTIAL | src/NATS.Server/Monitoring/VarzHandler.cs:125 | Partially in VarzHandler; missing Meta/Limits/Config update logic |
|
||||
| `Server.Varz()` | golang/nats-server/server/monitor.go:1580 | PORTED | src/NATS.Server/Monitoring/VarzHandler.cs:30 | `VarzHandler.HandleVarzAsync()` |
|
||||
| `Server.createVarz()` | golang/nats-server/server/monitor.go:1603 | PORTED | src/NATS.Server/Monitoring/VarzHandler.cs:67 | Logic inline in HandleVarzAsync |
|
||||
| `urlsToStrings()` | golang/nats-server/server/monitor.go:1756 | NOT_APPLICABLE | — | Helper for URL slice conversion; .NET uses LINQ Select |
|
||||
| `Server.updateVarzConfigReloadableFields()` | golang/nats-server/server/monitor.go:1768 | PARTIAL | src/NATS.Server/Monitoring/VarzHandler.cs:67 | Config fields set at construction; no reload tracking; missing TLSCertNotAfter for cluster/gateway/leafnode/mqtt/websocket |
|
||||
| `Server.updateVarzRuntimeFields()` | golang/nats-server/server/monitor.go:1840 | PORTED | src/NATS.Server/Monitoring/VarzHandler.cs:67 | Runtime fields computed each request |
|
||||
| `getPinnedCertsAsSlice()` | golang/nats-server/server/monitor.go:1825 | PORTED | src/NATS.Server/Monitoring/VarzHandler.cs:173 | Present for MQTT; no websocket equivalent |
|
||||
| `Server.HandleVarz()` | golang/nats-server/server/monitor.go:1946 | PORTED | src/NATS.Server/Monitoring/MonitorServer.cs:64 | Mapped at `/varz` |
|
||||
| `Server.HandleRoot()` | golang/nats-server/server/monitor.go:1480 | PARTIAL | src/NATS.Server/Monitoring/MonitorServer.cs:47 | .NET returns JSON array of endpoint names; Go returns HTML page with links and NATS logo |
|
||||
| `myUptime()` | golang/nats-server/server/monitor.go:1440 | PORTED | src/NATS.Server/Monitoring/VarzHandler.cs:183 | `FormatUptime()` and `FormatDuration()` — same format logic |
|
||||
| `tlsCertNotAfter()` | golang/nats-server/server/monitor.go:1463 | PARTIAL | src/NATS.Server/Monitoring/VarzHandler.cs:53 | .NET only reads cert for client TLS; Go reads for all subsystem TLS configs |
|
||||
| `Server.Gatewayz()` | golang/nats-server/server/monitor.go:2054 | PARTIAL | src/NATS.Server/Monitoring/GatewayzHandler.cs:13 | Go returns full outbound/inbound gateway maps; .NET returns only counts |
|
||||
| `getMonitorGWOptions()` | golang/nats-server/server/monitor.go:2090 | MISSING | — | No options parsing in .NET GatewayzHandler |
|
||||
| `Server.createOutboundsRemoteGatewayz()` | golang/nats-server/server/monitor.go:2108 | MISSING | — | No outbound gateway detail |
|
||||
| `createOutboundRemoteGatewayz()` | golang/nats-server/server/monitor.go:2138 | MISSING | — | No outbound gateway detail |
|
||||
| `createOutboundAccountsGatewayz()` | golang/nats-server/server/monitor.go:2163 | MISSING | — | No gateway account interest detail |
|
||||
| `createAccountOutboundGatewayz()` | golang/nats-server/server/monitor.go:2192 | MISSING | — | No gateway account interest detail |
|
||||
| `Server.createInboundsRemoteGatewayz()` | golang/nats-server/server/monitor.go:2233 | MISSING | — | No inbound gateway detail |
|
||||
| `createInboundAccountsGatewayz()` | golang/nats-server/server/monitor.go:2265 | MISSING | — | No gateway account interest detail |
|
||||
| `createInboundAccountGatewayz()` | golang/nats-server/server/monitor.go:2292 | MISSING | — | No gateway account interest detail |
|
||||
| `Server.HandleGatewayz()` | golang/nats-server/server/monitor.go:2306 | PARTIAL | src/NATS.Server/Monitoring/MonitorServer.cs:81 | Endpoint mapped; query params (gw_name, accs, acc_name, subs) not parsed |
|
||||
| `Server.Leafz()` | golang/nats-server/server/monitor.go:2384 | PARTIAL | src/NATS.Server/Monitoring/LeafzHandler.cs:13 | Go returns full LeafInfo per leafnode; .NET returns only count |
|
||||
| `Server.HandleLeafz()` | golang/nats-server/server/monitor.go:2445 | PARTIAL | src/NATS.Server/Monitoring/MonitorServer.cs:86 | Endpoint mapped; `subs` and `acc` query params not parsed |
|
||||
| `Server.AccountStatz()` | golang/nats-server/server/monitor.go:2484 | PARTIAL | src/NATS.Server/Monitoring/AccountzHandler.cs:27 | .NET BuildStats() returns totals; no per-account `AccountStat` detail or `accounts` / `include_unused` filter |
|
||||
| `Server.HandleAccountStatz()` | golang/nats-server/server/monitor.go:2516 | PARTIAL | src/NATS.Server/Monitoring/MonitorServer.cs:106 | Endpoint mapped at `/accstatz`; `unused` query param not parsed |
|
||||
| `ResponseHandler()` | golang/nats-server/server/monitor.go:2542 | PARTIAL | src/NATS.Server/Monitoring/MonitorServer.cs:47 | .NET uses ASP.NET Core Results.Ok(); no JSONP callback support |
|
||||
| `handleResponse()` | golang/nats-server/server/monitor.go:2548 | PARTIAL | src/NATS.Server/Monitoring/MonitorServer.cs:47 | No custom status code support from handler level; no JSONP support |
|
||||
| `ClosedState.String()` | golang/nats-server/server/monitor.go:2565 | PORTED | src/NATS.Server/Monitoring/Connz.cs:511 | `ClosedReasonHelper.ToReasonString()` |
|
||||
| `Server.HandleAccountz()` | golang/nats-server/server/monitor.go:2718 | PARTIAL | src/NATS.Server/Monitoring/MonitorServer.cs:101 | Endpoint mapped; `acc` query param for account detail not parsed |
|
||||
| `Server.Accountz()` | golang/nats-server/server/monitor.go:2735 | PARTIAL | src/NATS.Server/Monitoring/AccountzHandler.cs:14 | Go returns full Accountz with optional AccountInfo detail; .NET returns simplified list |
|
||||
| `newExtServiceLatency()` | golang/nats-server/server/monitor.go:2652 | MISSING | — | JWT service latency detail not ported |
|
||||
| `newExtImport()` | golang/nats-server/server/monitor.go:2759 | MISSING | — | JWT import detail not ported |
|
||||
| `Server.accountInfo()` | golang/nats-server/server/monitor.go:2786 | MISSING | — | Full account info with exports/imports/mappings/JWT not ported |
|
||||
| `Server.JszAccount()` | golang/nats-server/server/monitor.go:3142 | MISSING | — | Per-account JSZ detail not ported |
|
||||
| `Server.raftNodeToClusterInfo()` | golang/nats-server/server/monitor.go:3162 | MISSING | — | RAFT cluster info helper not ported |
|
||||
| `Server.Jsz()` | golang/nats-server/server/monitor.go:3180 | PARTIAL | src/NATS.Server/Monitoring/JszHandler.cs:16 | Go Jsz() has full JSInfo with accounts/streams/consumers/meta; .NET JszHandler.Build() returns simplified flat fields |
|
||||
| `Server.accountDetail()` | golang/nats-server/server/monitor.go:3041 | MISSING | — | Per-account stream/consumer detail not ported |
|
||||
| `Server.HandleJsz()` | golang/nats-server/server/monitor.go:3334 | PARTIAL | src/NATS.Server/Monitoring/MonitorServer.cs:111 | Endpoint mapped; all query params (accounts, streams, consumers, config, offset, limit, leader-only, raft, stream-leader-only) not parsed |
|
||||
| `Server.HandleHealthz()` | golang/nats-server/server/monitor.go:3478 | PARTIAL | src/NATS.Server/Monitoring/MonitorServer.cs:59 | Endpoint mapped; Go does JetStream-aware checks; .NET returns static "ok" with no JS checks |
|
||||
| `Server.healthz()` | golang/nats-server/server/monitor.go:3537 | MISSING | — | Full healthz logic with JS stream/consumer recovery checks not ported |
|
||||
| `Server.Healthz()` | golang/nats-server/server/monitor.go:4014 | MISSING | — | Public Healthz() method not ported |
|
||||
| `Server.expvarz()` | golang/nats-server/server/monitor.go:4024 | MISSING | — | /expvarz endpoint not implemented |
|
||||
| `Server.profilez()` | golang/nats-server/server/monitor.go:4048 | PARTIAL | src/NATS.Server/Monitoring/PprofHandler.cs:24 | .NET CaptureCpuProfile() captures process metrics as JSON; Go uses pprof binary format |
|
||||
| `Server.HandleRaftz()` | golang/nats-server/server/monitor.go:4123 | MISSING | — | /raftz endpoint not implemented |
|
||||
| `Server.Raftz()` | golang/nats-server/server/monitor.go:4145 | MISSING | — | RAFT node status reporting not ported |
|
||||
|
||||
---
|
||||
|
||||
## 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/Monitoring/ -name '*.cs' -type f -exec cat {} + | wc -l
|
||||
# Re-count .NET test LOC for this module
|
||||
find tests/NATS.Server.Tests/Monitoring/ -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-25 | File created with LLM analysis instructions | auto |
|
||||
| 2026-02-25 | Full gap inventory populated: 63 PORTED, 42 PARTIAL, 61 MISSING, 3 NOT_APPLICABLE, 0 DEFERRED (169 total rows) | claude-sonnet-4-6 |
|
||||
408
gaps/mqtt.md
Normal file
408
gaps/mqtt.md
Normal file
@@ -0,0 +1,408 @@
|
||||
# MQTT — Gap Analysis
|
||||
|
||||
> This file tracks what has and hasn't been ported from Go to .NET for the **MQTT** 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 MQTT
|
||||
|
||||
- MQTT runs on top of NATS — MQTT topics map to NATS subjects.
|
||||
- Session state persists across reconnections (stored in JetStream streams).
|
||||
- QoS 0 (at most once), QoS 1 (at least once), QoS 2 (exactly once — limited support).
|
||||
- Retained messages stored as JetStream messages.
|
||||
- MQTT connections use `ClientKind = MQTT` and have a different parser path.
|
||||
|
||||
---
|
||||
|
||||
## Go Reference Files (Source)
|
||||
|
||||
- `golang/nats-server/server/mqtt.go` — MQTT protocol support (~5,882 lines). MQTT 3.1.1 and 5.0. Session state, QoS levels, retained messages, will messages.
|
||||
|
||||
## Go Reference Files (Tests)
|
||||
|
||||
- `golang/nats-server/server/mqtt_test.go`
|
||||
- `golang/nats-server/server/mqtt_ex_test_test.go`
|
||||
- `golang/nats-server/server/mqtt_ex_bench_test.go`
|
||||
|
||||
## .NET Implementation Files (Source)
|
||||
|
||||
- `src/NATS.Server/Mqtt/` (all files)
|
||||
|
||||
## .NET Implementation Files (Tests)
|
||||
|
||||
- `tests/NATS.Server.Tests/Mqtt/`
|
||||
|
||||
---
|
||||
|
||||
## Gap Inventory
|
||||
|
||||
<!-- After analysis, fill in this table. Group rows by Go source file. -->
|
||||
|
||||
### golang/nats-server/server/mqtt.go
|
||||
|
||||
#### Constants and Protocol Definitions (lines 41-199)
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| mqttPacket* constants | mqtt.go:42-57 | PORTED | src/NATS.Server/Mqtt/MqttPacketReader.cs:3-15 | MqttControlPacketType enum covers the packet types |
|
||||
| mqttProtoLevel | mqtt.go:59 | PORTED | src/NATS.Server/Mqtt/MqttBinaryDecoder.cs:87-88 | Checked in ParseConnect |
|
||||
| mqttConnFlag* constants | mqtt.go:62-68 | PORTED | src/NATS.Server/Mqtt/MqttBinaryDecoder.cs:96-104 | Decoded inline in ParseConnect |
|
||||
| mqttPubFlag* constants | mqtt.go:71-75 | PORTED | src/NATS.Server/Mqtt/MqttBinaryDecoder.cs:161-163 | Decoded inline in ParsePublish |
|
||||
| mqttSubscribeFlags | mqtt.go:78 | MISSING | | Subscribe flag validation not implemented in binary parser |
|
||||
| mqttConnAckRC* constants | mqtt.go:85-91 | MISSING | | ConnAck return code constants not defined |
|
||||
| mqttMaxPayloadSize | mqtt.go:94 | MISSING | | Max payload size constant not defined |
|
||||
| mqttTopicLevelSep, mqttSingleLevelWC, mqttMultiLevelWC | mqtt.go:97-100 | PORTED | src/NATS.Server/Mqtt/MqttBinaryDecoder.cs:254-258 | Used in TranslateFilterToNatsSubject |
|
||||
| mqttMultiLevelSidSuffix | mqtt.go:105 | MISSING | | Multi-level SID suffix for '#' wildcard not implemented |
|
||||
| mqttPrefix, mqttSubPrefix | mqtt.go:108-113 | MISSING | | MQTT internal subject prefixes not defined |
|
||||
| mqttStreamName, mqttStreamSubjectPrefix | mqtt.go:116-117 | MISSING | | JetStream stream naming not implemented |
|
||||
| mqttRetainedMsgsStreamName | mqtt.go:120-121 | MISSING | | Retained messages stream not implemented |
|
||||
| mqttSessStreamName | mqtt.go:124-125 | MISSING | | Session stream naming not implemented |
|
||||
| mqttQoS2IncomingMsgsStreamName | mqtt.go:131-132 | MISSING | | QoS2 incoming stream not implemented |
|
||||
| mqttOutStreamName, mqttPubRel* | mqtt.go:135-139 | MISSING | | PUBREL stream/subject not implemented |
|
||||
| mqttDefaultAckWait | mqtt.go:145 | MISSING | | Default ack wait not defined (Go: 30s) |
|
||||
| mqttDefaultMaxAckPending | mqtt.go:149 | PARTIAL | src/NATS.Server/Mqtt/MqttFlowController.cs:15 | Default 1024 matches Go, but not wired to JetStream |
|
||||
| mqttMaxAckTotalLimit | mqtt.go:153 | MISSING | | Max ack total limit (0xFFFF) not defined |
|
||||
| mqttJSA* token constants | mqtt.go:156-177 | MISSING | | JetStream API reply subject tokens not implemented |
|
||||
| mqttSessFlappingJailDur | mqtt.go:182 | PARTIAL | src/NATS.Server/Mqtt/MqttSessionStore.cs:96-106 | Flap detection exists but uses different default timing |
|
||||
| sparkb* constants | mqtt.go:201-211 | MISSING | | Sparkplug B protocol constants not implemented |
|
||||
| mqttNatsHeader* constants | mqtt.go:474-492 | MISSING | | NATS header names for MQTT message encoding not defined |
|
||||
|
||||
#### Core Types (lines 246-498)
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| srvMQTT | mqtt.go:246 | PARTIAL | src/NATS.Server/Mqtt/MqttListener.cs:8-12 | Listener exists but no integration with Server struct or authOverride |
|
||||
| mqttSessionManager | mqtt.go:253-256 | PARTIAL | src/NATS.Server/Mqtt/MqttSessionStore.cs:67 | Session store exists but not multi-account |
|
||||
| mqttAccountSessionManager | mqtt.go:258-270 | PARTIAL | src/NATS.Server/Mqtt/MqttSessionStore.cs:67 | Single-account only, no JetStream backing, no retained msg sublist, no session hash |
|
||||
| mqttJSA | mqtt.go:277-289 | MISSING | | JetStream API helper struct not implemented |
|
||||
| mqttJSPubMsg | mqtt.go:291-296 | MISSING | | JS publish message type not implemented |
|
||||
| mqttRetMsgDel | mqtt.go:298-301 | MISSING | | Retained message delete notification type not implemented |
|
||||
| mqttSession | mqtt.go:303-344 | PARTIAL | src/NATS.Server/Mqtt/MqttSessionStore.cs:48-60 | MqttSessionData covers basic fields but missing pendingPublish/pendingPubRel maps, cpending, last_pi, maxp, tmaxack, JetStream consumer tracking |
|
||||
| mqttPersistedSession | mqtt.go:346-353 | MISSING | | Persisted session JSON format not implemented |
|
||||
| mqttRetainedMsg | mqtt.go:355-364 | PARTIAL | src/NATS.Server/Mqtt/MqttRetainedStore.cs:14 | MqttRetainedMessage exists but missing Origin, Flags, Source, Topic fields; no cache TTL |
|
||||
| mqttRetainedMsgRef | mqtt.go:366-369 | MISSING | | Retained message reference (sseq + sub) not implemented |
|
||||
| mqttSub | mqtt.go:375-391 | MISSING | | MQTT subscription metadata (qos, jsDur, prm, reserved) not implemented |
|
||||
| mqtt (client struct) | mqtt.go:393-408 | PARTIAL | src/NATS.Server/Mqtt/MqttConnection.cs:6-16 | MqttConnection exists but missing reader/writer, asm, sess, cid, rejectQoS2Pub, downgradeQoS2Sub |
|
||||
| mqttPending | mqtt.go:410-414 | PARTIAL | src/NATS.Server/Mqtt/MqttQoS1Tracker.cs:89-96 | QoS1PendingMessage exists but missing sseq, jsAckSubject, jsDur fields |
|
||||
| mqttConnectProto | mqtt.go:416-420 | PARTIAL | src/NATS.Server/Mqtt/MqttBinaryDecoder.cs:14-25 | MqttConnectInfo covers most fields but is a record struct, not mutable; missing rd (read deadline) |
|
||||
| mqttIOReader | mqtt.go:422-425 | NOT_APPLICABLE | | Go-specific IO interface; .NET uses NetworkStream directly |
|
||||
| mqttReader | mqtt.go:427-433 | PARTIAL | src/NATS.Server/Mqtt/MqttPacketReader.cs:23-41 | MqttPacketReader handles fixed header; missing streaming buffer/partial packet support (pbuf, pstart) |
|
||||
| mqttWriter | mqtt.go:435-437 | PORTED | src/NATS.Server/Mqtt/MqttPacketWriter.cs:3-38 | MqttPacketWriter covers write operations |
|
||||
| mqttWill | mqtt.go:439-446 | PARTIAL | src/NATS.Server/Mqtt/MqttSessionStore.cs:35-42 | WillMessage exists but missing subject, mapped byte arrays; topic is string not bytes |
|
||||
| mqttFilter | mqtt.go:448-453 | MISSING | | MQTT filter struct (filter, qos, ttopic) not implemented as a standalone type |
|
||||
| mqttPublish | mqtt.go:455-463 | PARTIAL | src/NATS.Server/Mqtt/MqttBinaryDecoder.cs:31-37 | MqttPublishInfo covers most fields but missing subject, mapped byte arrays |
|
||||
| mqttParsedPublishNATSHeader | mqtt.go:495-499 | MISSING | | Parsed NATS header struct for MQTT messages not implemented |
|
||||
|
||||
#### Server Lifecycle Functions (lines 501-722)
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| Server.startMQTT | mqtt.go:501 | PARTIAL | src/NATS.Server/Mqtt/MqttListener.cs:27-38 | MqttListener.StartAsync exists but no TLS, no integration with Server lifecycle |
|
||||
| Server.createMQTTClient | mqtt.go:542 | PARTIAL | src/NATS.Server/Mqtt/MqttListener.cs:130-158 | AcceptLoopAsync creates MqttConnection but no TLS handshake, no auth timer, no read/write loops as goroutines |
|
||||
| Server.mqttConfigAuth | mqtt.go:666 | MISSING | | Auth configuration override not implemented |
|
||||
| validateMQTTOptions | mqtt.go:673 | MISSING | | MQTT option validation not implemented |
|
||||
| client.isMqtt | mqtt.go:726 | NOT_APPLICABLE | | .NET uses MqttConnection as a distinct type rather than a flag on client |
|
||||
| client.getMQTTClientID | mqtt.go:733 | PARTIAL | src/NATS.Server/Mqtt/MqttConnection.cs:13 | _clientId field exists but no public accessor |
|
||||
|
||||
#### Protocol Parsing (lines 742-959)
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| client.mqttParse | mqtt.go:742 | PARTIAL | src/NATS.Server/Mqtt/MqttConnection.cs:17-66 | RunAsync handles basic text-based protocol; missing binary MQTT packet dispatch, partial packet handling, read deadline management |
|
||||
| client.mqttTraceMsg | mqtt.go:961 | MISSING | | MQTT message tracing not implemented |
|
||||
|
||||
#### Connection Close and Session Cleanup (lines 976-1107)
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| Server.mqttHandleClosedClient | mqtt.go:976 | PARTIAL | src/NATS.Server/Mqtt/MqttConnection.cs:71-77 | DisposeAsync calls Unregister but no session cleanup, no will message handling, no clean session logic |
|
||||
| Server.mqttUpdateMaxAckPending | mqtt.go:1026 | MISSING | | Max ack pending update across sessions not implemented |
|
||||
| Server.mqttGetJSAForAccount | mqtt.go:1048 | MISSING | | JetStream API access per account not implemented |
|
||||
| Server.mqttStoreQoSMsgForAccountOnNewSubject | mqtt.go:1065 | MISSING | | QoS message re-store on subject change not implemented |
|
||||
| mqttParsePublishNATSHeader | mqtt.go:1080 | MISSING | | NATS header parsing for MQTT messages not implemented |
|
||||
| mqttParsePubRelNATSHeader | mqtt.go:1096 | MISSING | | PUBREL NATS header parsing not implemented |
|
||||
|
||||
#### Account Session Manager (lines 1112-2389)
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| Server.getOrCreateMQTTAccountSessionManager | mqtt.go:1112 | MISSING | | Per-account session manager creation not implemented |
|
||||
| Server.mqttCreateAccountSessionManager | mqtt.go:1152 | MISSING | | JetStream stream/consumer creation for MQTT not implemented (350+ lines) |
|
||||
| Server.mqttDetermineReplicas | mqtt.go:1502 | MISSING | | Replica count determination for cluster not implemented |
|
||||
| mqttJSA.newRequest | mqtt.go:1533 | MISSING | | JetStream API request helper not implemented |
|
||||
| mqttJSA.prefixDomain | mqtt.go:1537 | MISSING | | Domain prefix for JS subjects not implemented |
|
||||
| mqttJSA.newRequestEx | mqtt.go:1547 | MISSING | | Extended JS API request not implemented |
|
||||
| mqttJSA.newRequestExMulti | mqtt.go:1566 | MISSING | | Multi-request JS API helper not implemented |
|
||||
| mqttJSA.sendAck | mqtt.go:1649 | MISSING | | JS ack sending not implemented |
|
||||
| mqttJSA.sendMsg | mqtt.go:1654 | MISSING | | JS message sending not implemented |
|
||||
| mqttJSA.createEphemeralConsumer | mqtt.go:1663 | MISSING | | Ephemeral consumer creation not implemented |
|
||||
| mqttJSA.createDurableConsumer | mqtt.go:1677 | MISSING | | Durable consumer creation not implemented |
|
||||
| mqttJSA.deleteConsumer | mqtt.go:1692 | MISSING | | Consumer deletion not implemented |
|
||||
| mqttJSA.createStream | mqtt.go:1707 | MISSING | | Stream creation not implemented |
|
||||
| mqttJSA.updateStream | mqtt.go:1720 | MISSING | | Stream update not implemented |
|
||||
| mqttJSA.lookupStream | mqtt.go:1733 | MISSING | | Stream lookup not implemented |
|
||||
| mqttJSA.deleteStream | mqtt.go:1742 | MISSING | | Stream deletion not implemented |
|
||||
| mqttJSA.loadLastMsgFor | mqtt.go:1751 | MISSING | | Last message loading not implemented |
|
||||
| mqttJSA.loadLastMsgForMulti | mqtt.go:1765 | MISSING | | Multi-subject last message loading not implemented |
|
||||
| mqttJSA.loadNextMsgFor | mqtt.go:1789 | MISSING | | Next message loading not implemented |
|
||||
| mqttJSA.loadMsg | mqtt.go:1803 | MISSING | | Message loading by seq not implemented |
|
||||
| mqttJSA.storeMsgNoWait | mqtt.go:1817 | MISSING | | No-wait message store not implemented |
|
||||
| mqttJSA.storeMsg | mqtt.go:1825 | MISSING | | Message store not implemented |
|
||||
| mqttJSA.storeSessionMsg | mqtt.go:1834 | MISSING | | Session message store not implemented |
|
||||
| mqttJSA.loadSessionMsg | mqtt.go:1848 | MISSING | | Session message load not implemented |
|
||||
| mqttJSA.deleteMsg | mqtt.go:1853 | MISSING | | Message deletion not implemented |
|
||||
| isErrorOtherThan | mqtt.go:1879 | NOT_APPLICABLE | | Go error-checking helper; .NET uses exception types |
|
||||
| mqttAccountSessionManager.processJSAPIReplies | mqtt.go:1886 | MISSING | | JS API reply processing callback not implemented |
|
||||
| mqttAccountSessionManager.processRetainedMsg | mqtt.go:1971 | MISSING | | Retained message processing callback not implemented |
|
||||
| mqttAccountSessionManager.processRetainedMsgDel | mqtt.go:2006 | MISSING | | Retained message delete callback not implemented |
|
||||
| mqttAccountSessionManager.processSessionPersist | mqtt.go:2030 | MISSING | | Session persist callback not implemented |
|
||||
| mqttAccountSessionManager.addSessToFlappers | mqtt.go:2092 | PARTIAL | src/NATS.Server/Mqtt/MqttSessionStore.cs:251 | TrackConnectDisconnect exists but uses different data structures |
|
||||
| mqttAccountSessionManager.removeSessFromFlappers | mqtt.go:2116 | PARTIAL | src/NATS.Server/Mqtt/MqttSessionStore.cs:322 | ClearFlapperState exists |
|
||||
| mqttAccountSessionManager.createSubscription | mqtt.go:2124 | MISSING | | Internal subscription creation not implemented |
|
||||
| mqttAccountSessionManager.cleanupRetainedMessageCache | mqtt.go:2140 | MISSING | | Retained message cache cleanup timer not implemented |
|
||||
| mqttAccountSessionManager.sendJSAPIrequests | mqtt.go:2174 | MISSING | | JS API request sending goroutine not implemented |
|
||||
| mqttAccountSessionManager.addRetainedMsg | mqtt.go:2269 | PARTIAL | src/NATS.Server/Mqtt/MqttRetainedStore.cs:51-62 | SetRetained exists but no sseq tracking, no sublist insertion |
|
||||
| mqttAccountSessionManager.removeRetainedMsg | mqtt.go:2304 | PARTIAL | src/NATS.Server/Mqtt/MqttRetainedStore.cs:53-55 | SetRetained with empty payload removes, but no seq/sublist management |
|
||||
| mqttAccountSessionManager.lockSession | mqtt.go:2326 | MISSING | | Session locking mechanism not implemented |
|
||||
| mqttAccountSessionManager.unlockSession | mqtt.go:2345 | MISSING | | Session unlocking mechanism not implemented |
|
||||
| mqttAccountSessionManager.addSession | mqtt.go:2356 | PARTIAL | src/NATS.Server/Mqtt/MqttSessionStore.cs:192-196 | SaveSession exists but no hash-based lookup |
|
||||
| mqttAccountSessionManager.removeSession | mqtt.go:2372 | PARTIAL | src/NATS.Server/Mqtt/MqttSessionStore.cs:209-210 | DeleteSession exists but no hash-based removal |
|
||||
|
||||
#### Subscription Processing (lines 2389-2730)
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| mqttSession.processQOS12Sub | mqtt.go:2389 | MISSING | | QoS 1/2 subscription processing with JS consumer not implemented |
|
||||
| mqttSession.processSub | mqtt.go:2396 | PARTIAL | src/NATS.Server/Mqtt/MqttListener.cs:40-43 | RegisterSubscription exists but extremely simplified; no QoS handling, no JS consumer, no retained message delivery |
|
||||
| mqttAccountSessionManager.processSubs | mqtt.go:2463 | MISSING | | Bulk subscription processing with JS consumers not implemented (180 lines) |
|
||||
| mqttAccountSessionManager.serializeRetainedMsgsForSub | mqtt.go:2642 | MISSING | | Retained message serialization for new subscriptions not implemented |
|
||||
| mqttAccountSessionManager.addRetainedSubjectsForSubject | mqtt.go:2700 | MISSING | | Retained subject matching via sublist not implemented |
|
||||
| mqttAccountSessionManager.loadRetainedMessages | mqtt.go:2730 | MISSING | | Retained message loading from JetStream not implemented |
|
||||
|
||||
#### Retained Message Encoding/Decoding (lines 2798-2960)
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| mqttEncodeRetainedMessage | mqtt.go:2798 | MISSING | | Retained message NATS encoding with headers not implemented |
|
||||
| mqttSliceHeaders | mqtt.go:2855 | MISSING | | NATS header slicing utility not implemented |
|
||||
| mqttDecodeRetainedMessage | mqtt.go:2908 | MISSING | | Retained message NATS decoding not implemented |
|
||||
|
||||
#### Session CRUD (lines 2972-3190)
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| mqttAccountSessionManager.createOrRestoreSession | mqtt.go:2972 | PARTIAL | src/NATS.Server/Mqtt/MqttSessionStore.cs:370-397 | ConnectAsync handles basic create/restore but no JetStream persistence, no hash-based lookup |
|
||||
| mqttAccountSessionManager.deleteRetainedMsg | mqtt.go:3011 | MISSING | | JetStream retained message deletion not implemented |
|
||||
| mqttAccountSessionManager.notifyRetainedMsgDeleted | mqtt.go:3018 | MISSING | | Retained message delete notification not implemented |
|
||||
| mqttAccountSessionManager.transferUniqueSessStreamsToMuxed | mqtt.go:3031 | MISSING | | Session stream migration not implemented |
|
||||
| mqttAccountSessionManager.transferRetainedToPerKeySubjectStream | mqtt.go:3089 | MISSING | | Retained message stream migration not implemented |
|
||||
| mqttAccountSessionManager.getCachedRetainedMsg | mqtt.go:3146 | MISSING | | Retained message cache get not implemented |
|
||||
| mqttAccountSessionManager.setCachedRetainedMsg | mqtt.go:3169 | MISSING | | Retained message cache set not implemented |
|
||||
| mqttSessionCreate | mqtt.go:3193 | PARTIAL | src/NATS.Server/Mqtt/MqttSessionStore.cs:48-60 | MqttSessionData creation exists but missing maxp, pubRelSubject, idHash |
|
||||
| mqttSession.save | mqtt.go:3215 | PARTIAL | src/NATS.Server/Mqtt/MqttSessionStore.cs:417-425 | SaveSessionAsync exists but no expected-last-sequence header, simplified JSON |
|
||||
| mqttSession.clear | mqtt.go:3259 | PARTIAL | src/NATS.Server/Mqtt/MqttSessionStore.cs:209-210 | DeleteSession exists but no JetStream consumer cleanup |
|
||||
| mqttSession.update | mqtt.go:3316 | PARTIAL | src/NATS.Server/Mqtt/MqttSessionStore.cs:402-411 | AddSubscription exists but no filter-level granularity, no auto-persist |
|
||||
| mqttSession.bumpPI | mqtt.go:3345 | PARTIAL | src/NATS.Server/Mqtt/MqttQoS1Tracker.cs:75-83 | GetNextPacketId exists but simpler (no collision check with pendingPubRel) |
|
||||
| mqttSession.trackPublishRetained | mqtt.go:3375 | MISSING | | Retained message publish tracking not implemented |
|
||||
| mqttSession.trackPublish | mqtt.go:3400 | PARTIAL | src/NATS.Server/Mqtt/MqttQoS1Tracker.cs:27-39 | Register exists but no JS ack subject tracking, no cpending map |
|
||||
| mqttSession.untrackPublish | mqtt.go:3478 | PARTIAL | src/NATS.Server/Mqtt/MqttQoS1Tracker.cs:45-48 | Acknowledge exists but does not return JS ack subject |
|
||||
| mqttSession.trackAsPubRel | mqtt.go:3503 | MISSING | | PUBREL tracking not implemented |
|
||||
| mqttSession.untrackPubRel | mqtt.go:3542 | MISSING | | PUBREL untracking not implemented |
|
||||
| mqttSession.deleteConsumer | mqtt.go:3562 | MISSING | | JS consumer deletion from session not implemented |
|
||||
|
||||
#### CONNECT Processing (lines 3576-3988)
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| client.mqttParseConnect | mqtt.go:3576 | PARTIAL | src/NATS.Server/Mqtt/MqttBinaryDecoder.cs:70-140 | ParseConnect handles binary parsing well, but missing protocol level validation (rejects non-4), QoS2 will rejection, UTF-8 validation, subject mapping |
|
||||
| client.mqttConnectTrace | mqtt.go:3753 | MISSING | | Connect trace formatting not implemented |
|
||||
| Server.mqttProcessConnect | mqtt.go:3792 | PARTIAL | src/NATS.Server/Mqtt/MqttConnection.cs:34-48 | Connect handling exists but extremely simplified; no account session manager, no session restore, no flapper detection, no existing client eviction, no JetStream integration |
|
||||
| client.mqttEnqueueConnAck | mqtt.go:3976 | PARTIAL | src/NATS.Server/Mqtt/MqttConnection.cs:45 | Writes "CONNACK" text; missing binary CONNACK with session-present flag and return code |
|
||||
| Server.mqttHandleWill | mqtt.go:3990 | PARTIAL | src/NATS.Server/Mqtt/MqttSessionStore.cs:164-178 | PublishWillMessage exists but not integrated into connection close flow |
|
||||
|
||||
#### PUBLISH Processing (lines 4023-4560)
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| client.mqttParsePub | mqtt.go:4023 | PARTIAL | src/NATS.Server/Mqtt/MqttBinaryDecoder.cs:155-185 | ParsePublish handles basic parsing; missing topic-to-subject conversion, subject mapping, empty topic validation |
|
||||
| mqttPubTrace | mqtt.go:4096 | MISSING | | Publish trace formatting not implemented |
|
||||
| mqttComputeNatsMsgSize | mqtt.go:4113 | MISSING | | NATS message size computation not implemented |
|
||||
| mqttNewDeliverableMessage | mqtt.go:4139 | MISSING | | NATS deliverable message composition not implemented |
|
||||
| mqttNewDeliverablePubRel | mqtt.go:4183 | MISSING | | PUBREL deliverable message composition not implemented |
|
||||
| Server.mqttProcessPub | mqtt.go:4201 | PARTIAL | src/NATS.Server/Mqtt/MqttConnection.cs:53-63 | Basic QoS0/QoS1 publish exists; missing max payload check, QoS2 flow, JetStream message storage |
|
||||
| Server.mqttInitiateMsgDelivery | mqtt.go:4249 | PARTIAL | src/NATS.Server/Mqtt/MqttListener.cs:46-58 | PublishAsync delivers to subscribers; missing NATS header encoding, permission check, JetStream storage for QoS1+ |
|
||||
| Server.mqttStoreQoS2MsgOnce | mqtt.go:4295 | MISSING | | QoS2 message deduplication and store not implemented |
|
||||
| client.mqttQoS2InternalSubject | mqtt.go:4318 | MISSING | | QoS2 internal subject composition not implemented |
|
||||
| Server.mqttProcessPubRel | mqtt.go:4326 | PARTIAL | src/NATS.Server/Mqtt/MqttRetainedStore.cs:264 | ProcessPubRel exists in QoS2StateMachine but not wired to JetStream or message delivery |
|
||||
| client.mqttHandlePubRetain | mqtt.go:4366 | PARTIAL | src/NATS.Server/Mqtt/MqttRetainedStore.cs:51-62 | SetRetained handles basic store/clear but no Sparkplug B, no JetStream persistence, no network notification |
|
||||
| Server.mqttCheckPubRetainedPerms | mqtt.go:4471 | MISSING | | Retained message permission checking not implemented |
|
||||
| generatePubPerms | mqtt.go:4564 | MISSING | | Publish permission generation not implemented |
|
||||
| pubAllowed | mqtt.go:4595 | MISSING | | Publish permission checking not implemented |
|
||||
| client.mqttEnqueuePubResponse | mqtt.go:4609 | PARTIAL | src/NATS.Server/Mqtt/MqttConnection.cs:58 | Writes "PUBACK" text for QoS1; missing binary PUBACK/PUBREC/PUBREL/PUBCOMP encoding |
|
||||
| mqttParsePIPacket | mqtt.go:4641 | PARTIAL | src/NATS.Server/Mqtt/MqttBinaryDecoder.cs:316-323 | ReadUInt16BigEndian reads packet ID but no zero-check wrapper |
|
||||
| client.mqttProcessPublishReceived | mqtt.go:4656 | MISSING | | PUBACK/PUBREC processing with JS ack not implemented |
|
||||
| client.mqttProcessPubAck | mqtt.go:4691 | PARTIAL | src/NATS.Server/Mqtt/MqttConnection.cs:62-63 | AckPendingPublish exists but no JS ack, no PUBREL tracking |
|
||||
| client.mqttProcessPubRec | mqtt.go:4695 | PARTIAL | src/NATS.Server/Mqtt/MqttRetainedStore.cs:248 | ProcessPubRec exists in QoS2StateMachine but not wired to session or JetStream |
|
||||
| client.mqttProcessPubComp | mqtt.go:4700 | PARTIAL | src/NATS.Server/Mqtt/MqttRetainedStore.cs:281 | ProcessPubComp exists in QoS2StateMachine but not wired to session or JetStream |
|
||||
| mqttGetQoS | mqtt.go:4720 | PORTED | src/NATS.Server/Mqtt/MqttBinaryDecoder.cs:162 | Inline in ParsePublish |
|
||||
| mqttIsRetained | mqtt.go:4724 | PORTED | src/NATS.Server/Mqtt/MqttBinaryDecoder.cs:163 | Inline in ParsePublish |
|
||||
| sparkbParseBirthDeathTopic | mqtt.go:4728 | MISSING | | Sparkplug B birth/death topic parsing not implemented |
|
||||
|
||||
#### SUBSCRIBE/UNSUBSCRIBE Processing (lines 4760-5557)
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| client.mqttParseSubs | mqtt.go:4760 | PARTIAL | src/NATS.Server/Mqtt/MqttBinaryDecoder.cs:197-217 | ParseSubscribe exists but no flag validation, no UTF-8 validation |
|
||||
| client.mqttParseSubsOrUnsubs | mqtt.go:4764 | PARTIAL | src/NATS.Server/Mqtt/MqttBinaryDecoder.cs:197-217 | Combined parse exists only for subscribe; no unsubscribe binary parser |
|
||||
| mqttSubscribeTrace | mqtt.go:4826 | MISSING | | Subscribe trace formatting not implemented |
|
||||
| mqttDeliverMsgCbQoS0 | mqtt.go:4863 | MISSING | | QoS0 delivery callback not implemented (71 lines; core delivery logic) |
|
||||
| mqttDeliverMsgCbQoS12 | mqtt.go:4934 | MISSING | | QoS1/2 delivery callback not implemented (70+ lines; core delivery logic) |
|
||||
| mqttDeliverPubRelCb | mqtt.go:5004 | MISSING | | PUBREL delivery callback not implemented |
|
||||
| mqttMustIgnoreForReservedSub | mqtt.go:5038 | MISSING | | Reserved subject ignore check not implemented |
|
||||
| isMQTTReservedSubscription | mqtt.go:5047 | MISSING | | Reserved subscription check not implemented |
|
||||
| sparkbReplaceDeathTimestamp | mqtt.go:5058 | MISSING | | Sparkplug B death timestamp replacement not implemented |
|
||||
| client.mqttEnqueuePublishMsgTo | mqtt.go:5107 | MISSING | | MQTT PUBLISH message serialization and enqueue not implemented |
|
||||
| mqttWriter.WritePublishHeader | mqtt.go:5156 | MISSING | | PUBLISH header serialization not implemented (MqttPacketWriter has generic Write but not PUBLISH-specific) |
|
||||
| mqttMakePublishHeader | mqtt.go:5184 | MISSING | | PUBLISH header creation not implemented |
|
||||
| client.mqttProcessSubs | mqtt.go:5204 | PARTIAL | src/NATS.Server/Mqtt/MqttListener.cs:40-43 | RegisterSubscription exists but no session locking, no JS consumer, no QoS handling |
|
||||
| mqttSession.cleanupFailedSub | mqtt.go:5224 | MISSING | | Failed subscription cleanup not implemented |
|
||||
| mqttSession.ensurePubRelConsumerSubscription | mqtt.go:5238 | MISSING | | PUBREL consumer subscription setup not implemented (90 lines) |
|
||||
| mqttSession.processJSConsumer | mqtt.go:5327 | MISSING | | JS consumer creation/restoration for subscription not implemented (100+ lines) |
|
||||
| client.mqttSendRetainedMsgsToNewSubs | mqtt.go:5435 | PARTIAL | src/NATS.Server/Mqtt/MqttRetainedStore.cs:98-104 | DeliverRetainedOnSubscribe exists but not integrated into connection flow |
|
||||
| client.mqttEnqueueSubAck | mqtt.go:5449 | PARTIAL | src/NATS.Server/Mqtt/MqttConnection.cs:51 | Writes "SUBACK" text; missing binary SUBACK with per-filter QoS return codes |
|
||||
| client.mqttParseUnsubs | mqtt.go:5469 | MISSING | | Binary UNSUBSCRIBE packet parsing not implemented |
|
||||
| client.mqttProcessUnsubs | mqtt.go:5480 | MISSING | | UNSUBSCRIBE processing with JS consumer cleanup not implemented |
|
||||
| client.mqttEnqueueUnsubAck | mqtt.go:5531 | MISSING | | Binary UNSUBACK packet encoding not implemented |
|
||||
| mqttUnsubscribeTrace | mqtt.go:5541 | MISSING | | Unsubscribe trace formatting not implemented |
|
||||
|
||||
#### PING/PONG (lines 5565-5576)
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| client.mqttEnqueuePingResp | mqtt.go:5565 | MISSING | | Binary PINGRESP encoding not implemented (text protocol only has no PING support) |
|
||||
|
||||
#### Trace and Error Helpers (lines 5577-5582)
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| errOrTrace | mqtt.go:5577 | NOT_APPLICABLE | | Go-specific trace helper; .NET uses ILogger |
|
||||
|
||||
#### Subject/Topic Conversion (lines 5592-5739)
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| mqttTopicToNATSPubSubject | mqtt.go:5592 | PARTIAL | src/NATS.Server/Mqtt/MqttBinaryDecoder.cs:246-264 | TranslateFilterToNatsSubject exists but uses simple char-by-char replace; missing dot-to-double-slash, empty level handling, space rejection, leading/trailing slash handling |
|
||||
| mqttFilterToNATSSubject | mqtt.go:5598 | PARTIAL | src/NATS.Server/Mqtt/MqttBinaryDecoder.cs:246-264 | Same as above; wildcard-ok flag not separated |
|
||||
| mqttToNATSSubjectConversion | mqtt.go:5619 | PARTIAL | src/NATS.Server/Mqtt/MqttBinaryDecoder.cs:246-264 | Core conversion exists but missing edge cases: '.' to '//', leading '/' to '/.', trailing '/' to './', empty levels to './', space error |
|
||||
| natsSubjectStrToMQTTTopic | mqtt.go:5694 | MISSING | | NATS subject to MQTT topic (string variant) not implemented |
|
||||
| natsSubjectToMQTTTopic | mqtt.go:5698 | MISSING | | NATS subject to MQTT topic (byte variant) not implemented |
|
||||
| mqttNeedSubForLevelUp | mqtt.go:5730 | MISSING | | Level-up subscription check for '#' wildcard not implemented |
|
||||
|
||||
#### Reader Functions (lines 5747-5842)
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| mqttReader.reset | mqtt.go:5747 | MISSING | | Streaming reader reset with partial buffer not implemented |
|
||||
| mqttReader.hasMore | mqtt.go:5760 | NOT_APPLICABLE | | .NET uses Span-based parsing, no position tracking needed |
|
||||
| mqttReader.readByte | mqtt.go:5764 | PORTED | src/NATS.Server/Mqtt/MqttPacketReader.cs:30-31 | Buffer indexing in Read method |
|
||||
| mqttReader.readPacketLen / readPacketLenWithCheck | mqtt.go:5773-5803 | PORTED | src/NATS.Server/Mqtt/MqttPacketReader.cs:43-62 | DecodeRemainingLength implemented |
|
||||
| mqttReader.readString | mqtt.go:5805 | PORTED | src/NATS.Server/Mqtt/MqttBinaryDecoder.cs:275-289 | ReadUtf8String implemented |
|
||||
| mqttReader.readBytes | mqtt.go:5814 | PORTED | src/NATS.Server/Mqtt/MqttBinaryDecoder.cs:296-310 | ReadBinaryField implemented |
|
||||
| mqttReader.readUint16 | mqtt.go:5835 | PORTED | src/NATS.Server/Mqtt/MqttBinaryDecoder.cs:316-323 | ReadUInt16BigEndian implemented |
|
||||
|
||||
#### Writer Functions (lines 5850-5882)
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| mqttWriter.WriteUint16 | mqtt.go:5850 | PARTIAL | src/NATS.Server/Mqtt/MqttPacketWriter.cs:12-14 | Inline in Write method, not a standalone helper |
|
||||
| mqttWriter.WriteString | mqtt.go:5855 | MISSING | | Standalone string write (length-prefixed) not implemented |
|
||||
| mqttWriter.WriteBytes | mqtt.go:5859 | MISSING | | Standalone bytes write (length-prefixed) not implemented |
|
||||
| mqttWriter.WriteVarInt | mqtt.go:5864 | PORTED | src/NATS.Server/Mqtt/MqttPacketWriter.cs:19-37 | EncodeRemainingLength implemented |
|
||||
| newMQTTWriter | mqtt.go:5878 | PARTIAL | src/NATS.Server/Mqtt/MqttPacketWriter.cs:3 | Static class, no constructor needed; Write method serves the purpose |
|
||||
|
||||
---
|
||||
|
||||
## 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/Mqtt/ -name '*.cs' -type f -exec cat {} + | wc -l
|
||||
# Re-count .NET test LOC for this module
|
||||
find tests/NATS.Server.Tests/Mqtt/ -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')"
|
||||
```
|
||||
|
||||
## Summary Counts
|
||||
|
||||
| Status | Count |
|
||||
|--------|-------|
|
||||
| PORTED | 14 |
|
||||
| PARTIAL | 57 |
|
||||
| MISSING | 119 |
|
||||
| NOT_APPLICABLE | 5 |
|
||||
| DEFERRED | 0 |
|
||||
| **Total** | **195** |
|
||||
|
||||
## Change Log
|
||||
|
||||
| Date | Change | By |
|
||||
|------|--------|----|
|
||||
| 2026-02-25 | Full gap analysis completed: 195 items analyzed. 14 PORTED, 57 PARTIAL, 119 MISSING, 5 NOT_APPLICABLE. Major gaps: JetStream integration (entire mqttJSA layer ~30 functions), binary protocol encoding (CONNACK/SUBACK/UNSUBACK/PUBLISH serialization), delivery callbacks (QoS0/QoS1/QoS2), account session management, retained message encoding/decoding, Sparkplug B support, NATS subject reverse mapping. .NET has solid foundation for packet reading/writing, connect parsing, basic pub/sub flow, QoS tracking, and retained store, but all are simplified/in-memory-only without JetStream backing. | claude |
|
||||
| 2026-02-25 | File created with LLM analysis instructions | auto |
|
||||
285
gaps/protocol.md
Normal file
285
gaps/protocol.md
Normal file
@@ -0,0 +1,285 @@
|
||||
# Protocol — Gap Analysis
|
||||
|
||||
> This file tracks what has and hasn't been ported from Go to .NET for the **Protocol** 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 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
|
||||
|
||||
<!-- After analysis, fill in this table. Group rows by Go source file. -->
|
||||
|
||||
### 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_START` … `INFO_ARG` (parser state constants) | parser.go:57–134 | 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:27–34 | 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`:
|
||||
```bash
|
||||
# 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:
|
||||
```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')"
|
||||
```
|
||||
|
||||
## 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.cs` — `System.IO.Pipelines` handles split buffers transparently; no explicit split-state test |
|
||||
| `TestSplitBufferUnsubOp` | PARTIAL | Same as above |
|
||||
| `TestSplitBufferPubOp` … `TestSplitBufferPubOp5` | 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 |
|
||||
389
gaps/raft.md
Normal file
389
gaps/raft.md
Normal file
@@ -0,0 +1,389 @@
|
||||
# RAFT — Gap Analysis
|
||||
|
||||
> This file tracks what has and hasn't been ported from Go to .NET for the **RAFT** 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 RAFT
|
||||
|
||||
- RAFT is used for JetStream cluster consensus — both meta-cluster (stream/consumer placement) and per-stream/consumer groups.
|
||||
- Key operations: leader election, log append/commit, snapshotting, peer management.
|
||||
- The .NET implementation (20 files, 3,045 LOC) is more granular than Go's single file. Check if the decomposition covers all RAFT states.
|
||||
|
||||
---
|
||||
|
||||
## Go Reference Files (Source)
|
||||
|
||||
- `golang/nats-server/server/raft.go` — RAFT consensus for clustered JetStream (~5,037 lines). Meta-cluster for metadata, per-stream/consumer RAFT groups. Leader election, log replication, snapshotting.
|
||||
|
||||
## Go Reference Files (Tests)
|
||||
|
||||
- `golang/nats-server/server/raft_test.go`
|
||||
- `golang/nats-server/server/raft_helpers_test.go`
|
||||
- `golang/nats-server/server/raft_chain_of_blocks_helpers_test.go`
|
||||
|
||||
## .NET Implementation Files (Source)
|
||||
|
||||
- `src/NATS.Server/Raft/` (all 20 files)
|
||||
|
||||
## .NET Implementation Files (Tests)
|
||||
|
||||
- `tests/NATS.Server.Tests/Raft/`
|
||||
|
||||
---
|
||||
|
||||
## Gap Inventory
|
||||
|
||||
<!-- After analysis, fill in this table. Group rows by Go source file. -->
|
||||
|
||||
### golang/nats-server/server/raft.go — Exported Interfaces & Types
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| RaftNode (interface) | raft.go:40-92 | PARTIAL | src/NATS.Server/Raft/IRaftNode.cs:5 | Interface declared but empty — none of the 40+ methods from Go are defined |
|
||||
| RaftNodeCheckpoint (interface) | raft.go:98-103 | PARTIAL | src/NATS.Server/Raft/RaftSnapshotCheckpoint.cs:7 | Chunk assembly exists but LoadLastSnapshot, AppendEntriesSeq, Abort, InstallSnapshot not matching Go's interface contract |
|
||||
| WAL (interface) | raft.go:105-118 | PARTIAL | src/NATS.Server/Raft/RaftWal.cs:20 | .NET RaftWal is a concrete file-based WAL; does not implement Go's WAL interface (StoreMsg, LoadMsg, RemoveMsg, Compact, Purge, Truncate, State, FastState, Stop, Delete) |
|
||||
| Peer (struct) | raft.go:120-125 | PARTIAL | src/NATS.Server/Raft/RaftPeerState.cs:7 | .NET has NextIndex/MatchIndex/LastContact/Active but missing Lag and Current fields from Go's Peer export |
|
||||
| RaftState (enum) | raft.go:127-135 | PORTED | src/NATS.Server/Raft/RaftState.cs:4 | All four states: Follower, Leader, Candidate, Closed |
|
||||
| RaftState.String() | raft.go:137-149 | MISSING | — | No .NET ToString override for RaftState enum |
|
||||
| RaftConfig (struct) | raft.go:301-317 | MISSING | — | No equivalent configuration struct (Name, Store, Log, Track, Observer, Recovering, ScaleUp) |
|
||||
| CommittedEntry (struct) | raft.go:2506-2509 | PARTIAL | src/NATS.Server/Raft/CommitQueue.cs:9 | CommitQueue<T> exists as channel wrapper, but no CommittedEntry struct with Index+Entries |
|
||||
| Entry (struct) | raft.go:2641-2644 | PARTIAL | src/NATS.Server/Raft/RaftWireFormat.cs:73 | RaftEntryWire has Type+Data but is wire-format only; no general Entry type used for proposals |
|
||||
| EntryType (enum) | raft.go:2605-2619 | PORTED | src/NATS.Server/Raft/RaftWireFormat.cs:54-63 | All types present including EntryCatchup (mapped as RaftEntryType) |
|
||||
|
||||
### golang/nats-server/server/raft.go — Exported RaftNode Interface Methods
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| Propose() | raft.go:909-924 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:308 | ProposeAsync exists but synchronous replication model, no write-error checking, no proposal queue |
|
||||
| ProposeMulti() | raft.go:928-945 | MISSING | — | No batch proposal support |
|
||||
| ForwardProposal() | raft.go:949-959 | PARTIAL | src/NATS.Server/Raft/NatsRaftTransport.cs:182 | Transport has ForwardProposal but RaftNode does not call it automatically for non-leaders |
|
||||
| InstallSnapshot() | raft.go:1295-1311 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:699 | InstallSnapshotAsync exists but no checkpointing, no WAL compaction, no highwayhash verification |
|
||||
| CreateSnapshotCheckpoint() | raft.go:1356-1360 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:519 | CreateSnapshotCheckpointAsync exists but simplified — no async write, no WAL compaction tracking |
|
||||
| SendSnapshot() | raft.go:1284-1290 | MISSING | — | No direct snapshot send as append entry |
|
||||
| NeedSnapshot() | raft.go:1551-1555 | MISSING | — | No equivalent check |
|
||||
| Applied() | raft.go:1183-1185 | MISSING | — | No callback from upper layer for applied index tracking (delegates to Processed) |
|
||||
| Processed() | raft.go:1193-1240 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:664 | MarkProcessed exists but much simpler — no aflr signaling, no leader state transition, no byte estimation |
|
||||
| State() | raft.go:2025-2027 | PORTED | src/NATS.Server/Raft/RaftNode.cs:43 | Role property (uses RaftRole enum instead of RaftState) |
|
||||
| Size() | raft.go:2037-2043 | MISSING | — | No WAL size reporting |
|
||||
| Progress() | raft.go:2030-2034 | MISSING | — | No combined (index, commit, applied) return |
|
||||
| Leader() | raft.go:1712-1717 | PORTED | src/NATS.Server/Raft/RaftNode.cs:42 | IsLeader property |
|
||||
| LeaderSince() | raft.go:1721-1726 | MISSING | — | No leader-since timestamp tracking |
|
||||
| Quorum() | raft.go:3070-3083 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:201 | HasQuorum() exists but uses different window calculation (2x electionTimeout vs Go's lostQuorumInterval) |
|
||||
| Current() | raft.go:1840-1847 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:879 | IsCurrent exists but no commit==applied check, no forward-progress polling |
|
||||
| Healthy() | raft.go:1850-1857 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:892 | IsHealthy exists but different semantics — checks peer responsiveness, not isCurrent(true) |
|
||||
| Term() | raft.go:3119-3123 | PORTED | src/NATS.Server/Raft/RaftNode.cs:41 | Term property |
|
||||
| Leaderless() | raft.go:1876-1883 | MISSING | — | No atomic hasleader flag |
|
||||
| GroupLeader() | raft.go:1865-1872 | MISSING | — | No leader ID tracking (only IsLeader bool) |
|
||||
| HadPreviousLeader() | raft.go:1860-1862 | MISSING | — | No pleader atomic flag |
|
||||
| StepDown() | raft.go:1900-1977 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:706 | RequestStepDown exists but no preferred leader selection, no leader transfer, no EntryLeaderTransfer |
|
||||
| SetObserver() | raft.go:2394-2396 | MISSING | — | No observer mode |
|
||||
| IsObserver() | raft.go:2387-2391 | MISSING | — | No observer mode |
|
||||
| Campaign() | raft.go:1980-1984 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:771 | CampaignImmediately exists but no random campaign timeout |
|
||||
| CampaignImmediately() | raft.go:1987-1993 | PORTED | src/NATS.Server/Raft/RaftNode.cs:771 | CampaignImmediately() |
|
||||
| ID() | raft.go:2045-2051 | PORTED | src/NATS.Server/Raft/RaftNode.cs:40 | Id property |
|
||||
| Group() | raft.go:2053-2056 | MISSING | — | No group name tracking |
|
||||
| Peers() | raft.go:2058-2077 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:872 | GetPeerStates returns Dict but missing Lag calculation |
|
||||
| ProposeKnownPeers() | raft.go:2080-2089 | MISSING | — | No peer state broadcast |
|
||||
| UpdateKnownPeers() | raft.go:2092-2096 | MISSING | — | No peer state update |
|
||||
| ProposeAddPeer() | raft.go:962-983 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:372 | ProposeAddPeerAsync exists but synchronous replication, no forwarding to leader |
|
||||
| ProposeRemovePeer() | raft.go:986-1019 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:415 | ProposeRemovePeerAsync exists but no forwarding, no self-removal handling |
|
||||
| MembershipChangeInProgress() | raft.go:1021-1025 | PORTED | src/NATS.Server/Raft/RaftNode.cs:67 | MembershipChangeInProgress property |
|
||||
| AdjustClusterSize() | raft.go:1059-1079 | MISSING | — | No cluster size adjustment |
|
||||
| AdjustBootClusterSize() | raft.go:1038-1055 | MISSING | — | No boot cluster size adjustment |
|
||||
| ClusterSize() | raft.go:1029-1033 | MISSING | — | No explicit cluster size property |
|
||||
| ApplyQ() | raft.go:2106 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:53 | CommitQueue exists as Channel-based queue, different API than ipQueue |
|
||||
| PauseApply() | raft.go:1084-1092 | MISSING | — | No apply pausing |
|
||||
| ResumeApply() | raft.go:1111-1156 | MISSING | — | No apply resuming with replay |
|
||||
| DrainAndReplaySnapshot() | raft.go:1162-1177 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:537 | DrainAndReplaySnapshotAsync exists but simplified — no catchup cancellation, no commit preservation |
|
||||
| LeadChangeC() | raft.go:2110 | MISSING | — | No leader change channel |
|
||||
| QuitC() | raft.go:2113 | MISSING | — | No quit channel |
|
||||
| Created() | raft.go:2115-2118 | MISSING | — | No creation timestamp |
|
||||
| Stop() | raft.go:2120-2122 | MISSING | — | No graceful shutdown (Dispose exists but minimal) |
|
||||
| WaitForStop() | raft.go:2124-2128 | MISSING | — | No wait-for-stop mechanism |
|
||||
| Delete() | raft.go:2130-2143 | MISSING | — | No delete with WAL cleanup |
|
||||
| IsDeleted() | raft.go:2145-2149 | MISSING | — | No deleted flag |
|
||||
| RecreateInternalSubs() | raft.go:658-747 | MISSING | — | No NATS internal subscription management |
|
||||
| IsSystemAccount() | raft.go:648-649 | NOT_APPLICABLE | — | .NET does not have system account NRG routing |
|
||||
| GetTrafficAccountName() | raft.go:652-656 | NOT_APPLICABLE | — | .NET does not have account NRG routing |
|
||||
|
||||
### golang/nats-server/server/raft.go — Core State Machine (unexported but critical)
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| raft (struct) | raft.go:151-251 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:3 | RaftNode has basic fields (term, vote, log, peers, commit) but missing: WAL, catchup state, progress map, pae cache, ipQueues, removed peers, many flags |
|
||||
| run() | raft.go:2275-2362 | MISSING | — | No main run loop / state machine goroutine |
|
||||
| runAsFollower() | raft.go:2441-2496 | MISSING | — | No follower state machine with select on channels |
|
||||
| runAsCandidate() | raft.go:3587-3670 | MISSING | — | No candidate state machine with vote collection |
|
||||
| runAsLeader() | raft.go:2951-3067 | MISSING | — | No leader state machine with heartbeat ticker and proposal batching |
|
||||
| processAppendEntry() | raft.go:3857-4248 | MISSING | — | Complex append entry processing with catchup, WAL alignment, truncation — not ported |
|
||||
| processAppendEntryResponse() | raft.go:4287-4339 | MISSING | — | No response processing with catchup triggering |
|
||||
| processVoteRequest() | raft.go:4780-4845 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:142 | GrantVote handles basic vote logic but missing: log up-to-dateness check (lastTerm/lastIndex), catchup cancellation, campaign timeout reset |
|
||||
| requestVote() | raft.go:4856-4872 | MISSING | — | No vote request broadcast via RPC |
|
||||
| handleAppendEntry() | raft.go:3672-3684 | MISSING | — | No wire callback for append entries |
|
||||
| handleAppendEntryResponse() | raft.go:4342-4346 | MISSING | — | No wire callback for AE responses |
|
||||
| handleVoteRequest() | raft.go:4847-4854 | MISSING | — | No wire callback for vote requests |
|
||||
| handleVoteResponse() | raft.go:4764-4778 | MISSING | — | No wire callback for vote responses |
|
||||
| handleForwardedProposal() | raft.go:2854-2874 | MISSING | — | No forwarded proposal handler |
|
||||
| handleForwardedRemovePeerProposal() | raft.go:2820-2851 | MISSING | — | No forwarded remove-peer handler |
|
||||
|
||||
### golang/nats-server/server/raft.go — WAL & Storage
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| storeToWAL() | raft.go:4360-4396 | PARTIAL | src/NATS.Server/Raft/RaftWal.cs:74 | RaftWal.AppendAsync exists but different storage model (custom binary vs Go's filestore/memstore) |
|
||||
| loadEntry() | raft.go:3339-3346 | MISSING | — | No entry loading from WAL by index |
|
||||
| loadFirstEntry() | raft.go:3126-3130 | MISSING | — | No first-entry loading |
|
||||
| truncateWAL() | raft.go:3758-3815 | MISSING | — | No WAL truncation with snapshot invalidation |
|
||||
| resetWAL() | raft.go:3819-3821 | MISSING | — | No WAL reset |
|
||||
| cachePendingEntry() | raft.go:4449-4460 | MISSING | — | No pending append entry cache |
|
||||
|
||||
### golang/nats-server/server/raft.go — Snapshots
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| snapshot (struct) | raft.go:1243-1248 | PARTIAL | src/NATS.Server/Raft/RaftSnapshot.cs:3 | RaftSnapshot has LastIncludedIndex/Term/Data but missing peerstate encoding |
|
||||
| encodeSnapshot() | raft.go:1254-1279 | MISSING | — | No binary snapshot encoding with highwayhash |
|
||||
| loadLastSnapshot() | raft.go:1660-1707 | MISSING | — | No binary snapshot loading with hash verification |
|
||||
| setupLastSnapshot() | raft.go:1578-1656 | MISSING | — | No startup snapshot recovery from disk |
|
||||
| installSnapshot() | raft.go:1315-1350 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:699 | InstallSnapshotAsync simplified — no WAL compact, no snapshot file management |
|
||||
| termAndIndexFromSnapFile() | raft.go:1564-1573 | MISSING | — | No snapshot filename parsing |
|
||||
|
||||
### golang/nats-server/server/raft.go — Peer & Membership State
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| peerState (struct) | raft.go:4470-4474 | MISSING | — | No peer state struct with knownPeers/clusterSize/domainExt |
|
||||
| encodePeerState() | raft.go:4480-4492 | MISSING | — | No binary peer state encoding |
|
||||
| decodePeerState() | raft.go:4494-4514 | MISSING | — | No binary peer state decoding |
|
||||
| writePeerState() | raft.go:4603-4609 | MISSING | — | No peer state file persistence |
|
||||
| readPeerState() | raft.go:4611-4620 | MISSING | — | No peer state file reading |
|
||||
| processPeerState() | raft.go:4264-4282 | MISSING | — | No peer state processing from leader |
|
||||
| addPeer() | raft.go:2879-2898 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:129 | AddMember exists but no cluster size/quorum adjustment, no removed-list reversal |
|
||||
| removePeer() | raft.go:2903-2913 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:131 | RemoveMember exists but no removed tracking, no cluster size/quorum adjustment |
|
||||
| adjustClusterSizeAndQuorum() | raft.go:3534-3556 | MISSING | — | No dynamic cluster size/quorum recalculation |
|
||||
| trackPeer() | raft.go:3559-3585 | MISSING | — | No automatic peer tracking with add-on-discovery |
|
||||
|
||||
### golang/nats-server/server/raft.go — Elections & Voting
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| switchToFollower() | raft.go:4958-4983 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:706 | RequestStepDown sets Follower but missing: aflr reset, leaderState/leaderSince clear, acks reset, leader update |
|
||||
| switchToCandidate() | raft.go:4985-5014 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:133 | StartElection increments term and votes but missing: observer/paused/processed checks, quorum loss signaling |
|
||||
| switchToLeader() | raft.go:5016-5037 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:986 | TryBecomeLeader sets Role but missing: aflr setup, peer state broadcast, leader update |
|
||||
| campaign() | raft.go:2002-2009 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:971 | CampaignWithPreVote starts election but no random timeout |
|
||||
| wonElection() | raft.go:4886-4888 | PORTED | src/NATS.Server/Raft/RaftNode.cs:986 | Quorum check in TryBecomeLeader |
|
||||
| selectNextLeader() | raft.go:1887-1897 | MISSING | — | No next-leader selection by highest index |
|
||||
| resetElectionTimeout() | raft.go:2241-2243 | PORTED | src/NATS.Server/Raft/RaftNode.cs:737 | ResetElectionTimeout with Timer |
|
||||
| randElectionTimeout() | raft.go:2235-2238 | PORTED | src/NATS.Server/Raft/RaftNode.cs:727 | RandomizedElectionTimeout |
|
||||
| randCampaignTimeout() | raft.go:1995-1998 | MISSING | — | No separate campaign timeout |
|
||||
|
||||
### golang/nats-server/server/raft.go — Wire Format & RPC
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| voteRequest (struct) | raft.go:4549-4556 | PORTED | src/NATS.Server/Raft/RaftWireFormat.cs:84 | RaftVoteRequestWire with Encode/Decode |
|
||||
| voteResponse (struct) | raft.go:4730-4735 | PORTED | src/NATS.Server/Raft/RaftWireFormat.cs:132 | RaftVoteResponseWire with Encode/Decode |
|
||||
| appendEntry (struct) | raft.go:2557-2569 | PORTED | src/NATS.Server/Raft/RaftWireFormat.cs:189 | RaftAppendEntryWire with Encode/Decode |
|
||||
| appendEntryResponse (struct) | raft.go:2760-2766 | PORTED | src/NATS.Server/Raft/RaftWireFormat.cs:318 | RaftAppendEntryResponseWire with Encode/Decode |
|
||||
| ae.encode() | raft.go:2662-2711 | PORTED | src/NATS.Server/Raft/RaftWireFormat.cs:202 | RaftAppendEntryWire.Encode() |
|
||||
| decodeAppendEntry() | raft.go:2714-2746 | PORTED | src/NATS.Server/Raft/RaftWireFormat.cs:255 | RaftAppendEntryWire.Decode() |
|
||||
| vr.encode() (voteRequest) | raft.go:4560-4568 | PORTED | src/NATS.Server/Raft/RaftWireFormat.cs:94 | RaftVoteRequestWire.Encode() |
|
||||
| decodeVoteRequest() | raft.go:4571-4583 | PORTED | src/NATS.Server/Raft/RaftWireFormat.cs:109 | RaftVoteRequestWire.Decode() |
|
||||
| vr.encode() (voteResponse) | raft.go:4739-4751 | PORTED | src/NATS.Server/Raft/RaftWireFormat.cs:142 | RaftVoteResponseWire.Encode() |
|
||||
| decodeVoteResponse() | raft.go:4753-4762 | PORTED | src/NATS.Server/Raft/RaftWireFormat.cs:159 | RaftVoteResponseWire.Decode() |
|
||||
| ar.encode() | raft.go:2777-2794 | PORTED | src/NATS.Server/Raft/RaftWireFormat.cs:328 | RaftAppendEntryResponseWire.Encode() |
|
||||
| decodeAppendEntryResponse() | raft.go:2799-2817 | PORTED | src/NATS.Server/Raft/RaftWireFormat.cs:343 | RaftAppendEntryResponseWire.Decode() |
|
||||
| idLen constant | raft.go:2756 | PORTED | src/NATS.Server/Raft/RaftWireFormat.cs:23 | RaftWireConstants.IdLen = 8 |
|
||||
| RaftWireHelpers (WriteId/ReadId) | raft.go:2693,4581 | PORTED | src/NATS.Server/Raft/RaftWireFormat.cs:561 | RaftWireHelpers class |
|
||||
| RaftWireHelpers (uvarint) | raft.go:2682,2740 | PORTED | src/NATS.Server/Raft/RaftWireFormat.cs:597 | WriteUvarint/ReadUvarint |
|
||||
|
||||
### golang/nats-server/server/raft.go — NATS Subjects & Transport
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| raftAllSubj constant | raft.go:2162 | PORTED | src/NATS.Server/Raft/RaftSubjects.cs:16 | RaftSubjects.All |
|
||||
| raftVoteSubj constant | raft.go:2163 | PORTED | src/NATS.Server/Raft/RaftSubjects.cs:22 | RaftSubjects.Vote() |
|
||||
| raftAppendSubj constant | raft.go:2164 | PORTED | src/NATS.Server/Raft/RaftSubjects.cs:28 | RaftSubjects.AppendEntry() |
|
||||
| raftPropSubj constant | raft.go:2165 | PORTED | src/NATS.Server/Raft/RaftSubjects.cs:34 | RaftSubjects.Proposal() |
|
||||
| raftRemovePeerSubj constant | raft.go:2166 | PORTED | src/NATS.Server/Raft/RaftSubjects.cs:40 | RaftSubjects.RemovePeer() |
|
||||
| raftReply constant | raft.go:2167 | PORTED | src/NATS.Server/Raft/RaftSubjects.cs:46 | RaftSubjects.Reply() |
|
||||
| raftCatchupReply constant | raft.go:2168 | PORTED | src/NATS.Server/Raft/RaftSubjects.cs:52 | RaftSubjects.CatchupReply() |
|
||||
| sendRPC() | raft.go:4874-4878 | PARTIAL | src/NATS.Server/Raft/NatsRaftTransport.cs:29 | NatsRaftTransport._publish delegate, but no sendq integration |
|
||||
| sendReply() | raft.go:4880-4884 | MISSING | — | No reply sending via sendq |
|
||||
| sendHeartbeat() | raft.go:4545-4547 | PARTIAL | src/NATS.Server/Raft/NatsRaftTransport.cs:229 | SendHeartbeatAsync exists but optimistic (no real ACK tracking) |
|
||||
| IRaftTransport | — | PORTED | src/NATS.Server/Raft/RaftTransport.cs:3 | Interface with AppendEntries, RequestVote, InstallSnapshot, SendTimeoutNow, SendHeartbeat |
|
||||
| InMemoryRaftTransport | — | PORTED | src/NATS.Server/Raft/RaftTransport.cs:26 | Full in-memory transport for testing |
|
||||
| NatsRaftTransport | — | PORTED | src/NATS.Server/Raft/NatsRaftTransport.cs:18 | Wire-level NATS transport with binary encoding |
|
||||
| createInternalSubs() | raft.go:2209-2233 | MISSING | — | No internal NATS subscription creation |
|
||||
| subscribe() / unsubscribe() | raft.go:2194-2206 | MISSING | — | No internal subscription management |
|
||||
|
||||
### golang/nats-server/server/raft.go — Catchup & Leader Coordination
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| catchupState (struct) | raft.go:260-268 | MISSING | — | No catchup state tracking |
|
||||
| runCatchup() | raft.go:3132-3236 | MISSING | — | No catchup goroutine for lagging followers |
|
||||
| catchupFollower() | raft.go:3266-3337 | MISSING | — | No follower catchup initiation |
|
||||
| createCatchup() | raft.go:3718-3735 | MISSING | — | No catchup subscription/inbox creation |
|
||||
| cancelCatchup() | raft.go:3689-3697 | MISSING | — | No catchup cancellation |
|
||||
| catchupStalled() | raft.go:3702-3712 | MISSING | — | No stall detection |
|
||||
| sendSnapshotToFollower() | raft.go:3239-3264 | MISSING | — | No snapshot streaming to follower |
|
||||
| sendCatchupSignal() | raft.go:3738-3745 | MISSING | — | No catchup signal to upper layer |
|
||||
| cancelCatchupSignal() | raft.go:3748-3754 | MISSING | — | No catchup signal cancellation |
|
||||
|
||||
### golang/nats-server/server/raft.go — Commit & Apply
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| applyCommit() | raft.go:3350-3462 | MISSING | — | Complex commit application with entry type dispatch, peer state processing, membership changes |
|
||||
| tryCommit() | raft.go:3468-3487 | MISSING | — | No quorum-based commit with ack counting |
|
||||
| trackResponse() | raft.go:3493-3529 | MISSING | — | No response tracking with ack map |
|
||||
| sendMembershipChange() | raft.go:2918-2949 | MISSING | — | No membership change send with WAL store |
|
||||
|
||||
### golang/nats-server/server/raft.go — Persistence & Term/Vote
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| writeTermVote() | raft.go:4708-4727 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:993 | PersistAsync writes meta.json (JSON) but Go uses binary tav.idx with dedup |
|
||||
| readTermVote() | raft.go:4637-4656 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:1013 | LoadPersistedStateAsync reads meta.json but Go uses binary tav.idx |
|
||||
| writePeerState() (on raft) | raft.go:4589-4600 | MISSING | — | No binary peer state file writing |
|
||||
| setWriteErr() / setWriteErrLocked() | raft.go:4659-4704 | MISSING | — | No write error tracking with permission/space error escalation |
|
||||
|
||||
### golang/nats-server/server/raft.go — Server Integration
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| bootstrapRaftNode() | raft.go:346-407 | MISSING | — | No raft bootstrap with peer validation and directory setup |
|
||||
| initRaftNode() | raft.go:410-614 | MISSING | — | No full raft initialization with WAL replay, snapshot recovery, peer state restore |
|
||||
| startRaftNode() | raft.go:617-628 | MISSING | — | No start-and-run goroutine |
|
||||
| registerRaftNode() | raft.go:776-783 | MISSING | — | No server-level raft registration |
|
||||
| unregisterRaftNode() | raft.go:786-792 | MISSING | — | No server-level raft unregistration |
|
||||
| lookupRaftNode() | raft.go:803-811 | MISSING | — | No raft node lookup by group |
|
||||
| numRaftNodes() | raft.go:795-799 | MISSING | — | No raft node count |
|
||||
| stepdownRaftNodes() | raft.go:831-851 | MISSING | — | No server-wide raft stepdown |
|
||||
| shutdownRaftNodes() | raft.go:856-875 | MISSING | — | No server-wide raft shutdown |
|
||||
| transferRaftLeaders() | raft.go:880-903 | MISSING | — | No server-wide leader transfer |
|
||||
| reloadDebugRaftNodes() | raft.go:815-827 | NOT_APPLICABLE | — | Debug flag reload is Go-specific |
|
||||
| serverNameForNode() | raft.go:759-763 | NOT_APPLICABLE | — | Server-level node mapping not yet needed |
|
||||
| clusterNameForNode() | raft.go:767-772 | NOT_APPLICABLE | — | Cluster-level node mapping not yet needed |
|
||||
|
||||
### golang/nats-server/server/raft.go — .NET Extensions (not in Go)
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| (none — .NET addition) | — | PORTED | src/NATS.Server/Raft/RaftWireFormat.cs:366 | RaftPreVoteRequestWire — pre-vote wire format (Go does not have pre-vote) |
|
||||
| (none — .NET addition) | — | PORTED | src/NATS.Server/Raft/RaftWireFormat.cs:410 | RaftPreVoteResponseWire — pre-vote wire format |
|
||||
| (none — .NET addition) | — | PORTED | src/NATS.Server/Raft/RaftWireFormat.cs:455 | RaftTimeoutNowWire — leadership transfer wire format |
|
||||
| (none — .NET addition) | — | PORTED | src/NATS.Server/Raft/RaftWireFormat.cs:508 | RaftInstallSnapshotChunkWire — chunked snapshot wire format |
|
||||
| (none — .NET addition) | — | PORTED | src/NATS.Server/Raft/CompactionPolicy.cs:9 | CompactionPolicy / CompactionOptions — configurable log compaction |
|
||||
| (none — .NET addition) | — | PORTED | src/NATS.Server/Raft/SnapshotChunkEnumerator.cs:16 | SnapshotChunkEnumerator — snapshot chunking |
|
||||
| (none — .NET addition) | — | PORTED | src/NATS.Server/Raft/RaftNode.cs:251 | ReadIndexAsync — linearizable reads (Go does not expose this) |
|
||||
| (none — .NET addition) | — | PORTED | src/NATS.Server/Raft/RaftNode.cs:464 | Joint consensus (BeginJointConsensus/CommitJointConsensus) |
|
||||
| (none — .NET addition) | — | PORTED | src/NATS.Server/Raft/RaftNode.cs:790 | TransferLeadershipAsync with TimeoutNow RPC |
|
||||
| (none — .NET addition) | — | PORTED | src/NATS.Server/Raft/RaftNode.cs:906 | Pre-vote protocol (StartPreVote/RequestPreVote) |
|
||||
|
||||
### golang/nats-server/server/raft.go — Object Pools & Helpers
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| cePool / newCommittedEntry / ReturnToPool | raft.go:2498-2532 | NOT_APPLICABLE | — | Go sync.Pool for CommittedEntry; .NET uses GC |
|
||||
| entryPool / newEntry | raft.go:2534-2547 | NOT_APPLICABLE | — | Go sync.Pool for Entry; .NET uses GC |
|
||||
| aePool / newAppendEntry / returnToPool | raft.go:2549-2583 | NOT_APPLICABLE | — | Go sync.Pool for appendEntry; .NET uses GC |
|
||||
| pePool / newProposedEntry | raft.go:2586-2603 | NOT_APPLICABLE | — | Go sync.Pool for proposedEntry; .NET uses GC |
|
||||
| arPool / newAppendEntryResponse | raft.go:2748-2775 | NOT_APPLICABLE | — | Go sync.Pool for appendEntryResponse; .NET uses GC |
|
||||
| peers sync.Map (string interning) | raft.go:2797 | NOT_APPLICABLE | — | Go-specific string interning optimization |
|
||||
| debug() / warn() / error() | raft.go:2364-2379 | NOT_APPLICABLE | — | Logging helpers — .NET uses ILogger<T> |
|
||||
| dios semaphore | raft.go:1665-1667 | NOT_APPLICABLE | — | Go disk I/O semaphore — .NET uses async I/O |
|
||||
| ipQueue usage | raft.go:233-238 | MISSING | — | Go uses typed ipQueue channels for proposals, entries, votes, responses — .NET has no equivalent internal queue infrastructure |
|
||||
|
||||
### golang/nats-server/server/raft.go — Constants & Configuration
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| Election timeout defaults | raft.go:277-287 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:56-57 | .NET uses 150-300ms defaults; Go uses 4-9s defaults. Different design choice |
|
||||
| hbInterval | raft.go:283 | MISSING | — | No heartbeat interval constant |
|
||||
| lostQuorumInterval | raft.go:284 | MISSING | — | No lost quorum interval |
|
||||
| observerModeInterval | raft.go:286 | MISSING | — | No observer mode interval |
|
||||
| peerRemoveTimeout | raft.go:287 | MISSING | — | No peer remove timeout |
|
||||
| Error sentinels | raft.go:319-343 | PARTIAL | — | .NET uses InvalidOperationException instead of typed error sentinels |
|
||||
| noLeader / noVote constants | raft.go:4954-4956 | MISSING | — | No explicit no-leader/no-vote constants |
|
||||
| paeDropThreshold / paeWarnThreshold | raft.go:4399-4401 | MISSING | — | No pending append entry limits |
|
||||
| maxBatch / maxEntries | raft.go:3004-3005 | MISSING | — | No proposal batching thresholds |
|
||||
| extensionState | raft.go:4462-4468 | MISSING | — | No domain extension state tracking |
|
||||
|
||||
---
|
||||
|
||||
## 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/Raft/ -name '*.cs' -type f -exec cat {} + | wc -l
|
||||
# Re-count .NET test LOC for this module
|
||||
find tests/NATS.Server.Tests/Raft/ -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-25 | File created with LLM analysis instructions | auto |
|
||||
| 2026-02-25 | Full gap analysis completed: 196 items inventoried across 12 categories. Summary: 46 PORTED, 38 PARTIAL, 99 MISSING, 13 NOT_APPLICABLE, 0 DEFERRED. Wire format is well-ported; core state machine (run loop, catchup, WAL integration) is largely missing. | claude-opus |
|
||||
186
gaps/routes.md
Normal file
186
gaps/routes.md
Normal file
@@ -0,0 +1,186 @@
|
||||
# Routes — Gap Analysis
|
||||
|
||||
> This file tracks what has and hasn't been ported from Go to .NET for the **Routes** 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 Routes
|
||||
|
||||
- Route connections use the same `client.go` read/write loop but with `ClientKind = ROUTER`.
|
||||
- Route pooling sends different accounts over different connections to avoid head-of-line blocking.
|
||||
- `RS+`/`RS-` are subscription interest propagation messages between clustered servers.
|
||||
- `RMSG` is the routed message format (differs from client `MSG`).
|
||||
|
||||
---
|
||||
|
||||
## Go Reference Files (Source)
|
||||
|
||||
- `golang/nats-server/server/route.go` — Full-mesh cluster routes (~3,300 lines). Route pooling (default 3 connections per peer). Account-specific dedicated routes. Protocol: `RS+`/`RS-` for subscribe propagation, `RMSG` for routed messages.
|
||||
|
||||
## Go Reference Files (Tests)
|
||||
|
||||
- `golang/nats-server/server/routes_test.go`
|
||||
- `golang/nats-server/test/routes_test.go` (integration)
|
||||
- `golang/nats-server/test/new_routes_test.go` (integration)
|
||||
|
||||
## .NET Implementation Files (Source)
|
||||
|
||||
- `src/NATS.Server/Routes/` (all files)
|
||||
|
||||
## .NET Implementation Files (Tests)
|
||||
|
||||
- `tests/NATS.Server.Tests/Routes/`
|
||||
|
||||
---
|
||||
|
||||
## Gap Inventory
|
||||
|
||||
<!-- After analysis, fill in this table. Group rows by Go source file. -->
|
||||
|
||||
### `golang/nats-server/server/route.go`
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `RouteType` (type alias + consts `Implicit`/`Explicit`) | route.go:36–44 | PORTED | `src/NATS.Server/Routes/RouteConnection.cs` — implicit/explicit distinction tracked in `RouteConnection` handshake and `RouteManager.ConnectToRouteWithRetryAsync` | No explicit enum; Implicit/Explicit distinction is encoded in how routes are established (solicited vs inbound) |
|
||||
| `route` struct (unexported) | route.go:56–94 | PARTIAL | `src/NATS.Server/Routes/RouteConnection.cs:8` | Fields `remoteID`, `poolIdx`, `accName`, `noPool`, `compression`, `gossipMode` are present. Fields for `lnoc`, `lnocu`, `jetstream`, `connectURLs`, `wsConnURLs`, `gatewayURL`, `leafnodeURL`, `hash`, `idHash`, `startNewRoute`, `retry` are MISSING — not modelled in .NET |
|
||||
| `routeInfo` struct (unexported) | route.go:97–101 | MISSING | — | Used internally for deferred pool-connection creation after first PONG; no .NET equivalent |
|
||||
| `gossipDefault`/`gossipDisabled`/`gossipOverride` consts | route.go:104–108 | MISSING | — | Gossip mode bytes used in INFO propagation; not implemented in .NET |
|
||||
| `connectInfo` struct | route.go:110–124 | PARTIAL | `src/NATS.Server/Routes/RouteConnection.cs:328` (`BuildConnectInfoJson`) | .NET builds a simplified JSON payload; `connectInfo` fields Echo, Verbose, Pedantic, TLS, Headers, Cluster, Dynamic, LNOC, LNOCU are all MISSING from the .NET payload |
|
||||
| `ConProto`/`InfoProto` protocol format strings | route.go:127–130 | PARTIAL | `src/NATS.Server/Routes/RouteConnection.cs:73,83` | Handshake uses a simplified `ROUTE <serverId>` format rather than `CONNECT <json>` / `INFO <json>` |
|
||||
| `clusterTLSInsecureWarning` const | route.go:134 | NOT_APPLICABLE | — | TLS not yet implemented in .NET port; warning string has no counterpart |
|
||||
| `defaultRouteMaxPingInterval` const | route.go:140 | MISSING | — | Ping interval management for compression RTT auto-mode not implemented |
|
||||
| `routeConnectDelay`/`routeConnectMaxDelay`/`routeMaxPingInterval` vars | route.go:145–148 | PARTIAL | `src/NATS.Server/Routes/RouteManager.cs:486` (250ms hardcoded delay) | .NET hardcodes 250ms retry delay; Go uses configurable `DEFAULT_ROUTE_CONNECT` with exponential backoff |
|
||||
| `(c *client) removeReplySub` | route.go:151 | MISSING | — | Reply-sub cleanup for remote reply subs not implemented |
|
||||
| `(c *client) processAccountSub` | route.go:167 | NOT_APPLICABLE | — | Gateway-only path; gateway sub interest not in routes module |
|
||||
| `(c *client) processAccountUnsub` | route.go:174 | NOT_APPLICABLE | — | Gateway-only path |
|
||||
| `(c *client) processRoutedOriginClusterMsgArgs` | route.go:182 | MISSING | — | LMSG (origin-cluster routed msg) arg parsing not implemented; .NET only handles RMSG |
|
||||
| `(c *client) processRoutedHeaderMsgArgs` | route.go:281 | MISSING | — | HMSG (header msg from route) arg parsing not implemented |
|
||||
| `(c *client) processRoutedMsgArgs` | route.go:378 | PARTIAL | `src/NATS.Server/Routes/RouteConnection.cs:187–222` (`ReadFramesAsync`) | .NET parses basic RMSG account/subject/reply/size. Missing: queue-group routing indicators (`+`/`|`), header size field, multi-field arg parsing |
|
||||
| `(c *client) processInboundRoutedMsg` | route.go:460 | PARTIAL | `src/NATS.Server/Routes/RouteConnection.cs:219–221` | .NET fires `RoutedMessageReceived` callback; missing: stats update, gateway reply handling (`handleGatewayReply`), account lookup and fanout via `processMsgResults` |
|
||||
| `(c *client) sendRouteConnect` | route.go:503 | MISSING | — | Outbound CONNECT protocol (with cluster auth, TLS flags, etc.) not sent; .NET uses a simpler `ROUTE <serverId>` handshake |
|
||||
| `computeRoutePoolIdx` | route.go:538 | PORTED | `src/NATS.Server/Routes/RouteManager.cs:149` (`ComputeRoutePoolIdx`) | FNV-1a 32-bit hash, identical algorithm |
|
||||
| `(c *client) processRouteInfo` | route.go:549 | MISSING | — | Full INFO processing (cluster name negotiation, compression negotiation, duplicate detection, account route setup) not implemented; .NET handshake is a simple ID exchange |
|
||||
| `(s *Server) negotiateRouteCompression` | route.go:897 | PARTIAL | `src/NATS.Server/Routes/RouteCompressionCodec.cs:82` (`NegotiateCompression`) | .NET has the negotiation logic; but integration into handshake (INFO exchange, switching compression writer/reader mid-stream) is MISSING |
|
||||
| `(s *Server) updateRemoteRoutePerms` | route.go:953 | MISSING | — | Route permission update on INFO reload not implemented |
|
||||
| `(s *Server) sendAsyncInfoToClients` | route.go:1015 | MISSING | — | Async INFO broadcast to connected clients not implemented |
|
||||
| `(s *Server) processImplicitRoute` | route.go:1043 | PARTIAL | `src/NATS.Server/Routes/RouteManager.cs:107` (`ProcessImplicitRoute`) | .NET collects discovered URLs; missing: duplicate-ID check, pinned-account re-solicitation, `hasThisRouteConfigured` guard |
|
||||
| `(s *Server) hasThisRouteConfigured` | route.go:1104 | MISSING | — | Check whether incoming gossip URL is already a configured explicit route; not implemented |
|
||||
| `(s *Server) forwardNewRouteInfoToKnownServers` | route.go:1139 | PARTIAL | `src/NATS.Server/Routes/RouteManager.cs:127` (`ForwardNewRouteInfoToKnownServers`) | .NET raises an event with the new peer URL; missing: gossip mode logic (`gossipDefault`/`gossipDisabled`/`gossipOverride`), pinned-account route filtering, serialized INFO JSON sending |
|
||||
| `(c *client) canImport` | route.go:1226 | MISSING | — | Route import permission check not implemented |
|
||||
| `(c *client) canExport` | route.go:1235 | MISSING | — | Route export permission check not implemented |
|
||||
| `(c *client) setRoutePermissions` | route.go:1244 | MISSING | — | Route permission mapping (Import→Publish, Export→Subscribe) not implemented |
|
||||
| `asubs` struct | route.go:1263 | NOT_APPLICABLE | — | Internal Go helper to group subscriptions by account during cleanup; .NET uses LINQ equivalents |
|
||||
| `getAccNameFromRoutedSubKey` | route.go:1273 | MISSING | — | Sub key parsing for account name extraction not implemented |
|
||||
| `(c *client) getRoutedSubKeyInfo` | route.go:1290 | MISSING | — | Helper to determine account/key info for a route's subscriptions; not implemented |
|
||||
| `(c *client) removeRemoteSubs` | route.go:1299 | PARTIAL | `src/NATS.Server/Routes/RouteManager.cs:577` (`RemoveRoute`) | .NET removes the route connection but does NOT remove individual remote subscriptions from the SubList on close |
|
||||
| `(c *client) removeRemoteSubsForAcc` | route.go:1352 | MISSING | — | Per-account remote sub removal for dedicated route transition not implemented |
|
||||
| `(c *client) parseUnsubProto` | route.go:1366 | MISSING | — | RS-/LS- protocol arg parser not implemented; .NET `ReadFramesAsync` only extracts account/subject/queue loosely |
|
||||
| `(c *client) processRemoteUnsub` | route.go:1404 | PARTIAL | `src/NATS.Server/Routes/RouteConnection.cs:177–185` | .NET fires `RemoteSubscriptionReceived` with `IsRemoval=true`; missing: sub key lookup and removal from SubList, gateway/leafnode interest updates |
|
||||
| `(c *client) processRemoteSub` | route.go:1489 | PARTIAL | `src/NATS.Server/Routes/RouteConnection.cs:167–175` | .NET fires `RemoteSubscriptionReceived`; missing: key construction with type byte prefix, account lookup/creation, permission check (`canExport`), SubList insertion, gateway/leafnode updates, queue-weight delta tracking |
|
||||
| `(c *client) addRouteSubOrUnsubProtoToBuf` | route.go:1729 | PARTIAL | `src/NATS.Server/Routes/RouteConnection.cs:95–109` (`SendRsPlusAsync`/`SendRsMinusAsync`) | .NET sends RS+/RS- with account and optional queue; missing: LS+/LS- variant for leaf origin clusters, queue weight field in RS+ |
|
||||
| `(s *Server) sendSubsToRoute` | route.go:1781 | MISSING | — | Bulk send of local subscription interest to newly connected route not implemented; .NET only propagates incremental sub/unsub |
|
||||
| `(c *client) sendRouteSubProtos` | route.go:1881 | MISSING | — | Batch RS+ send not implemented |
|
||||
| `(c *client) sendRouteUnSubProtos` | route.go:1890 | MISSING | — | Batch RS- send not implemented |
|
||||
| `(c *client) sendRouteSubOrUnSubProtos` | route.go:1898 | MISSING | — | Low-level batch RS+/RS-/LS+/LS- sender not implemented |
|
||||
| `(s *Server) createRoute` | route.go:1935 | PARTIAL | `src/NATS.Server/Routes/RouteManager.cs:447,462` (`HandleInboundRouteAsync`/`ConnectToRouteWithRetryAsync`) | .NET creates a RouteConnection and performs handshake; missing: TLS setup, auth timeout timer, CONNECT protocol sending, INFO JSON sending, compression negotiation, ping timer |
|
||||
| `routeShouldDelayInfo` | route.go:2082 | MISSING | — | Logic to delay initial INFO until pool connection auth is confirmed not implemented |
|
||||
| `(s *Server) generateRouteInitialInfoJSON` | route.go:2090 | MISSING | — | Route INFO JSON generation (with nonce, pool index, gossip mode, compression) not implemented |
|
||||
| `(s *Server) addRoute` | route.go:2113 | PARTIAL | `src/NATS.Server/Routes/RouteManager.cs:496` (`Register`) | .NET registers route in dictionary; missing: pool index management, duplicate detection with `handleDuplicateRoute`, per-account route registration in `accRoutes`, `sendSubsToRoute` call, gateway/leafnode URL propagation, `forwardNewRouteInfoToKnownServers` |
|
||||
| `hasSolicitedRoute` | route.go:2438 | MISSING | — | Helper to find a solicited route in a pool slice; not implemented |
|
||||
| `upgradeRouteToSolicited` | route.go:2458 | MISSING | — | Upgrade an inbound route to solicited status; not implemented |
|
||||
| `handleDuplicateRoute` | route.go:2473 | MISSING | — | Duplicate route resolution (close extra connection, preserve retry flag) not implemented |
|
||||
| `(c *client) importFilter` | route.go:2510 | MISSING | — | Permission-based subscription filter for sending to routes not implemented |
|
||||
| `(s *Server) updateRouteSubscriptionMap` | route.go:2519 | PARTIAL | `src/NATS.Server/Routes/RouteManager.cs:381,392` (`PropagateLocalSubscription`/`PropagateLocalUnsubscription`) | .NET broadcasts RS+/RS- to all routes; missing: account routePoolIdx-based routing, queue-weight dedup (`sqmu`/`lqws`), no-pool route handling, gateway/leafnode interest updates |
|
||||
| `(s *Server) startRouteAcceptLoop` | route.go:2696 | PARTIAL | `src/NATS.Server/Routes/RouteManager.cs:333` (`StartAsync`) | .NET binds and starts accept loop, solicits configured routes; missing: cluster name logging, TLS config on accept, routeInfo construction, advertise/NoAdvertise, LeafNode/Gateway URL propagation |
|
||||
| `(s *Server) setRouteInfoHostPortAndIP` | route.go:2829 | MISSING | — | Route INFO host/port/IP with Cluster.Advertise support not implemented |
|
||||
| `(s *Server) StartRouting` | route.go:2849 | PORTED | `src/NATS.Server/Routes/RouteManager.cs:333` (`StartAsync`) | Functionally equivalent: starts accept loop and solicits routes |
|
||||
| `(s *Server) reConnectToRoute` | route.go:2861 | PARTIAL | `src/NATS.Server/Routes/RouteManager.cs:462` (`ConnectToRouteWithRetryAsync`) | .NET retries indefinitely with 250ms delay; missing: random jitter delay, explicit vs implicit distinction affecting delay, quit-channel integration |
|
||||
| `(s *Server) routeStillValid` | route.go:2881 | MISSING | — | Check that a route URL is still in configured routes list (for reconnect guard) not implemented |
|
||||
| `(s *Server) connectToRoute` | route.go:2890 | PARTIAL | `src/NATS.Server/Routes/RouteManager.cs:462` (`ConnectToRouteWithRetryAsync`) | .NET connects and retries; missing: explicit/implicit distinction, ConnectRetries limit, exponential backoff (`ConnectBackoff`), `routesToSelf` exclusion, address randomization from DNS |
|
||||
| `(c *client) isSolicitedRoute` | route.go:2976 | MISSING | — | Helper predicate; not implemented |
|
||||
| `(s *Server) saveRouteTLSName` | route.go:2985 | NOT_APPLICABLE | — | TLS not yet implemented in .NET port |
|
||||
| `(s *Server) solicitRoutes` | route.go:2996 | PARTIAL | `src/NATS.Server/Routes/RouteManager.cs:347–354` | .NET solicits configured routes with pool connections; missing: per-account (pinned) route solicitation, `saveRouteTLSName` |
|
||||
| `(c *client) processRouteConnect` | route.go:3011 | MISSING | — | Parsing and validation of inbound CONNECT from route (cluster name check, wrong-port detection, LNOC/LNOCU flags) not implemented; .NET uses a simpler handshake |
|
||||
| `(s *Server) removeAllRoutesExcept` | route.go:3085 | PORTED | `src/NATS.Server/Routes/RouteManager.cs:602` (`RemoveAllRoutesExcept`) | Equivalent behavior: remove all routes not in the keep-set |
|
||||
| `(s *Server) removeRoute` | route.go:3113 | PARTIAL | `src/NATS.Server/Routes/RouteManager.cs:577` (`RemoveRoute`) | .NET removes from `_routes` dict; missing: per-account route cleanup (`accRoutes`), hash removal, gateway/leafnode URL withdrawal, noPool counter, reconnect-after-noPool logic |
|
||||
| `(s *Server) isDuplicateServerName` | route.go:3233 | MISSING | — | Duplicate server name detection across routes not implemented |
|
||||
| `(s *Server) forEachNonPerAccountRoute` | route.go:3263 | NOT_APPLICABLE | — | Internal Go iterator over route slice; .NET uses `_routes.Values` LINQ directly |
|
||||
| `(s *Server) forEachRoute` | route.go:3277 | NOT_APPLICABLE | — | Internal Go iterator; .NET enumerates `_routes` and `_accountRoutes` directly |
|
||||
| `(s *Server) forEachRouteIdx` | route.go:3292 | NOT_APPLICABLE | — | Internal Go pool-index iterator; .NET `ComputeRoutePoolIdx` achieves equivalent selection |
|
||||
| `(s *Server) forEachRemote` | route.go:3305 | NOT_APPLICABLE | — | Internal Go iterator (first non-nil per remote); .NET has no equivalent but uses LINQ |
|
||||
|
||||
---
|
||||
|
||||
## Keeping This File Updated
|
||||
|
||||
After porting work is completed:
|
||||
|
||||
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/Routes/ -name '*.cs' -type f -exec cat {} + | wc -l
|
||||
# Re-count .NET test LOC for this module
|
||||
find tests/NATS.Server.Tests/Routes/ -name '*.cs' -type f -exec cat {} + | wc -l
|
||||
```
|
||||
4. **Add a changelog entry** below with date and summary of what was ported
|
||||
5. **Update the parity DB** if new test mappings were created:
|
||||
```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-25 | File created with LLM analysis instructions | auto |
|
||||
| 2026-02-25 | Full gap inventory populated: 57 Go symbols classified across route.go (3,314 lines). Counts: PORTED 4, PARTIAL 21, MISSING 23, NOT_APPLICABLE 9, DEFERRED 0 | auto |
|
||||
143
gaps/stillmissing.md
Normal file
143
gaps/stillmissing.md
Normal file
@@ -0,0 +1,143 @@
|
||||
# LOC Comparison: Go Reference vs .NET Port
|
||||
|
||||
> Generated 2026-02-25 (gap inventory populated). Lines of code counted with `wc -l` (includes blanks and comments).
|
||||
|
||||
## Summary
|
||||
|
||||
| Metric | Go | .NET | .NET / Go |
|
||||
|--------|---:|-----:|----------:|
|
||||
| **Source files** | 123 | 249 | 2.0x |
|
||||
| **Source LOC** | 134,140 | 51,303 | 38% |
|
||||
| **Test files** | 127 | 562 | 4.4x |
|
||||
| **Test LOC** | 260,730 | 165,630 | 64% |
|
||||
| **Total files** | 250 | 811 | 3.2x |
|
||||
| **Total LOC** | 394,870 | 216,933 | 55% |
|
||||
|
||||
The .NET port has **more files** (smaller, focused C# files vs Go's monolithic files) but **fewer total lines**. Source code is at 38% of Go LOC. Test code is at 64% of Go LOC. The higher file count reflects C#'s convention of one class/type per file.
|
||||
|
||||
---
|
||||
|
||||
## Source Code by Functional Area
|
||||
|
||||
| Functional Area | Go Files | Go LOC | .NET Files | .NET LOC | .NET/Go | Detail File | Notes |
|
||||
|-----------------|:--------:|-------:|:----------:|----------|--------:|-------------|-------|
|
||||
| Core Server | 10 | 21,223 | 13 | 3,597 | 17% | [core-server.md](core-server.md) | Go `server.go` + `client.go` = 13K+ combined |
|
||||
| Protocol | 3 | 1,829 | 6 | 1,156 | 63% | [protocol.md](protocol.md) | Parser, wire protocol, constants |
|
||||
| Subscriptions | 2 | 2,416 | 10 | 2,142 | 89% | [subscriptions.md](subscriptions.md) | Subject matching, trie, transforms |
|
||||
| Auth & Accounts | 5 | 7,260 | 41 | 3,445 | 47% | [auth-and-accounts.md](auth-and-accounts.md) | .NET includes Imports/ (12 files, 264 LOC) |
|
||||
| Configuration | 3 | 1,871 | 12 | 4,559 | 244% | [configuration.md](configuration.md) | .NET exceeds Go; includes rich config parsing |
|
||||
| Routes | 1 | 3,314 | 3 | 1,139 | 34% | [routes.md](routes.md) | Cluster full-mesh routing |
|
||||
| Gateways | 1 | 3,426 | 6 | 1,368 | 40% | [gateways.md](gateways.md) | Inter-cluster bridges |
|
||||
| Leaf Nodes | 1 | 3,470 | 5 | 1,553 | 45% | [leaf-nodes.md](leaf-nodes.md) | Hub-and-spoke edge topology |
|
||||
| **JetStream** | **20** | **55,228** | **68** | **16,630** | **30%** | [jetstream.md](jetstream.md) | Largest module; see breakdown below |
|
||||
| RAFT | 1 | 5,037 | 20 | 3,045 | 60% | [raft.md](raft.md) | Consensus protocol |
|
||||
| MQTT | 1 | 5,882 | 10 | 1,853 | 31% | [mqtt.md](mqtt.md) | MQTT protocol adapter |
|
||||
| WebSocket | 1 | 1,550 | 8 | 1,531 | 99% | [websocket.md](websocket.md) | Near-complete |
|
||||
| Monitoring | 2 | 4,396 | 15 | 2,308 | 53% | [monitoring.md](monitoring.md) | HTTP endpoints (/varz, /connz, etc.) |
|
||||
| Events | 2 | 4,133 | 5 | 1,642 | 40% | [events.md](events.md) | System events, message tracing |
|
||||
| TLS / Security | 12 | 4,207 | 7 | 516 | 12% | [tls-security.md](tls-security.md) | OCSP, certs, ciphersuites, proxy proto |
|
||||
| Internal DS | 34 | 4,020 | 7 | 4,225 | 105% | [internal-ds.md](internal-ds.md) | AVL, stree, THW, GSL, PSE, sysmem |
|
||||
| Logging | 4 | 936 | 0 | 0 | -- | [logging.md](logging.md) | .NET uses Microsoft.Extensions.Logging |
|
||||
| Utilities & Other | 19 | 3,282 | 3 | 244 | 7% | [utilities-and-other.md](utilities-and-other.md) | Ring, sendq, errors, ats, elastic, tpm |
|
||||
| Misc / Uncategorized | 1 | 660 | 6 | 70 | -- | [misc-uncategorized.md](misc-uncategorized.md) | |
|
||||
| **TOTAL** | **123** | **134,140** | **249** | **51,303** | **38%** | | |
|
||||
|
||||
### JetStream Breakdown
|
||||
|
||||
| JetStream Sub-area | Go Files | Go LOC | .NET Files | .NET LOC | .NET/Go |
|
||||
|--------------------|:--------:|-------:|:----------:|----------|--------:|
|
||||
| Core (orchestration, API, stream, consumer, store) | 10 | 28,150 | 5 | 1,656 | 6% |
|
||||
| API handlers | -- | (in Core) | 13 | 2,011 | -- |
|
||||
| Consumers | -- | (in Core) | 10 | 2,147 | -- |
|
||||
| Models | -- | (in Core) | 4 | 230 | -- |
|
||||
| Publish | -- | (in Core) | 5 | 639 | -- |
|
||||
| MirrorSource | -- | (in Core) | 2 | 1,085 | -- |
|
||||
| Snapshots | -- | (in Core) | 1 | 210 | -- |
|
||||
| Validation | -- | (in Core) | 1 | 60 | -- |
|
||||
| Storage (filestore, memstore, dirstore) | 9 | 16,191 | 19 | 6,254 | 39% |
|
||||
| Cluster (jetstream_cluster.go) | 1 | 10,887 | 8 | 2,338 | 21% |
|
||||
| **JetStream Total** | **20** | **55,228** | **68** | **16,630** | **30%** |
|
||||
|
||||
Go consolidates JetStream into a few massive files: `consumer.go` (~6K), `jetstream_api.go` (~5K), `stream.go` (~8K), `filestore.go` (~12K), `jetstream_cluster.go` (~11K). The .NET port decomposes these into smaller, focused files.
|
||||
|
||||
---
|
||||
|
||||
## Test Code by Functional Area
|
||||
|
||||
| Functional Area | Go Files | Go LOC | .NET Files | .NET LOC | .NET/Go |
|
||||
|-----------------|:--------:|-------:|:----------:|----------|--------:|
|
||||
| Core Server | 7 | 18,011 | 173* | 25,024* | 139%* |
|
||||
| Protocol | 5 | 2,020 | 4 | 1,433 | 71% |
|
||||
| Subscriptions | 2 | 2,711 | 8 | 1,211 | 45% |
|
||||
| Auth & Accounts | 6 | 15,448 | 26 | 10,170 | 66% |
|
||||
| Configuration | 3 | 5,281 | 12 | 8,237 | 156% |
|
||||
| Routes | 1 | 5,041 | 18 | 6,025 | 120% |
|
||||
| Gateways | 1 | 7,667 | 17 | 5,914 | 77% |
|
||||
| Leaf Nodes | 2 | 11,778 | 22 | 7,238 | 61% |
|
||||
| JetStream (all) | 22 | 105,284 | 160 | 65,314 | 62% |
|
||||
| RAFT | 3 | 6,140 | 33 | 9,154 | 149% |
|
||||
| MQTT | 3 | 9,793 | 25 | 5,953 | 61% |
|
||||
| WebSocket | 1 | 4,982 | 13 | 2,590 | 52% |
|
||||
| Monitoring | 1 | 6,603 | 19 | 5,989 | 91% |
|
||||
| Events | 2 | 9,344 | 8 | 3,091 | 33% |
|
||||
| NoRace / Stress | 2 | 12,009 | 3 | 2,342 | 20% |
|
||||
| Internal DS | 7 | 3,557 | 6 | 3,837 | 108% |
|
||||
| Integration (test/) | 35 | 29,812 | -- | -- | -- |
|
||||
| Other / Misc | 14 | 2,904 | 14 | 2,058 | 71% |
|
||||
| **TOTAL** | **127** | **260,730** | **562** | **165,630** | **64%** |
|
||||
|
||||
*\* The .NET "Core Server" root test directory (173 files, 25,024 LOC) contains mixed-purpose tests spanning multiple functional areas (server lifecycle, client protocol, pubsub, integration). Not directly comparable to the Go "Core Server" test files.*
|
||||
|
||||
### JetStream Test Breakdown
|
||||
|
||||
| JetStream Test Area | Go Files | Go LOC | .NET Files | .NET LOC | .NET/Go |
|
||||
|---------------------|:--------:|-------:|:----------:|----------|--------:|
|
||||
| Core tests | 9 | 42,582 | 50* | 14,824* | 35% |
|
||||
| Storage tests | 4 | 16,930 | 32 | 14,672 | 87% |
|
||||
| Cluster tests | 8 | 43,426 | 41 | 24,737 | 57% |
|
||||
| Consumer tests | -- | (in Core) | 22 | 7,701 | -- |
|
||||
| API tests | -- | (in Core) | 9 | 2,162 | -- |
|
||||
| Other (mirror, snap, stream) | -- | (in Core) | 6 | 1,918 | -- |
|
||||
| Benchmark | 1 | 2,346 | -- | -- | -- |
|
||||
| **JetStream Test Total** | **22** | **105,284** | **160** | **65,314** | **62%** |
|
||||
|
||||
*\* JetStream root-level test files not in a named subdirectory.*
|
||||
|
||||
---
|
||||
|
||||
## Areas Where .NET Exceeds Go LOC
|
||||
|
||||
| Area | Go LOC | .NET LOC | Ratio | Explanation |
|
||||
|------|-------:|----------|------:|-------------|
|
||||
| Configuration | 1,871 | 4,559 | 2.4x | Richer config parser with lexer/token model |
|
||||
| Internal DS | 4,020 | 4,225 | 1.05x | Full parity on AVL, stree, THW, GSL |
|
||||
| WebSocket | 1,550 | 1,531 | 0.99x | Near-identical coverage |
|
||||
|
||||
---
|
||||
|
||||
## Largest Gaps (Source LOC)
|
||||
|
||||
| Area | Go LOC | .NET LOC | Gap (LOC) | .NET/Go |
|
||||
|------|-------:|----------|----------:|--------:|
|
||||
| Core Server | 21,223 | 3,877 | 17,346 | 18% |
|
||||
| JetStream Core | 28,150 | 8,038 | 20,112 | 29% |
|
||||
| JetStream Storage | 16,191 | 6,254 | 9,937 | 39% |
|
||||
| JetStream Cluster | 10,887 | 2,338 | 8,549 | 21% |
|
||||
| Auth & Accounts | 7,260 | 3,445 | 3,815 | 47% |
|
||||
| MQTT | 5,882 | 1,853 | 4,029 | 31% |
|
||||
| Monitoring | 4,396 | 2,308 | 2,088 | 53% |
|
||||
| Events | 4,133 | 1,642 | 2,491 | 40% |
|
||||
| TLS / Security | 4,207 | 516 | 3,691 | 12% |
|
||||
| Utilities & Other | 3,282 | 244 | 3,038 | 7% |
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
- **LOC counts include blank lines and comments.** Go tends to be more verbose with explicit error handling; C# uses exceptions and has less boilerplate per line. A 1:1 LOC ratio is not the goal.
|
||||
- **Go's monolithic files vs .NET's granular files.** Go's `client.go` is 6,700+ lines; the .NET equivalent is split across `NatsClient.cs`, `ClientFlags.cs`, `ClientKind.cs`, `INatsClient.cs`, etc. File count comparisons favor .NET.
|
||||
- **Go `test/` directory** (35 files, 29,812 LOC) contains integration/acceptance tests with no direct .NET equivalent directory; .NET integration tests are spread across subdirectories.
|
||||
- **Go `norace_*_test.go`** files (12,009 LOC) contain long-running race-condition tests that cross multiple subsystems. .NET equivalents are in `Stress/` (2,342 LOC).
|
||||
- **Configuration .NET > Go** because .NET implements a full lexer/parser/token pipeline for the NATS config format rather than using Go's simpler approach.
|
||||
- **.NET Logging = 0 LOC** because it uses `Microsoft.Extensions.Logging` + `Serilog` as NuGet packages rather than custom logging code.
|
||||
- **Internal Data Structures .NET >= Go** — the AVL, subject tree, time hash wheel, and GSL implementations are at or above Go parity.
|
||||
232
gaps/subscriptions.md
Normal file
232
gaps/subscriptions.md
Normal file
@@ -0,0 +1,232 @@
|
||||
# Subscriptions — Gap Analysis
|
||||
|
||||
> This file tracks what has and hasn't been ported from Go to .NET for the **Subscriptions** 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 Subscriptions
|
||||
|
||||
- The `Sublist` trie is **performance-critical** — every published message triggers a `Match()` call.
|
||||
- Cache invalidation uses atomic generation counters (`Interlocked` in .NET).
|
||||
- `subject_transform.go` handles subject mapping for imports/exports — check if the .NET `Imports/` directory covers this.
|
||||
|
||||
---
|
||||
|
||||
## Go Reference Files (Source)
|
||||
|
||||
- `golang/nats-server/server/sublist.go` — Trie-based subject matcher with wildcard support (`*` single, `>` multi). Nodes have `psubs` (plain), `qsubs` (queue groups). Results cached with atomic generation IDs.
|
||||
- `golang/nats-server/server/subject_transform.go` — Subject transformation logic (mapping, filtering)
|
||||
|
||||
## Go Reference Files (Tests)
|
||||
|
||||
- `golang/nats-server/server/sublist_test.go`
|
||||
- `golang/nats-server/server/subject_transform_test.go`
|
||||
|
||||
## .NET Implementation Files (Source)
|
||||
|
||||
- `src/NATS.Server/Subscriptions/SubjectMatch.cs`
|
||||
- `src/NATS.Server/Subscriptions/SubList.cs`
|
||||
- `src/NATS.Server/Subscriptions/SubListResult.cs`
|
||||
- `src/NATS.Server/Subscriptions/Subscription.cs`
|
||||
- All other files in `src/NATS.Server/Subscriptions/`
|
||||
|
||||
## .NET Implementation Files (Tests)
|
||||
|
||||
- `tests/NATS.Server.Tests/Subscriptions/`
|
||||
- `tests/NATS.Server.Tests/SubList/`
|
||||
|
||||
---
|
||||
|
||||
## Gap Inventory
|
||||
|
||||
<!-- After analysis, fill in this table. Group rows by Go source file. -->
|
||||
|
||||
### golang/nats-server/server/sublist.go
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `SublistResult` (struct) | sublist.go:59 | PORTED | `src/NATS.Server/Subscriptions/SubListResult.cs:4` | Fields renamed: `psubs`→`PlainSubs`, `qsubs`→`QueueSubs`; .NET uses arrays instead of slices |
|
||||
| `Sublist` (struct) | sublist.go:65 | PORTED | `src/NATS.Server/Subscriptions/SubList.cs:11` | Core trie struct ported; notification model differs (event vs channels) |
|
||||
| `notifyMaps` (struct) | sublist.go:80 | NOT_APPLICABLE | — | Go uses buffered channels for interest notifications; .NET uses `InterestChanged` event (`InterestChange.cs`) |
|
||||
| `node` (struct) | sublist.go:87 | PORTED | `src/NATS.Server/Subscriptions/SubList.cs:974` (private `TrieNode`) | Inner class; `psubs`+`plist` merged into `PlainSubs HashSet`, `qsubs`→`QueueSubs Dictionary` |
|
||||
| `level` (struct) | sublist.go:96 | PORTED | `src/NATS.Server/Subscriptions/SubList.cs:967` (private `TrieLevel`) | Inner class; `nodes`, `pwc`, `fwc` all present |
|
||||
| `newNode()` | sublist.go:102 | PORTED | `src/NATS.Server/Subscriptions/SubList.cs:974` | Inline `new TrieNode()` |
|
||||
| `newLevel()` | sublist.go:107 | PORTED | `src/NATS.Server/Subscriptions/SubList.cs:967` | Inline `new TrieLevel()` |
|
||||
| `NewSublist(bool)` | sublist.go:117 | PARTIAL | `src/NATS.Server/Subscriptions/SubList.cs:11` | .NET `SubList` always starts with cache; no `enableCache` constructor param |
|
||||
| `NewSublistWithCache()` | sublist.go:125 | PORTED | `src/NATS.Server/Subscriptions/SubList.cs:11` | Default `new SubList()` has cache enabled |
|
||||
| `NewSublistNoCache()` | sublist.go:130 | MISSING | — | No .NET equivalent; SubList always caches |
|
||||
| `CacheEnabled()` | sublist.go:135 | MISSING | — | No public method to query whether cache is on; `CacheCount` exists but different semantics |
|
||||
| `RegisterNotification()` | sublist.go:149 | MISSING | — | Go sends `true/false` on channel when first/last interest added/removed; .NET uses `InterestChanged` event which fires on every change but doesn't replicate channel-based deferred notify semantics |
|
||||
| `RegisterQueueNotification()` | sublist.go:153 | MISSING | — | Queue-specific interest notification; no .NET equivalent |
|
||||
| `ClearNotification()` | sublist.go:227 | MISSING | — | No .NET equivalent for removing a notification channel |
|
||||
| `ClearQueueNotification()` | sublist.go:231 | MISSING | — | No .NET equivalent |
|
||||
| `chkForInsertNotification()` | sublist.go:301 | NOT_APPLICABLE | — | Internal helper for channel notification; replaced by `InterestChanged` event emission in `Insert()` |
|
||||
| `chkForRemoveNotification()` | sublist.go:317 | NOT_APPLICABLE | — | Internal helper; replaced by `InterestChanged` event emission in `Remove()` |
|
||||
| `sendNotification()` | sublist.go:253 | NOT_APPLICABLE | — | Non-blocking channel send; Go-specific pattern, no .NET equivalent needed |
|
||||
| `addInsertNotify()` | sublist.go:262 | NOT_APPLICABLE | — | Internal channel registration helper; replaced by event model |
|
||||
| `addRemoveNotify()` | sublist.go:268 | NOT_APPLICABLE | — | Internal channel registration helper |
|
||||
| `addNotify()` | sublist.go:274 | NOT_APPLICABLE | — | Internal channel map helper |
|
||||
| `keyFromSubjectAndQueue()` | sublist.go:290 | NOT_APPLICABLE | — | Key generation for notification maps; not needed in .NET event model |
|
||||
| `Insert()` | sublist.go:356 | PORTED | `src/NATS.Server/Subscriptions/SubList.cs:179` | Full trie insertion with queue support; fires `InterestChanged` event instead of channel notify |
|
||||
| `copyResult()` | sublist.go:449 | NOT_APPLICABLE | — | Go uses copy-on-write for cache mutation; .NET creates new `SubListResult` directly |
|
||||
| `SublistResult.addSubToResult()` | sublist.go:460 | NOT_APPLICABLE | — | Copy-on-write pattern for cache updates; not needed in .NET generation-based cache invalidation |
|
||||
| `addToCache()` | sublist.go:478 | NOT_APPLICABLE | — | Go updates individual cache entries on insert; .NET uses generation-based invalidation (simpler) |
|
||||
| `removeFromCache()` | sublist.go:498 | NOT_APPLICABLE | — | Same as above; .NET bumps generation counter instead |
|
||||
| `Match()` | sublist.go:520 | PORTED | `src/NATS.Server/Subscriptions/SubList.cs:360` | Generation-based cache; same algorithm |
|
||||
| `MatchBytes()` | sublist.go:526 | PORTED | `src/NATS.Server/Subscriptions/SubList.cs:425` | Takes `ReadOnlySpan<byte>` vs `[]byte` |
|
||||
| `HasInterest()` | sublist.go:532 | PORTED | `src/NATS.Server/Subscriptions/SubList.cs:638` | Cache-aware fast path |
|
||||
| `NumInterest()` | sublist.go:537 | PORTED | `src/NATS.Server/Subscriptions/SubList.cs:669` | Returns `(plainCount, queueCount)` tuple |
|
||||
| `matchNoLock()` | sublist.go:543 | NOT_APPLICABLE | — | Internal; .NET lock model differs (no equivalent needed) |
|
||||
| `match()` | sublist.go:547 | NOT_APPLICABLE | — | Internal implementation; split into `Match()` + private helpers in .NET |
|
||||
| `hasInterest()` | sublist.go:624 | NOT_APPLICABLE | — | Internal; maps to `HasInterestLevel()` private helper |
|
||||
| `reduceCacheCount()` | sublist.go:675 | PORTED | `src/NATS.Server/Subscriptions/SubListCacheSweeper.cs:7` + `SubList.cs:458` | Background goroutine mapped to `SubListCacheSweeper` + `SweepCache()` |
|
||||
| `isRemoteQSub()` | sublist.go:689 | NOT_APPLICABLE | — | Go has client.kind == ROUTER/LEAF; .NET uses `RemoteSubscription` model instead |
|
||||
| `UpdateRemoteQSub()` | sublist.go:695 | MISSING | — | Go updates weight of remote qsub; .NET uses `ApplyRemoteSub()` for a different model (full add/remove) |
|
||||
| `addNodeToResults()` | sublist.go:706 | PORTED | `src/NATS.Server/Subscriptions/SubList.cs:549` (private `AddNodeToResults()`) | Remote qsub weight expansion present in Go missing in .NET |
|
||||
| `findQSlot()` | sublist.go:745 | PORTED | `src/NATS.Server/Subscriptions/SubList.cs:562` (inline in `AddNodeToResults`) | Inlined in .NET |
|
||||
| `matchLevel()` | sublist.go:757 | PORTED | `src/NATS.Server/Subscriptions/SubList.cs:506` (private `MatchLevel()`) | Core trie descent algorithm |
|
||||
| `matchLevelForAny()` | sublist.go:785 | PORTED | `src/NATS.Server/Subscriptions/SubList.cs:754` (private `HasInterestLevel()`) + `CountInterestLevel()` | Split into two .NET helpers |
|
||||
| `remove()` (internal) | sublist.go:843 | PORTED | `src/NATS.Server/Subscriptions/SubList.cs:282` (private `RemoveInternal()`) | Internal removal logic |
|
||||
| `Remove()` | sublist.go:915 | PORTED | `src/NATS.Server/Subscriptions/SubList.cs:255` | Fires `InterestChanged` event |
|
||||
| `RemoveBatch()` | sublist.go:920 | PORTED | `src/NATS.Server/Subscriptions/SubList.cs:687` | Cache disable/re-enable pattern ported |
|
||||
| `level.pruneNode()` | sublist.go:953 | PORTED | `src/NATS.Server/Subscriptions/SubList.cs:345` (inline in `RemoveInternal`) | Inlined in removal path |
|
||||
| `node.isEmpty()` | sublist.go:968 | PORTED | `src/NATS.Server/Subscriptions/SubList.cs:981` (`TrieNode.IsEmpty` property) | Property instead of method |
|
||||
| `level.numNodes()` | sublist.go:978 | NOT_APPLICABLE | — | Used internally in `visitLevel()`; no .NET equivalent needed |
|
||||
| `removeFromNode()` | sublist.go:993 | PORTED | `src/NATS.Server/Subscriptions/SubList.cs:324` (inline in `RemoveInternal`) | Inlined |
|
||||
| `Count()` | sublist.go:1023 | PORTED | `src/NATS.Server/Subscriptions/SubList.cs:41` (`Count` property) | |
|
||||
| `CacheCount()` | sublist.go:1030 | PORTED | `src/NATS.Server/Subscriptions/SubList.cs:97` (`CacheCount` property) | |
|
||||
| `SublistStats` (struct) | sublist.go:1038 | PORTED | `src/NATS.Server/Subscriptions/SubListStats.cs:3` | All public fields present; unexported fields `totFanout`, `cacheCnt`, `cacheHits` dropped (computed inline in `Stats()`) |
|
||||
| `SublistStats.add()` | sublist.go:1052 | MISSING | — | Aggregates multiple SublistStats into one; used for cluster monitoring; no .NET equivalent |
|
||||
| `Stats()` | sublist.go:1076 | PORTED | `src/NATS.Server/Subscriptions/SubList.cs:580` | Full fanout stats computed |
|
||||
| `numLevels()` | sublist.go:1120 | MISSING | — | Debug/test utility counting trie depth; not ported |
|
||||
| `visitLevel()` | sublist.go:1126 | MISSING | — | Internal helper for `numLevels()`; not ported |
|
||||
| `subjectHasWildcard()` | sublist.go:1159 | PARTIAL | `src/NATS.Server/Subscriptions/SubjectMatch.cs:50` (`IsLiteral()` — inverse) | .NET `!IsLiteral()` is equivalent but not a dedicated function |
|
||||
| `subjectIsLiteral()` | sublist.go:1174 | PORTED | `src/NATS.Server/Subscriptions/SubjectMatch.cs:50` (`IsLiteral()`) | Exact equivalent |
|
||||
| `IsValidPublishSubject()` | sublist.go:1187 | PORTED | `src/NATS.Server/Subscriptions/SubjectMatch.cs:67` | |
|
||||
| `IsValidSubject()` | sublist.go:1192 | PORTED | `src/NATS.Server/Subscriptions/SubjectMatch.cs:9` | |
|
||||
| `isValidSubject()` (internal, checkRunes) | sublist.go:1196 | PORTED | `src/NATS.Server/Subscriptions/SubjectMatch.cs:211` (`IsValidSubject(string, bool)`) | |
|
||||
| `IsValidLiteralSubject()` | sublist.go:1236 | PARTIAL | `src/NATS.Server/Subscriptions/SubjectMatch.cs:50` (`IsLiteral()`) | `IsLiteral()` does not validate the subject first; `IsValidPublishSubject()` combines both |
|
||||
| `isValidLiteralSubject()` (tokens iter) | sublist.go:1241 | NOT_APPLICABLE | — | Takes `iter.Seq[string]` (Go 1.23 iterator); C# uses different iteration model |
|
||||
| `ValidateMapping()` | sublist.go:1258 | MISSING | — | Validates a mapping destination subject string including `{{function()}}` syntax; no public .NET equivalent |
|
||||
| `analyzeTokens()` | sublist.go:1298 | NOT_APPLICABLE | — | Internal helper used only in `SubjectsCollide()`; logic inlined in .NET `SubjectsCollide()` |
|
||||
| `tokensCanMatch()` | sublist.go:1314 | PORTED | `src/NATS.Server/Subscriptions/SubjectMatch.cs:199` (private `TokensCanMatch()`) | |
|
||||
| `SubjectsCollide()` | sublist.go:1326 | PORTED | `src/NATS.Server/Subscriptions/SubjectMatch.cs:159` | |
|
||||
| `numTokens()` | sublist.go:1374 | PORTED | `src/NATS.Server/Subscriptions/SubjectMatch.cs:118` (`NumTokens()`) | |
|
||||
| `tokenAt()` | sublist.go:1389 | PORTED | `src/NATS.Server/Subscriptions/SubjectMatch.cs:132` (`TokenAt()`) | Go is 1-based index; .NET is 0-based |
|
||||
| `tokenizeSubjectIntoSlice()` | sublist.go:1407 | NOT_APPLICABLE | — | Internal slice-reuse helper; .NET uses `Tokenize()` private method in SubList |
|
||||
| `SubjectMatchesFilter()` | sublist.go:1421 | PARTIAL | `src/NATS.Server/JetStream/Storage/MemStore.cs:1175` (private), `FileStore.cs:773` (private) | Duplicated as private methods in MemStore and FileStore; not a public standalone function |
|
||||
| `subjectIsSubsetMatch()` | sublist.go:1426 | MISSING | — | No public .NET equivalent; logic exists privately in MemStore/FileStore |
|
||||
| `isSubsetMatch()` | sublist.go:1434 | MISSING | — | Internal; no public .NET equivalent |
|
||||
| `isSubsetMatchTokenized()` | sublist.go:1444 | MISSING | — | Internal; no public .NET equivalent |
|
||||
| `matchLiteral()` | sublist.go:1483 | PORTED | `src/NATS.Server/Subscriptions/SubjectMatch.cs:75` (`MatchLiteral()`) | |
|
||||
| `addLocalSub()` | sublist.go:1552 | NOT_APPLICABLE | — | Filters by client kind (CLIENT/SYSTEM/JETSTREAM/ACCOUNT/LEAF); no .NET equivalent needed (client kind routing done elsewhere) |
|
||||
| `Sublist.addNodeToSubs()` | sublist.go:1562 | NOT_APPLICABLE | — | Internal helper for `localSubs()`; not ported |
|
||||
| `Sublist.collectLocalSubs()` | sublist.go:1581 | NOT_APPLICABLE | — | Internal helper for `localSubs()`; not ported |
|
||||
| `Sublist.localSubs()` | sublist.go:1597 | MISSING | — | Returns only local-client subscriptions (excludes routes/gateways); no .NET equivalent |
|
||||
| `Sublist.All()` | sublist.go:1604 | PORTED | `src/NATS.Server/Subscriptions/SubList.cs:712` | |
|
||||
| `Sublist.addAllNodeToSubs()` | sublist.go:1610 | NOT_APPLICABLE | — | Internal helper for `All()`; inlined in .NET |
|
||||
| `Sublist.collectAllSubs()` | sublist.go:1627 | NOT_APPLICABLE | — | Internal; inlined in `CollectAllSubs()` private method in .NET |
|
||||
| `ReverseMatch()` | sublist.go:1649 | PORTED | `src/NATS.Server/Subscriptions/SubList.cs:727` | |
|
||||
| `reverseMatchLevel()` | sublist.go:1674 | PORTED | `src/NATS.Server/Subscriptions/SubList.cs:860` (private `ReverseMatchLevel()`) | |
|
||||
| `getAllNodes()` | sublist.go:1714 | PORTED | `src/NATS.Server/Subscriptions/SubList.cs:909` (private `CollectAllNodes()`) | |
|
||||
|
||||
### golang/nats-server/server/subject_transform.go
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| Transform type constants (`NoTransform`…`Random`) | subject_transform.go:43 | PARTIAL | `src/NATS.Server/Subscriptions/SubjectTransform.cs:682` (private `TransformType` enum) | `Random` (value 11 in Go) is absent from the .NET enum and switch statement; all others ported |
|
||||
| `subjectTransform` (struct) | subject_transform.go:61 | PORTED | `src/NATS.Server/Subscriptions/SubjectTransform.cs:11` | Internal fields mapped to `_source`, `_dest`, `_sourceTokens`, `_destTokens`, `_ops` |
|
||||
| `SubjectTransformer` (interface) | subject_transform.go:73 | NOT_APPLICABLE | — | Go exports interface for polymorphism; C# uses concrete `SubjectTransform` class directly |
|
||||
| `NewSubjectTransformWithStrict()` | subject_transform.go:81 | MISSING | — | Strict mode validates that all source wildcards are used in dest; no .NET equivalent |
|
||||
| `NewSubjectTransform()` | subject_transform.go:198 | PORTED | `src/NATS.Server/Subscriptions/SubjectTransform.cs:31` (`Create()`) | Non-strict creation |
|
||||
| `NewSubjectTransformStrict()` | subject_transform.go:202 | MISSING | — | Strict version for import mappings; no .NET equivalent |
|
||||
| `getMappingFunctionArgs()` | subject_transform.go:206 | PORTED | `src/NATS.Server/Subscriptions/SubjectTransform.cs:639` (private `GetFunctionArgs()`) | |
|
||||
| `transformIndexIntArgsHelper()` | subject_transform.go:215 | PORTED | `src/NATS.Server/Subscriptions/SubjectTransform.cs:610` (private `ParseIndexIntArgs()`) | |
|
||||
| `indexPlaceHolders()` | subject_transform.go:237 | PORTED | `src/NATS.Server/Subscriptions/SubjectTransform.cs:486` (`ParseDestToken()` + `ParseMustacheToken()`) | Split into two methods; `Random` branch missing |
|
||||
| `transformTokenize()` | subject_transform.go:378 | MISSING | — | Converts `foo.*.*` to `foo.$1.$2`; used for import subject mapping reversal; no .NET equivalent |
|
||||
| `transformUntokenize()` | subject_transform.go:399 | MISSING | — | Inverse of above; used in `reverse()`; no .NET equivalent |
|
||||
| `tokenizeSubject()` | subject_transform.go:414 | NOT_APPLICABLE | — | Internal tokenizer; .NET uses `string.Split('.')` or `Tokenize()` private method |
|
||||
| `subjectTransform.Match()` | subject_transform.go:433 | PORTED | `src/NATS.Server/Subscriptions/SubjectTransform.cs:126` (`Apply()`) | Renamed; returns `null` instead of error on no-match |
|
||||
| `subjectTransform.TransformSubject()` | subject_transform.go:456 | PARTIAL | `src/NATS.Server/Subscriptions/SubjectTransform.cs:126` (via `Apply()`) | `TransformSubject` (apply without match check) not separately exposed; `Apply()` always checks match |
|
||||
| `subjectTransform.getRandomPartition()` | subject_transform.go:460 | MISSING | — | `Random` transform type not implemented in .NET |
|
||||
| `subjectTransform.getHashPartition()` | subject_transform.go:469 | PORTED | `src/NATS.Server/Subscriptions/SubjectTransform.cs:226` (`ComputePartition()` + `Fnv1A32()`) | FNV-1a 32-bit hash ported |
|
||||
| `subjectTransform.TransformTokenizedSubject()` | subject_transform.go:482 | PORTED | `src/NATS.Server/Subscriptions/SubjectTransform.cs:144` (private `TransformTokenized()`) | All transform types except `Random` ported |
|
||||
| `subjectTransform.reverse()` | subject_transform.go:638 | MISSING | — | Produces the inverse transform; used for import subject mapping; no .NET equivalent |
|
||||
| `subjectInfo()` | subject_transform.go:666 | PORTED | `src/NATS.Server/Subscriptions/SubjectTransform.cs:451` (private `SubjectInfo()`) | |
|
||||
| `ValidateMapping()` (in subject_transform.go context) | sublist.go:1258 | MISSING | — | Also defined via `NewSubjectTransform`; validates mapping destination with function syntax; no .NET public equivalent |
|
||||
|
||||
---
|
||||
|
||||
## 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/Subscriptions/ -name '*.cs' -type f -exec cat {} + | wc -l
|
||||
# Re-count .NET test LOC for this module
|
||||
find tests/NATS.Server.Tests/Subscriptions/ tests/NATS.Server.Tests/SubList/ -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-25 | File created with LLM analysis instructions | auto |
|
||||
| 2026-02-25 | Full gap inventory populated: analyzed sublist.go (~1,729 lines) and subject_transform.go (~689 lines) against all .NET Subscriptions/*.cs files. Counted 49 PORTED, 6 PARTIAL, 22 MISSING, 27 NOT_APPLICABLE, 0 DEFERRED. | claude-sonnet-4-6 |
|
||||
340
gaps/tls-security.md
Normal file
340
gaps/tls-security.md
Normal file
@@ -0,0 +1,340 @@
|
||||
# TLS / Security — Gap Analysis
|
||||
|
||||
> This file tracks what has and hasn't been ported from Go to .NET for the **TLS / Security** 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 TLS / Security
|
||||
|
||||
- This category is at **12% LOC parity** — one of the largest gaps.
|
||||
- OCSP stapling and peer validation are critical for mutual TLS deployments.
|
||||
- Cipher suite filtering allows operators to restrict which TLS ciphers are accepted.
|
||||
- PROXY protocol support enables running NATS behind HAProxy/nginx with real client IPs.
|
||||
- `certstore` provides OS certificate store access (Windows-specific parts may be NOT_APPLICABLE).
|
||||
- `certidp` implements certificate identity validation via OCSP responders.
|
||||
|
||||
---
|
||||
|
||||
## Go Reference Files (Source)
|
||||
|
||||
- `golang/nats-server/server/ocsp.go` — OCSP stapling configuration
|
||||
- `golang/nats-server/server/ocsp_peer.go` — OCSP peer certificate validation
|
||||
- `golang/nats-server/server/ocsp_responsecache.go` — OCSP response caching
|
||||
- `golang/nats-server/server/ciphersuites.go` — TLS cipher suite management and filtering
|
||||
- `golang/nats-server/server/client_proxyproto.go` — PROXY protocol v1/v2 support (HAProxy)
|
||||
- `golang/nats-server/server/certidp/certidp.go` — Certificate identity provider
|
||||
- `golang/nats-server/server/certidp/messages.go` — CertIDP message types
|
||||
- `golang/nats-server/server/certidp/ocsp_responder.go` — OCSP responder client
|
||||
- `golang/nats-server/server/certstore/certstore.go` — OS certificate store access
|
||||
- `golang/nats-server/server/certstore/certstore_other.go` — Non-Windows cert store
|
||||
- `golang/nats-server/server/certstore/certstore_windows.go` — Windows cert store
|
||||
- `golang/nats-server/server/certstore/errors.go` — Cert store errors
|
||||
|
||||
## Go Reference Files (Tests)
|
||||
|
||||
- `golang/nats-server/test/ocsp_test.go` (integration)
|
||||
- `golang/nats-server/test/ocsp_peer_test.go` (integration)
|
||||
- `golang/nats-server/test/tls_test.go` (integration)
|
||||
- `golang/nats-server/server/certstore/certstore_windows_test.go`
|
||||
- `golang/nats-server/server/certidp/*_test.go`
|
||||
|
||||
## .NET Implementation Files (Source)
|
||||
|
||||
- `src/NATS.Server/Tls/TlsHelper.cs`
|
||||
- `src/NATS.Server/Tls/TlsCertificateProvider.cs`
|
||||
- `src/NATS.Server/Tls/TlsConnectionState.cs`
|
||||
- `src/NATS.Server/Tls/TlsConnectionWrapper.cs`
|
||||
- `src/NATS.Server/Tls/TlsRateLimiter.cs`
|
||||
- `src/NATS.Server/Tls/PeekableStream.cs`
|
||||
- `src/NATS.Server/Tls/OcspConfig.cs`
|
||||
|
||||
## .NET Implementation Files (Tests)
|
||||
|
||||
- `tests/NATS.Server.Tests/` (TLS-related test files in root)
|
||||
|
||||
---
|
||||
|
||||
## Gap Inventory
|
||||
|
||||
<!-- After analysis, fill in this table. Group rows by Go source file. -->
|
||||
|
||||
### ocsp.go — OCSP stapling configuration
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| OCSPMode (type) | golang/nats-server/server/ocsp.go:46 | PORTED | src/NATS.Server/Tls/OcspConfig.cs:8 | Mapped to `OcspMode` enum with matching values |
|
||||
| OCSPModeAuto | golang/nats-server/server/ocsp.go:49 | PORTED | src/NATS.Server/Tls/OcspConfig.cs:10 | `OcspMode.Auto = 0` |
|
||||
| OCSPModeAlways | golang/nats-server/server/ocsp.go:52 | PORTED | src/NATS.Server/Tls/OcspConfig.cs:11 | `OcspMode.Always = 1` |
|
||||
| OCSPModeNever | golang/nats-server/server/ocsp.go:55 | PORTED | src/NATS.Server/Tls/OcspConfig.cs:13 | `OcspMode.Never = 3` |
|
||||
| OCSPModeMust | golang/nats-server/server/ocsp.go:60 | PORTED | src/NATS.Server/Tls/OcspConfig.cs:12 | `OcspMode.Must = 2` |
|
||||
| OCSPMonitor (struct) | golang/nats-server/server/ocsp.go:65 | MISSING | — | No dedicated OCSP monitor struct; .NET delegates to SslStreamCertificateContext for stapling |
|
||||
| OCSPMonitor.getNextRun | golang/nats-server/server/ocsp.go:80 | MISSING | — | No periodic OCSP refresh loop |
|
||||
| OCSPMonitor.getStatus | golang/nats-server/server/ocsp.go:102 | MISSING | — | No cache/local/remote OCSP status retrieval |
|
||||
| OCSPMonitor.getCacheStatus | golang/nats-server/server/ocsp.go:119 | MISSING | — | No in-memory OCSP response caching |
|
||||
| OCSPMonitor.getLocalStatus | golang/nats-server/server/ocsp.go:125 | MISSING | — | No file-based OCSP status persistence |
|
||||
| OCSPMonitor.getRemoteStatus | golang/nats-server/server/ocsp.go:160 | MISSING | — | No manual OCSP responder HTTP callout; .NET relies on runtime OCSP |
|
||||
| OCSPMonitor.run | golang/nats-server/server/ocsp.go:272 | MISSING | — | No background goroutine for OCSP staple renewal |
|
||||
| OCSPMonitor.stop | golang/nats-server/server/ocsp.go:348 | MISSING | — | No explicit stop channel for OCSP monitor |
|
||||
| Server.NewOCSPMonitor | golang/nats-server/server/ocsp.go:357 | PARTIAL | src/NATS.Server/Tls/TlsHelper.cs:75 | `BuildCertificateContext` delegates stapling to .NET runtime; missing per-kind config, VerifyConnection callbacks, shutdown-on-revoke |
|
||||
| Server.setupOCSPStapleStoreDir | golang/nats-server/server/ocsp.go:560 | MISSING | — | No OCSP store directory setup |
|
||||
| tlsConfigKind (struct) | golang/nats-server/server/ocsp.go:577 | MISSING | — | No per-listener TLS config kind abstraction |
|
||||
| Server.configureOCSP | golang/nats-server/server/ocsp.go:585 | MISSING | — | No multi-listener OCSP configuration loop |
|
||||
| Server.enableOCSP | golang/nats-server/server/ocsp.go:680 | MISSING | — | No server-level OCSP enablement orchestrator |
|
||||
| Server.startOCSPMonitoring | golang/nats-server/server/ocsp.go:717 | MISSING | — | No OCSP monitor goroutine dispatcher |
|
||||
| Server.reloadOCSP | golang/nats-server/server/ocsp.go:734 | MISSING | — | No OCSP hot-reload support |
|
||||
| hasOCSPStatusRequest | golang/nats-server/server/ocsp.go:804 | MISSING | — | No MustStaple TLS extension detection |
|
||||
| OCSPMonitor.writeOCSPStatus | golang/nats-server/server/ocsp.go:840 | MISSING | — | No atomic file write for OCSP status persistence |
|
||||
| parseCertPEM | golang/nats-server/server/ocsp.go:867 | PARTIAL | src/NATS.Server/Tls/TlsHelper.cs:17 | `LoadCaCertificates` uses `ImportFromPemFile` but does not validate PEM block type |
|
||||
| getOCSPIssuerLocally | golang/nats-server/server/ocsp.go:892 | MISSING | — | No local issuer resolution from cert bundle |
|
||||
| getOCSPIssuer | golang/nats-server/server/ocsp.go:932 | MISSING | — | No issuer resolution logic |
|
||||
| ocspStatusString | golang/nats-server/server/ocsp.go:968 | PORTED | src/NATS.Server/Events/EventTypes.cs:647 | `OcspEventBuilder.ParseStatus` and `OcspStatus` enum |
|
||||
| validOCSPResponse | golang/nats-server/server/ocsp.go:979 | MISSING | — | No manual OCSP response time validation |
|
||||
|
||||
### ocsp_peer.go — OCSP peer certificate validation
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| parseOCSPPeer | golang/nats-server/server/ocsp_peer.go:29 | MISSING | — | No config-file parsing for OCSP peer options |
|
||||
| peerFromVerifiedChains | golang/nats-server/server/ocsp_peer.go:130 | MISSING | — | No peer extraction from verified chains |
|
||||
| Server.plugTLSOCSPPeer | golang/nats-server/server/ocsp_peer.go:138 | PARTIAL | src/NATS.Server/Tls/TlsHelper.cs:36 | .NET uses X509RevocationMode.Online when OcspPeerVerify set; missing full OCSP peer plugin pattern with per-chain validation |
|
||||
| Server.plugClientTLSOCSPPeer | golang/nats-server/server/ocsp_peer.go:163 | PARTIAL | src/NATS.Server/Tls/TlsHelper.cs:41 | RemoteCertificateValidationCallback with revocation check, but no OCSP-specific chain walking or event publishing |
|
||||
| Server.plugServerTLSOCSPPeer | golang/nats-server/server/ocsp_peer.go:183 | MISSING | — | No leaf-spoke server-side OCSP peer plug |
|
||||
| Server.tlsServerOCSPValid | golang/nats-server/server/ocsp_peer.go:209 | MISSING | — | No server-side verified-chain OCSP evaluation |
|
||||
| Server.tlsClientOCSPValid | golang/nats-server/server/ocsp_peer.go:220 | MISSING | — | No client-side verified-chain OCSP evaluation |
|
||||
| Server.peerOCSPValid | golang/nats-server/server/ocsp_peer.go:225 | MISSING | — | No multi-chain OCSP eligibility walker |
|
||||
| Server.certOCSPGood | golang/nats-server/server/ocsp_peer.go:286 | MISSING | — | No per-link OCSP response fetch, cache check, delegation validation, and policy overrides (WarnOnly, AllowWhenCAUnreachable) |
|
||||
| sendOCSPPeerRejectEvent | golang/nats-server/server/ocsp_peer.go (ref) | PARTIAL | src/NATS.Server/Events/EventTypes.cs:612 | `OcspEventBuilder.BuildPeerReject` exists as data builder but no actual event publishing to system account |
|
||||
| sendOCSPPeerChainlinkInvalidEvent | golang/nats-server/server/ocsp_peer.go (ref) | PARTIAL | src/NATS.Server/Events/EventTypes.cs:628 | `OcspEventBuilder.BuildChainValidation` exists as data builder but no actual event publishing |
|
||||
|
||||
### ocsp_responsecache.go — OCSP response caching
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| OCSPResponseCacheType (type) | golang/nats-server/server/ocsp_responsecache.go:44 | MISSING | — | No cache type enum (NONE/LOCAL) |
|
||||
| OCSPResponseCacheConfig | golang/nats-server/server/ocsp_responsecache.go:56 | MISSING | — | No OCSP cache configuration struct |
|
||||
| NewOCSPResponseCacheConfig | golang/nats-server/server/ocsp_responsecache.go:63 | MISSING | — | No cache config factory |
|
||||
| OCSPResponseCacheStats | golang/nats-server/server/ocsp_responsecache.go:72 | MISSING | — | No cache statistics tracking |
|
||||
| OCSPResponseCacheItem | golang/nats-server/server/ocsp_responsecache.go:81 | MISSING | — | No cache item model |
|
||||
| OCSPResponseCache (interface) | golang/nats-server/server/ocsp_responsecache.go:89 | MISSING | — | No cache interface (Put/Get/Delete/Start/Stop) |
|
||||
| NoOpCache (struct) | golang/nats-server/server/ocsp_responsecache.go:102 | MISSING | — | No no-op cache implementation |
|
||||
| LocalCache (struct) | golang/nats-server/server/ocsp_responsecache.go:155 | MISSING | — | No local file-backed OCSP response cache |
|
||||
| LocalCache.Put | golang/nats-server/server/ocsp_responsecache.go:167 | MISSING | — | No cache insert with S2 compression |
|
||||
| LocalCache.Get | golang/nats-server/server/ocsp_responsecache.go:201 | MISSING | — | No cache lookup with decompression |
|
||||
| LocalCache.Delete | golang/nats-server/server/ocsp_responsecache.go:245 | MISSING | — | No cache delete with preserve-revoked policy |
|
||||
| LocalCache.Start | golang/nats-server/server/ocsp_responsecache.go:272 | MISSING | — | No cache initialization from disk |
|
||||
| LocalCache.Stop | golang/nats-server/server/ocsp_responsecache.go:281 | MISSING | — | No cache shutdown and save |
|
||||
| LocalCache.Compress / Decompress | golang/nats-server/server/ocsp_responsecache.go:344 | MISSING | — | No S2 compression for OCSP responses |
|
||||
| LocalCache.loadCache / saveCache | golang/nats-server/server/ocsp_responsecache.go:371 | MISSING | — | No JSON-based disk persistence |
|
||||
| Server.initOCSPResponseCache | golang/nats-server/server/ocsp_responsecache.go:508 | MISSING | — | No cache initialization on server startup |
|
||||
| Server.startOCSPResponseCache | golang/nats-server/server/ocsp_responsecache.go:546 | MISSING | — | No cache start on server startup |
|
||||
| Server.stopOCSPResponseCache | golang/nats-server/server/ocsp_responsecache.go:564 | MISSING | — | No cache stop on server shutdown |
|
||||
| parseOCSPResponseCache | golang/nats-server/server/ocsp_responsecache.go:574 | MISSING | — | No config-file parsing for OCSP cache options |
|
||||
|
||||
### ciphersuites.go — TLS cipher suite management and filtering
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| cipherMap (var) | golang/nats-server/server/ciphersuites.go:32 | MISSING | — | No cipher name-to-suite mapping; .NET handles cipher suites via SslServerAuthenticationOptions |
|
||||
| cipherMapByID (var) | golang/nats-server/server/ciphersuites.go:33 | MISSING | — | No cipher ID-to-suite mapping |
|
||||
| defaultCipherSuites | golang/nats-server/server/ciphersuites.go:35 | NOT_APPLICABLE | — | .NET TLS stack manages default cipher suites internally; no explicit enumeration needed |
|
||||
| curvePreferenceMap (var) | golang/nats-server/server/ciphersuites.go:45 | NOT_APPLICABLE | — | .NET does not expose curve preference ordering at application level |
|
||||
| defaultCurvePreferences | golang/nats-server/server/ciphersuites.go:55 | NOT_APPLICABLE | — | .NET runtime handles curve negotiation; FIPS mode handled by OS/.NET runtime config |
|
||||
| init() cipher registration | golang/nats-server/server/ciphersuites.go:21 | NOT_APPLICABLE | — | Go-specific init pattern; .NET populates cipher lists differently |
|
||||
|
||||
### client_proxyproto.go — PROXY protocol v1/v2 support
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| proxyProtoV2 constants | golang/nats-server/server/client_proxyproto.go:28 | PORTED | src/NATS.Server/Protocol/ProxyProtocol.cs:54 | All v2 constants mirrored |
|
||||
| proxyProtoV1 constants | golang/nats-server/server/client_proxyproto.go:63 | PORTED | src/NATS.Server/Protocol/ProxyProtocol.cs:82 | All v1 constants mirrored |
|
||||
| proxyProtoAddr (struct) | golang/nats-server/server/client_proxyproto.go:81 | PORTED | src/NATS.Server/Protocol/ProxyProtocol.cs:11 | `ProxyAddress` class with SrcIp, SrcPort, DstIp, DstPort |
|
||||
| proxyProtoAddr.String | golang/nats-server/server/client_proxyproto.go:89 | PORTED | src/NATS.Server/Protocol/ProxyProtocol.cs:20 | `ProxyAddress.ToString()` with IPv6 bracket formatting |
|
||||
| proxyProtoAddr.Network | golang/nats-server/server/client_proxyproto.go:94 | PORTED | src/NATS.Server/Protocol/ProxyProtocol.cs:18 | `ProxyAddress.Network` property |
|
||||
| proxyConn (struct) | golang/nats-server/server/client_proxyproto.go:102 | MISSING | — | No connection wrapper; .NET parser is pure buffer-based, integration with accept loop not yet wired |
|
||||
| proxyConn.RemoteAddr | golang/nats-server/server/client_proxyproto.go:109 | MISSING | — | Part of proxyConn; accept loop integration needed |
|
||||
| detectProxyProtoVersion | golang/nats-server/server/client_proxyproto.go:116 | PORTED | src/NATS.Server/Protocol/ProxyProtocol.cs:91 | `ProxyProtocolParser.Parse` auto-detects v1/v2 from first 6 bytes |
|
||||
| readProxyProtoV1Header | golang/nats-server/server/client_proxyproto.go:134 | PORTED | src/NATS.Server/Protocol/ProxyProtocol.cs:117 | `ProxyProtocolParser.ParseV1` — buffer-based, all edge cases handled |
|
||||
| readProxyProtoHeader | golang/nats-server/server/client_proxyproto.go:226 | PORTED | src/NATS.Server/Protocol/ProxyProtocol.cs:91 | `ProxyProtocolParser.Parse` covers both versions |
|
||||
| readProxyProtoV2Header | golang/nats-server/server/client_proxyproto.go:274 | PORTED | src/NATS.Server/Protocol/ProxyProtocol.cs:190 | `ProxyProtocolParser.ParseV2` |
|
||||
| parseProxyProtoV2Header | golang/nats-server/server/client_proxyproto.go:301 | PORTED | src/NATS.Server/Protocol/ProxyProtocol.cs:208 | `ProxyProtocolParser.ParseV2AfterSig` |
|
||||
| parseIPv4Addr | golang/nats-server/server/client_proxyproto.go:365 | PORTED | src/NATS.Server/Protocol/ProxyProtocol.cs:250 | `ParseIPv4` private method |
|
||||
| parseIPv6Addr | golang/nats-server/server/client_proxyproto.go:383 | PORTED | src/NATS.Server/Protocol/ProxyProtocol.cs:269 | `ParseIPv6` private method |
|
||||
| error variables | golang/nats-server/server/client_proxyproto.go:73 | PORTED | src/NATS.Server/Protocol/ProxyProtocol.cs:353 | `ProxyProtocolException` and `ProxyProtocolUnsupportedException` |
|
||||
|
||||
### certidp/certidp.go — Certificate identity provider
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| DefaultAllowedClockSkew | golang/nats-server/server/certidp/certidp.go:30 | MISSING | — | No OCSP clock skew constant |
|
||||
| DefaultOCSPResponderTimeout | golang/nats-server/server/certidp/certidp.go:31 | MISSING | — | No OCSP responder timeout constant |
|
||||
| DefaultTTLUnsetNextUpdate | golang/nats-server/server/certidp/certidp.go:32 | MISSING | — | No default TTL when NextUpdate is unset |
|
||||
| StatusAssertion (type) | golang/nats-server/server/certidp/certidp.go:35 | PARTIAL | src/NATS.Server/Events/EventTypes.cs:595 | `OcspStatus` enum exists (Good, Revoked, Unknown) but no JSON marshal/unmarshal or bidirectional maps |
|
||||
| GetStatusAssertionStr | golang/nats-server/server/certidp/certidp.go:56 | PORTED | src/NATS.Server/Events/EventTypes.cs:647 | `OcspEventBuilder.ParseStatus` provides string-to-enum; reverse mapping implicit |
|
||||
| ChainLink (struct) | golang/nats-server/server/certidp/certidp.go:93 | MISSING | — | No chain link struct with Leaf/Issuer/OCSPWebEndpoints |
|
||||
| OCSPPeerConfig (struct) | golang/nats-server/server/certidp/certidp.go:100 | MISSING | — | No OCSP peer config struct (Verify, Timeout, ClockSkew, WarnOnly, UnknownIsGood, AllowWhenCAUnreachable, TTLUnsetNextUpdate) |
|
||||
| NewOCSPPeerConfig | golang/nats-server/server/certidp/certidp.go:110 | MISSING | — | No peer config factory |
|
||||
| Log (struct) | golang/nats-server/server/certidp/certidp.go:123 | NOT_APPLICABLE | — | .NET uses ILogger<T> injection; no need for function-pointer log struct |
|
||||
| CertInfo (struct) | golang/nats-server/server/certidp/certidp.go:131 | MISSING | — | No cert info DTO for events |
|
||||
| GenerateFingerprint | golang/nats-server/server/certidp/certidp.go:179 | PARTIAL | src/NATS.Server/Tls/TlsHelper.cs:88 | `GetCertificateHash` uses SHA256 on SPKI (not raw cert as Go does); different hash input |
|
||||
| getWebEndpoints | golang/nats-server/server/certidp/certidp.go:184 | MISSING | — | No OCSP endpoint URL extraction/filtering |
|
||||
| GetSubjectDNForm | golang/nats-server/server/certidp/certidp.go:203 | MISSING | — | No subject RDN sequence formatting |
|
||||
| GetIssuerDNForm | golang/nats-server/server/certidp/certidp.go:212 | MISSING | — | No issuer RDN sequence formatting |
|
||||
| CertOCSPEligible | golang/nats-server/server/certidp/certidp.go:221 | MISSING | — | No OCSP eligibility check based on AIA extension |
|
||||
| GetLeafIssuerCert | golang/nats-server/server/certidp/certidp.go:237 | MISSING | — | No positional issuer extraction from chain |
|
||||
| OCSPResponseCurrent | golang/nats-server/server/certidp/certidp.go:250 | MISSING | — | No OCSP response currency check with clock skew and TTL fallback |
|
||||
| ValidDelegationCheck | golang/nats-server/server/certidp/certidp.go:288 | MISSING | — | No OCSP response delegation validation per RFC 6960 section 4.2.2.2 |
|
||||
|
||||
### certidp/messages.go — CertIDP message types
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| Error message constants | golang/nats-server/server/certidp/messages.go:17 | MISSING | — | No equivalent error/debug message constants; .NET uses structured logging |
|
||||
| Debug message constants | golang/nats-server/server/certidp/messages.go:47 | MISSING | — | Debug format strings not ported; .NET logs differently |
|
||||
| MsgTLSClientRejectConnection | golang/nats-server/server/certidp/messages.go:81 | PARTIAL | src/NATS.Server/Events/EventTypes.cs:520 | Reject event type exists but literal reject reason string not exposed |
|
||||
| MsgTLSServerRejectConnection | golang/nats-server/server/certidp/messages.go:82 | PARTIAL | src/NATS.Server/Events/EventTypes.cs:520 | Same as above |
|
||||
| MsgCacheOnline / MsgCacheOffline | golang/nats-server/server/certidp/messages.go:96 | MISSING | — | No cache status notification messages |
|
||||
|
||||
### certidp/ocsp_responder.go — OCSP responder client
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| FetchOCSPResponse | golang/nats-server/server/certidp/ocsp_responder.go:29 | MISSING | — | No HTTP-based OCSP response fetcher (GET with base64 encoding); .NET relies on X509Chain revocation |
|
||||
| encodeOCSPRequest | golang/nats-server/server/certidp/ocsp_responder.go:89 | MISSING | — | No DER-to-base64-to-URL encoding for OCSP requests |
|
||||
|
||||
### certstore/certstore.go — OS certificate store access
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| StoreType (type) | golang/nats-server/server/certstore/certstore.go:24 | NOT_APPLICABLE | — | .NET has native X509Store; no need for Windows-specific store type enum |
|
||||
| StoreMap / StoreOSMap | golang/nats-server/server/certstore/certstore.go:34 | NOT_APPLICABLE | — | .NET handles store locations via X509StoreLocation enum |
|
||||
| MatchByType (type) | golang/nats-server/server/certstore/certstore.go:44 | NOT_APPLICABLE | — | .NET can use X509FindType enum for similar functionality |
|
||||
| MatchByMap | golang/nats-server/server/certstore/certstore.go:52 | NOT_APPLICABLE | — | .NET equivalent: X509FindType |
|
||||
| ParseCertStore | golang/nats-server/server/certstore/certstore.go:68 | NOT_APPLICABLE | — | .NET has built-in X509Store with StoreLocation |
|
||||
| ParseCertMatchBy | golang/nats-server/server/certstore/certstore.go:80 | NOT_APPLICABLE | — | .NET has X509FindType |
|
||||
| GetLeafIssuer | golang/nats-server/server/certstore/certstore.go:88 | MISSING | — | Could port using X509Chain verification to find issuer |
|
||||
| credential (interface) | golang/nats-server/server/certstore/certstore.go:99 | NOT_APPLICABLE | — | .NET uses X509Certificate2 with private key; no separate credential interface needed |
|
||||
|
||||
### certstore/certstore_other.go — Non-Windows cert store stub
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| otherKey (struct) | golang/nats-server/server/certstore/certstore_other.go:27 | NOT_APPLICABLE | — | Go build-tag stub for non-Windows; .NET X509Store is cross-platform |
|
||||
| TLSConfig (stub) | golang/nats-server/server/certstore/certstore_other.go:29 | NOT_APPLICABLE | — | Build-tag stub; not needed in .NET |
|
||||
|
||||
### certstore/certstore_windows.go — Windows cert store
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| Windows CNG/NCrypt constants | golang/nats-server/server/certstore/certstore_windows.go:42 | NOT_APPLICABLE | — | .NET X509Store abstracts away Windows CNG; no P/Invoke needed |
|
||||
| winCertStore (struct) | golang/nats-server/server/certstore/certstore_windows.go:352 | NOT_APPLICABLE | — | .NET X509Store handles store access natively |
|
||||
| TLSConfig (Windows) | golang/nats-server/server/certstore/certstore_windows.go:202 | NOT_APPLICABLE | — | .NET can use X509Store + X509Certificate2 directly; no syscall-level TLS config needed |
|
||||
| winKey (struct) | golang/nats-server/server/certstore/certstore_windows.go:528 | NOT_APPLICABLE | — | .NET X509Certificate2.GetRSAPrivateKey/GetECDsaPrivateKey handles this |
|
||||
| winSignECDSA / winSignRSA* | golang/nats-server/server/certstore/certstore_windows.go:562 | NOT_APPLICABLE | — | .NET runtime handles signing through X509Certificate2 private key |
|
||||
| createCACertsPool | golang/nats-server/server/certstore/certstore_windows.go:173 | NOT_APPLICABLE | — | .NET uses X509Certificate2Collection / X509Chain for CA pool |
|
||||
| certByIssuer / certBySubject / certByThumbprint | golang/nats-server/server/certstore/certstore_windows.go:390 | NOT_APPLICABLE | — | .NET uses X509Store.Certificates.Find() with X509FindType |
|
||||
| unmarshalECC / winUnmarshalRSA | golang/nats-server/server/certstore/certstore_windows.go:844 | NOT_APPLICABLE | — | .NET BCL handles key deserialization |
|
||||
|
||||
### certstore/errors.go — Cert store errors
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| ErrBadCryptoStoreProvider | golang/nats-server/server/certstore/errors.go:9 | NOT_APPLICABLE | — | .NET X509Store throws its own exceptions |
|
||||
| ErrBadRSAHashAlgorithm | golang/nats-server/server/certstore/errors.go:12 | NOT_APPLICABLE | — | .NET runtime handles hash algorithm validation |
|
||||
| ErrBadSigningAlgorithm | golang/nats-server/server/certstore/errors.go:15 | NOT_APPLICABLE | — | .NET runtime handles signing |
|
||||
| ErrStoreRSASigningError | golang/nats-server/server/certstore/errors.go:18 | NOT_APPLICABLE | — | .NET CryptographicException covers this |
|
||||
| ErrStoreECDSASigningError | golang/nats-server/server/certstore/errors.go:21 | NOT_APPLICABLE | — | .NET CryptographicException covers this |
|
||||
| ErrNoPrivateKeyStoreRef | golang/nats-server/server/certstore/errors.go:24 | NOT_APPLICABLE | — | .NET checks HasPrivateKey |
|
||||
| ErrExtractingPrivateKeyMetadata | golang/nats-server/server/certstore/errors.go:27 | NOT_APPLICABLE | — | .NET handles internally |
|
||||
| ErrExtractingECCPublicKey | golang/nats-server/server/certstore/errors.go:30 | NOT_APPLICABLE | — | .NET handles internally |
|
||||
| ErrExtractingRSAPublicKey | golang/nats-server/server/certstore/errors.go:33 | NOT_APPLICABLE | — | .NET handles internally |
|
||||
| ErrExtractingPublicKey | golang/nats-server/server/certstore/errors.go:36 | NOT_APPLICABLE | — | .NET handles internally |
|
||||
| ErrBadPublicKeyAlgorithm | golang/nats-server/server/certstore/errors.go:39 | NOT_APPLICABLE | — | .NET handles internally |
|
||||
| ErrExtractPropertyFromKey | golang/nats-server/server/certstore/errors.go:42 | NOT_APPLICABLE | — | .NET handles internally |
|
||||
| ErrBadECCCurveName | golang/nats-server/server/certstore/errors.go:45 | NOT_APPLICABLE | — | .NET handles internally |
|
||||
| ErrFailedCertSearch | golang/nats-server/server/certstore/errors.go:48 | NOT_APPLICABLE | — | .NET X509Store.Find returns empty collection |
|
||||
| ErrFailedX509Extract | golang/nats-server/server/certstore/errors.go:51 | NOT_APPLICABLE | — | .NET handles internally |
|
||||
| ErrBadMatchByType | golang/nats-server/server/certstore/errors.go:54 | NOT_APPLICABLE | — | .NET uses enum |
|
||||
| ErrBadCertStore | golang/nats-server/server/certstore/errors.go:57 | NOT_APPLICABLE | — | .NET uses enum |
|
||||
| ErrConflictCertFileAndStore | golang/nats-server/server/certstore/errors.go:60 | MISSING | — | Config validation for conflicting cert_file + cert_store not ported |
|
||||
| ErrBadCertStoreField | golang/nats-server/server/certstore/errors.go:63 | NOT_APPLICABLE | — | Config validation |
|
||||
| ErrBadCertMatchByField | golang/nats-server/server/certstore/errors.go:66 | NOT_APPLICABLE | — | Config validation |
|
||||
| ErrBadCertMatchField | golang/nats-server/server/certstore/errors.go:69 | NOT_APPLICABLE | — | Config validation |
|
||||
| ErrBadCaCertMatchField | golang/nats-server/server/certstore/errors.go:72 | NOT_APPLICABLE | — | Config validation |
|
||||
| ErrBadCertMatchSkipInvalidField | golang/nats-server/server/certstore/errors.go:75 | NOT_APPLICABLE | — | Config validation |
|
||||
| ErrOSNotCompatCertStore | golang/nats-server/server/certstore/errors.go:78 | NOT_APPLICABLE | — | .NET X509Store is cross-platform |
|
||||
|
||||
---
|
||||
|
||||
## 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/Tls/ -name '*.cs' -type f -exec cat {} + | wc -l
|
||||
# Re-count .NET test LOC for this module
|
||||
# TLS tests are in root test directory — filter manually
|
||||
```
|
||||
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-25 | File created with LLM analysis instructions | auto |
|
||||
| 2026-02-25 | Completed full gap inventory: 12 Go source files analyzed, 144 symbols classified (20 PORTED, 9 PARTIAL, 70 MISSING, 45 NOT_APPLICABLE, 0 DEFERRED) | claude-opus |
|
||||
448
gaps/utilities-and-other.md
Normal file
448
gaps/utilities-and-other.md
Normal file
@@ -0,0 +1,448 @@
|
||||
# Utilities & Other — Gap Analysis
|
||||
|
||||
> This file tracks what has and hasn't been ported from Go to .NET for the **Utilities & Other** 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 Utilities & Other
|
||||
|
||||
- This category is at **7% LOC parity** — significant gap, but many items may be NOT_APPLICABLE:
|
||||
- `sendq.go` / `ipqueue.go` — Go channel-based patterns; .NET uses `Channel<T>` or different buffering
|
||||
- `ring.go` — May be replaced by .NET collections
|
||||
- `ats/` — Address translation for NAT environments
|
||||
- `elastic/` — Elasticsearch metric export (may be deferred)
|
||||
- `tpm/` — Trusted Platform Module integration (platform-specific, likely DEFERRED)
|
||||
- `internal/fastrand` — Go's fast random; .NET has `Random.Shared`
|
||||
- `internal/ldap` — LDAP DN parsing for LDAP auth
|
||||
- `internal/antithesis` — Chaos testing assertions (NOT_APPLICABLE unless using Antithesis)
|
||||
- Focus on: `util.go` (byte parsing helpers), `errors.go` (error definitions), `rate_counter.go`, `scheduler.go`.
|
||||
|
||||
---
|
||||
|
||||
## Go Reference Files (Source)
|
||||
|
||||
- `golang/nats-server/server/util.go` — General utilities (string helpers, byte parsing, etc.)
|
||||
- `golang/nats-server/server/ring.go` — Ring buffer implementation
|
||||
- `golang/nats-server/server/rate_counter.go` — Rate limiting / counting
|
||||
- `golang/nats-server/server/sendq.go` — Send queue for outbound messages
|
||||
- `golang/nats-server/server/ipqueue.go` — IP-based queue
|
||||
- `golang/nats-server/server/errors.go` — Error type definitions
|
||||
- `golang/nats-server/server/errors_gen.go` — Generated error codes
|
||||
- `golang/nats-server/server/sdm.go` — Server dynamic management
|
||||
- `golang/nats-server/server/scheduler.go` — Internal task scheduler
|
||||
- `golang/nats-server/server/ats/ats.go` — Address translation service
|
||||
- `golang/nats-server/server/elastic/elastic.go` — Elasticsearch integration
|
||||
- `golang/nats-server/server/tpm/js_ek_tpm_windows.go` — TPM key management (Windows)
|
||||
- `golang/nats-server/server/tpm/js_ek_tpm_other.go` — TPM key management (non-Windows)
|
||||
- `golang/nats-server/internal/` — Internal utility packages (fastrand, ldap, ocsp, testhelper, antithesis)
|
||||
|
||||
## Go Reference Files (Tests)
|
||||
|
||||
- `golang/nats-server/server/util_test.go`
|
||||
- `golang/nats-server/server/ring_test.go`
|
||||
- `golang/nats-server/server/rate_counter_test.go`
|
||||
- `golang/nats-server/server/ipqueue_test.go`
|
||||
- `golang/nats-server/server/errors_test.go`
|
||||
|
||||
## .NET Implementation Files (Source)
|
||||
|
||||
- `src/NATS.Server/IO/OutboundBufferPool.cs`
|
||||
- `src/NATS.Server/IO/AdaptiveReadBuffer.cs`
|
||||
- `src/NATS.Server/Server/AcceptLoopErrorHandler.cs`
|
||||
|
||||
## .NET Implementation Files (Tests)
|
||||
|
||||
- `tests/NATS.Server.Tests/IO/`
|
||||
|
||||
---
|
||||
|
||||
## Gap Inventory
|
||||
|
||||
### `golang/nats-server/server/util.go`
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `refCountedUrlSet` (type alias) | golang/nats-server/server/util.go:34 | NOT_APPLICABLE | — | Internal clustering gossip helper; no equivalent needed in .NET port yet (clustering/gateway not fully ported) |
|
||||
| `refCountedUrlSet.addUrl` | golang/nats-server/server/util.go:203 | NOT_APPLICABLE | — | Part of gossip URL ref-counting; used only in route/gateway code not yet ported |
|
||||
| `refCountedUrlSet.removeUrl` | golang/nats-server/server/util.go:212 | NOT_APPLICABLE | — | Same as above |
|
||||
| `refCountedUrlSet.getAsStringSlice` | golang/nats-server/server/util.go:226 | NOT_APPLICABLE | — | Same as above |
|
||||
| `versionComponents` (unexported) | golang/nats-server/server/util.go:42 | PORTED | tests/NATS.Server.Tests/InfrastructureGoParityTests.cs:992 | Inlined as `VersionAtLeast` helper in test file; production equivalent used in server |
|
||||
| `versionAtLeastCheckError` (unexported) | golang/nats-server/server/util.go:62 | PORTED | tests/NATS.Server.Tests/InfrastructureGoParityTests.cs:992 | Logic folded into `VersionAtLeast` test helper |
|
||||
| `versionAtLeast` (unexported) | golang/nats-server/server/util.go:75 | PORTED | tests/NATS.Server.Tests/InfrastructureGoParityTests.cs:992 | Ported as `VersionAtLeast` in InfrastructureGoParityTests; production uses `System.Version` comparisons |
|
||||
| `parseSize` (unexported) | golang/nats-server/server/util.go:82 | PORTED | src/NATS.Server/Protocol/NatsParser.cs:434 | `NatsParser.ParseSize(Span<byte>)` — exact behavioral match including -1 on error; tested in InfrastructureGoParityTests |
|
||||
| `parseInt64` (unexported) | golang/nats-server/server/util.go:113 | PORTED | src/NATS.Server/Protocol/NatsParser.cs:434 | Folded into `ParseSize` / inline parser logic; behavior covered by parser tests |
|
||||
| `secondsToDuration` (unexported) | golang/nats-server/server/util.go:127 | NOT_APPLICABLE | — | Go-specific `time.Duration` helper; .NET uses `TimeSpan.FromSeconds(double)` directly |
|
||||
| `parseHostPort` (unexported) | golang/nats-server/server/util.go:134 | PARTIAL | src/NATS.Server/Configuration/ConfigProcessor.cs | ConfigProcessor parses `host:port` strings but does not have a standalone `ParseHostPort` helper matching Go's default-port fallback logic |
|
||||
| `urlsAreEqual` (unexported) | golang/nats-server/server/util.go:158 | NOT_APPLICABLE | — | Uses `reflect.DeepEqual` on `*url.URL`; not needed in .NET port (no equivalent URL gossip pattern yet) |
|
||||
| `comma` (unexported) | golang/nats-server/server/util.go:169 | PORTED | tests/NATS.Server.Tests/InfrastructureGoParityTests.cs:960 | Ported as `CommaFormat` helper in InfrastructureGoParityTests (test-side only); monitoring output uses `string.Format("{0:N0}")` in production |
|
||||
| `natsListenConfig` | golang/nats-server/server/util.go:246 | PORTED | src/NATS.Server/NatsServer.cs | .NET `TcpListener` / `Socket` used without OS TCP keepalives; keepalive is disabled by default in .NET socket setup matching Go behavior |
|
||||
| `natsListen` (unexported) | golang/nats-server/server/util.go:252 | PORTED | src/NATS.Server/NatsServer.cs | Equivalent accept loop uses `TcpListener.AcceptTcpClientAsync` without system keepalives |
|
||||
| `natsDialTimeout` (unexported) | golang/nats-server/server/util.go:258 | PARTIAL | src/NATS.Server/Routes/RouteConnection.cs | Route dialing exists but the explicit keepalive=-1 (disabled) setting is not verified in .NET route code |
|
||||
| `redactURLList` (unexported) | golang/nats-server/server/util.go:270 | PARTIAL | tests/NATS.Server.Tests/InfrastructureGoParityTests.cs:979 | `RedactUrl` helper ported in test file; no production-side `redactURLList` for URL slices |
|
||||
| `redactURLString` (unexported) | golang/nats-server/server/util.go:296 | PORTED | tests/NATS.Server.Tests/InfrastructureGoParityTests.cs:979 | `RedactUrl` in InfrastructureGoParityTests matches behavior; also used in log redaction |
|
||||
| `getURLsAsString` (unexported) | golang/nats-server/server/util.go:308 | NOT_APPLICABLE | — | Internal URL slice utility for clustering; not needed in current .NET scope |
|
||||
| `copyBytes` (unexported) | golang/nats-server/server/util.go:317 | PORTED | (inline) | .NET uses `ReadOnlySpan<byte>.ToArray()`, `Array.Copy`, or `Buffer.BlockCopy` equivalently throughout the codebase |
|
||||
| `copyStrings` (unexported) | golang/nats-server/server/util.go:328 | NOT_APPLICABLE | — | Go slice copying idiom; .NET uses LINQ `.ToArray()` or list copy directly |
|
||||
| `generateInfoJSON` (unexported) | golang/nats-server/server/util.go:339 | PORTED | src/NATS.Server/Protocol/NatsProtocol.cs | `NatsProtocol.WriteInfo()` writes the `INFO …\r\n` frame directly to the write buffer |
|
||||
| `parallelTaskQueue` (unexported) | golang/nats-server/server/util.go:350 | NOT_APPLICABLE | — | Go goroutine pool pattern; .NET uses `Parallel.ForEach`, `Task.WhenAll`, or `Channel<T>` for equivalent parallelism |
|
||||
|
||||
### `golang/nats-server/server/ring.go`
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `closedClient` (type) | golang/nats-server/server/ring.go:17 | PORTED | src/NATS.Server/Monitoring/ClosedClient.cs | `ClosedClient` C# record mirrors the Go struct; includes connection info, subscription details |
|
||||
| `closedRingBuffer` (type) | golang/nats-server/server/ring.go:25 | PORTED | src/NATS.Server/Monitoring/ClosedConnectionRingBuffer.cs | `ClosedConnectionRingBuffer` — full ring buffer with capacity, count, totalClosed, thread-safe via `Lock` |
|
||||
| `newClosedRingBuffer` | golang/nats-server/server/ring.go:31 | PORTED | src/NATS.Server/Monitoring/ClosedConnectionRingBuffer.cs:16 | Constructor with capacity parameter |
|
||||
| `closedRingBuffer.append` | golang/nats-server/server/ring.go:39 | PORTED | src/NATS.Server/Monitoring/ClosedConnectionRingBuffer.cs:37 | `Add(ClosedClient)` — overwrites oldest on overflow |
|
||||
| `closedRingBuffer.next` (unexported) | golang/nats-server/server/ring.go:44 | PORTED | src/NATS.Server/Monitoring/ClosedConnectionRingBuffer.cs | Inlined into `Add` — `_head = (_head + 1) % _buffer.Length` |
|
||||
| `closedRingBuffer.len` (unexported) | golang/nats-server/server/ring.go:48 | PORTED | src/NATS.Server/Monitoring/ClosedConnectionRingBuffer.cs:24 | `Count` property |
|
||||
| `closedRingBuffer.totalConns` (unexported) | golang/nats-server/server/ring.go:55 | PORTED | src/NATS.Server/Monitoring/ClosedConnectionRingBuffer.cs:29 | `TotalClosed` property |
|
||||
| `closedRingBuffer.closedClients` (unexported) | golang/nats-server/server/ring.go:65 | PORTED | src/NATS.Server/Monitoring/ClosedConnectionRingBuffer.cs:52 | `GetAll()` and `GetRecent(int)` — returns sorted copy; .NET returns newest-first (monitoring convention) |
|
||||
|
||||
### `golang/nats-server/server/rate_counter.go`
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `rateCounter` (type) | golang/nats-server/server/rate_counter.go:21 | PORTED | src/NATS.Server/Tls/TlsRateLimiter.cs | `TlsRateLimiter` provides equivalent token-bucket rate limiting for TLS handshakes; also `ApiRateLimiter` for JetStream API |
|
||||
| `newRateCounter` | golang/nats-server/server/rate_counter.go:30 | PORTED | src/NATS.Server/Tls/TlsRateLimiter.cs:9 | `TlsRateLimiter(long tokensPerSecond)` constructor |
|
||||
| `rateCounter.allow` (unexported) | golang/nats-server/server/rate_counter.go:37 | PARTIAL | src/NATS.Server/Tls/TlsRateLimiter.cs:22 | `WaitAsync(CancellationToken)` is async/blocking rather than a synchronous allow-or-deny check; Go's `allow()` is non-blocking |
|
||||
| `rateCounter.countBlocked` (unexported) | golang/nats-server/server/rate_counter.go:58 | MISSING | — | No equivalent "count blocked requests" metric; `TlsRateLimiter` does not expose a blocked-count stat |
|
||||
|
||||
### `golang/nats-server/server/sendq.go`
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `outMsg` (type) | golang/nats-server/server/sendq.go:21 | NOT_APPLICABLE | — | Internal message envelope for Go goroutine-to-goroutine delivery; .NET internal system events use `Channel<T>` with typed event objects directly |
|
||||
| `sendq` (type) | golang/nats-server/server/sendq.go:28 | NOT_APPLICABLE | — | Go-specific goroutine-based internal send queue for system account messages; .NET equivalent is `Channel<T>` or `InternalEventSystem` dispatch |
|
||||
| `Server.newSendQ` | golang/nats-server/server/sendq.go:35 | NOT_APPLICABLE | — | Factory for goroutine-based queue; not applicable to .NET async model |
|
||||
| `sendq.internalLoop` (unexported) | golang/nats-server/server/sendq.go:41 | NOT_APPLICABLE | — | Go goroutine event loop; .NET uses `await foreach` on `Channel<T>` reader |
|
||||
| `outMsgPool` | golang/nats-server/server/sendq.go:103 | NOT_APPLICABLE | — | `sync.Pool` for `outMsg` structs; .NET uses `ArrayPool<byte>` or object pools via `System.Buffers` |
|
||||
| `sendq.send` | golang/nats-server/server/sendq.go:109 | NOT_APPLICABLE | — | Public entry point for internal system message dispatch; .NET internal event dispatch uses `InternalEventSystem.PublishAsync` |
|
||||
|
||||
### `golang/nats-server/server/ipqueue.go`
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `ipQueue[T]` (generic type) | golang/nats-server/server/ipqueue.go:25 | NOT_APPLICABLE | — | Go generics-based intra-process queue using goroutine channels; .NET uses `System.Threading.Channels.Channel<T>` which provides the same producer-consumer semantics natively |
|
||||
| `ipQueueOpts[T]` | golang/nats-server/server/ipqueue.go:38 | NOT_APPLICABLE | — | Options struct for ipQueue; .NET `Channel.CreateBounded/CreateUnbounded` options cover equivalent functionality |
|
||||
| `ipQueueOpt[T]` (function type) | golang/nats-server/server/ipqueue.go:45 | NOT_APPLICABLE | — | Functional options pattern; .NET uses `BoundedChannelOptions` |
|
||||
| `ipqMaxRecycleSize[T]` | golang/nats-server/server/ipqueue.go:49 | NOT_APPLICABLE | — | Pool recycling threshold; not needed with .NET GC |
|
||||
| `ipqSizeCalculation[T]` | golang/nats-server/server/ipqueue.go:57 | NOT_APPLICABLE | — | Size tracking callback; .NET channels track count natively |
|
||||
| `ipqLimitBySize[T]` | golang/nats-server/server/ipqueue.go:68 | NOT_APPLICABLE | — | Capacity limiting by byte size; .NET `BoundedChannelOptions.Capacity` limits by count; byte-size limiting is MISSING but not critical for current port scope |
|
||||
| `ipqLimitByLen[T]` | golang/nats-server/server/ipqueue.go:77 | NOT_APPLICABLE | — | Capacity limiting by count; maps to `BoundedChannelOptions.Capacity` |
|
||||
| `errIPQLenLimitReached` | golang/nats-server/server/ipqueue.go:83 | NOT_APPLICABLE | — | Error sentinel for capacity; .NET channels return `false` from `TryWrite` when full |
|
||||
| `errIPQSizeLimitReached` | golang/nats-server/server/ipqueue.go:84 | NOT_APPLICABLE | — | Same as above for size-based limit |
|
||||
| `newIPQueue[T]` | golang/nats-server/server/ipqueue.go:86 | NOT_APPLICABLE | — | Factory; equivalent is `Channel.CreateBounded<T>` or `Channel.CreateUnbounded<T>` |
|
||||
| `ipQueue[T].push` | golang/nats-server/server/ipqueue.go:113 | NOT_APPLICABLE | — | Maps to `ChannelWriter<T>.TryWrite` |
|
||||
| `ipQueue[T].pop` | golang/nats-server/server/ipqueue.go:152 | NOT_APPLICABLE | — | Maps to `ChannelReader<T>.ReadAllAsync` |
|
||||
| `ipQueue[T].popOne` | golang/nats-server/server/ipqueue.go:178 | NOT_APPLICABLE | — | Maps to `ChannelReader<T>.TryRead` |
|
||||
| `ipQueue[T].recycle` | golang/nats-server/server/ipqueue.go:215 | NOT_APPLICABLE | — | Slice pool return; not needed with .NET GC |
|
||||
| `ipQueue[T].len` | golang/nats-server/server/ipqueue.go:234 | NOT_APPLICABLE | — | Maps to `ChannelReader<T>.Count` |
|
||||
| `ipQueue[T].size` | golang/nats-server/server/ipqueue.go:242 | NOT_APPLICABLE | — | Byte-size tracking; no direct .NET equivalent but not needed |
|
||||
| `ipQueue[T].drain` | golang/nats-server/server/ipqueue.go:253 | NOT_APPLICABLE | — | Flush/clear; .NET channels are drained by completing the writer and reading remaining items |
|
||||
| `ipQueue[T].inProgress` | golang/nats-server/server/ipqueue.go:275 | NOT_APPLICABLE | — | In-flight count tracking; not needed in .NET model |
|
||||
| `ipQueue[T].unregister` | golang/nats-server/server/ipqueue.go:281 | NOT_APPLICABLE | — | Remove from server map; .NET uses `ConcurrentDictionary.TryRemove` |
|
||||
|
||||
### `golang/nats-server/server/errors.go`
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `ErrConnectionClosed` | golang/nats-server/server/errors.go:23 | PORTED | src/NATS.Server/Protocol/NatsProtocol.cs | Protocol-level error strings defined as constants in `NatsProtocol`; connection close reason tracked via `ClientClosedReason` enum |
|
||||
| `ErrAuthentication` | golang/nats-server/server/errors.go:26 | PORTED | src/NATS.Server/Protocol/NatsProtocol.cs:30 | `ErrAuthorizationViolation` constant |
|
||||
| `ErrAuthTimeout` | golang/nats-server/server/errors.go:29 | PORTED | src/NATS.Server/NatsClient.cs | Auth timeout enforced; close reason `AuthTimeout` in `ClientClosedReason` |
|
||||
| `ErrAuthExpired` | golang/nats-server/server/errors.go:32 | PORTED | src/NATS.Server/NatsClient.cs | Auth expiry enforced via connection deadline |
|
||||
| `ErrAuthProxyNotTrusted` | golang/nats-server/server/errors.go:35 | PORTED | src/NATS.Server/Auth/ | Proxy trust handled in auth layer |
|
||||
| `ErrAuthProxyRequired` | golang/nats-server/server/errors.go:39 | PORTED | src/NATS.Server/Auth/ | Proxy requirement enforced |
|
||||
| `ErrMaxPayload` | golang/nats-server/server/errors.go:43 | PORTED | src/NATS.Server/Protocol/NatsProtocol.cs:27 | `ErrMaxPayloadViolation` constant |
|
||||
| `ErrMaxControlLine` | golang/nats-server/server/errors.go:46 | PORTED | src/NATS.Server/Protocol/NatsParser.cs | Parser throws when control line exceeds `MaxControlLineSize` |
|
||||
| `ErrReservedPublishSubject` | golang/nats-server/server/errors.go:49 | PORTED | src/NATS.Server/NatsClient.cs | Reserved subject check on publish |
|
||||
| `ErrBadPublishSubject` | golang/nats-server/server/errors.go:52 | PORTED | src/NATS.Server/Protocol/NatsProtocol.cs:28 | `ErrInvalidPublishSubject` constant |
|
||||
| `ErrBadSubject` | golang/nats-server/server/errors.go:55 | PORTED | src/NATS.Server/Protocol/NatsProtocol.cs:29 | `ErrInvalidSubject` constant |
|
||||
| `ErrBadQualifier` | golang/nats-server/server/errors.go:58 | MISSING | — | No dedicated bad-qualifier error; transform validation throws `ArgumentException` |
|
||||
| `ErrBadClientProtocol` | golang/nats-server/server/errors.go:61 | PORTED | src/NATS.Server/NatsClient.cs | Protocol version validation on CONNECT |
|
||||
| `ErrTooManyConnections` | golang/nats-server/server/errors.go:64 | PORTED | src/NATS.Server/Protocol/NatsProtocol.cs:25 | `ErrMaxConnectionsExceeded` constant |
|
||||
| `ErrTooManyAccountConnections` | golang/nats-server/server/errors.go:68 | MISSING | — | Account-level connection limits not yet implemented |
|
||||
| `ErrLeafNodeLoop` | golang/nats-server/server/errors.go:72 | PORTED | src/NATS.Server/LeafNode/ | Leaf node loop detection implemented |
|
||||
| `ErrTooManySubs` | golang/nats-server/server/errors.go:76 | MISSING | — | Per-connection subscription limit not yet enforced |
|
||||
| `ErrTooManySubTokens` | golang/nats-server/server/errors.go:79 | MISSING | — | Subject token count limit not yet enforced |
|
||||
| `ErrClientConnectedToRoutePort` | golang/nats-server/server/errors.go:83 | PORTED | src/NATS.Server/Routes/ | Wrong port detection on route listener |
|
||||
| `ErrClientConnectedToLeafNodePort` | golang/nats-server/server/errors.go:87 | PORTED | src/NATS.Server/LeafNode/ | Wrong port detection on leaf node listener |
|
||||
| `ErrLeafNodeHasSameClusterName` | golang/nats-server/server/errors.go:91 | PORTED | src/NATS.Server/LeafNode/ | Same-cluster-name rejection |
|
||||
| `ErrLeafNodeDisabled` | golang/nats-server/server/errors.go:94 | PORTED | src/NATS.Server/LeafNode/ | Leaf node disabled check |
|
||||
| `ErrConnectedToWrongPort` | golang/nats-server/server/errors.go:98 | PORTED | src/NATS.Server/NatsServer.cs | Port sniffing / wrong-port close |
|
||||
| `ErrAccountExists` | golang/nats-server/server/errors.go:102 | PORTED | src/NATS.Server/Auth/Account.cs | Duplicate account registration check |
|
||||
| `ErrBadAccount` | golang/nats-server/server/errors.go:105 | PORTED | src/NATS.Server/Auth/ | Bad/malformed account |
|
||||
| `ErrReservedAccount` | golang/nats-server/server/errors.go:108 | MISSING | — | Reserved account name check not yet implemented |
|
||||
| `ErrMissingAccount` | golang/nats-server/server/errors.go:111 | PORTED | src/NATS.Server/Auth/ | Missing account lookup |
|
||||
| `ErrMissingService` | golang/nats-server/server/errors.go:114 | MISSING | — | Service export/import not yet ported |
|
||||
| `ErrBadServiceType` | golang/nats-server/server/errors.go:117 | MISSING | — | Service latency tracking not yet ported |
|
||||
| `ErrBadSampling` | golang/nats-server/server/errors.go:120 | MISSING | — | Latency sampling validation not yet ported |
|
||||
| `ErrAccountValidation` | golang/nats-server/server/errors.go:123 | PORTED | src/NATS.Server/Auth/ | Account validation logic |
|
||||
| `ErrAccountExpired` | golang/nats-server/server/errors.go:126 | PORTED | src/NATS.Server/Auth/ | Account expiry check |
|
||||
| `ErrNoAccountResolver` | golang/nats-server/server/errors.go:129 | PORTED | src/NATS.Server/Auth/ | No resolver configured check |
|
||||
| `ErrAccountResolverUpdateTooSoon` | golang/nats-server/server/errors.go:132 | MISSING | — | Resolver update rate limiting not yet ported |
|
||||
| `ErrAccountResolverSameClaims` | golang/nats-server/server/errors.go:135 | MISSING | — | Same-claims dedup not yet ported |
|
||||
| `ErrStreamImportAuthorization` | golang/nats-server/server/errors.go:138 | MISSING | — | Stream import auth not yet ported |
|
||||
| `ErrStreamImportBadPrefix` | golang/nats-server/server/errors.go:141 | MISSING | — | Stream import prefix validation not yet ported |
|
||||
| `ErrStreamImportDuplicate` | golang/nats-server/server/errors.go:144 | MISSING | — | Duplicate import detection not yet ported |
|
||||
| `ErrServiceImportAuthorization` | golang/nats-server/server/errors.go:147 | MISSING | — | Service import auth not yet ported |
|
||||
| `ErrImportFormsCycle` | golang/nats-server/server/errors.go:150 | MISSING | — | Import cycle detection not yet ported |
|
||||
| `ErrCycleSearchDepth` | golang/nats-server/server/errors.go:153 | MISSING | — | Cycle search depth limit not yet ported |
|
||||
| `ErrClientOrRouteConnectedToGatewayPort` | golang/nats-server/server/errors.go:157 | PORTED | src/NATS.Server/Gateways/ | Wrong port detection on gateway listener |
|
||||
| `ErrWrongGateway` | golang/nats-server/server/errors.go:161 | PORTED | src/NATS.Server/Gateways/ | Wrong gateway name on connect |
|
||||
| `ErrGatewayNameHasSpaces` | golang/nats-server/server/errors.go:165 | PORTED | src/NATS.Server/Configuration/ConfigProcessor.cs | Config validation rejects spaces in gateway name |
|
||||
| `ErrNoSysAccount` | golang/nats-server/server/errors.go:169 | PORTED | src/NATS.Server/NatsServer.cs | System account presence check |
|
||||
| `ErrRevocation` | golang/nats-server/server/errors.go:172 | PORTED | src/NATS.Server/Auth/ | Credential revocation check |
|
||||
| `ErrServerNotRunning` | golang/nats-server/server/errors.go:175 | PORTED | src/NATS.Server/NatsServer.cs | Server running state check |
|
||||
| `ErrServerNameHasSpaces` | golang/nats-server/server/errors.go:178 | PORTED | src/NATS.Server/Configuration/ConfigProcessor.cs:1434 | Config validation; `ConfigProcessorException` thrown |
|
||||
| `ErrBadMsgHeader` | golang/nats-server/server/errors.go:181 | PORTED | src/NATS.Server/Protocol/NatsParser.cs | Header parsing validation |
|
||||
| `ErrMsgHeadersNotSupported` | golang/nats-server/server/errors.go:185 | PORTED | src/NATS.Server/NatsClient.cs | Headers-not-supported check |
|
||||
| `ErrNoRespondersRequiresHeaders` | golang/nats-server/server/errors.go:189 | PORTED | src/NATS.Server/NatsClient.cs | No-responders validation |
|
||||
| `ErrClusterNameConfigConflict` | golang/nats-server/server/errors.go:193 | PORTED | src/NATS.Server/Configuration/ConfigProcessor.cs | Cluster/gateway name conflict validation |
|
||||
| `ErrClusterNameRemoteConflict` | golang/nats-server/server/errors.go:196 | PORTED | src/NATS.Server/Routes/ | Remote cluster name mismatch |
|
||||
| `ErrClusterNameHasSpaces` | golang/nats-server/server/errors.go:199 | PORTED | src/NATS.Server/Configuration/ConfigProcessor.cs | Config validation |
|
||||
| `ErrMalformedSubject` | golang/nats-server/server/errors.go:202 | PORTED | src/NATS.Server/Subscriptions/SubjectMatch.cs | Subject validation rejects malformed subjects |
|
||||
| `ErrSubscribePermissionViolation` | golang/nats-server/server/errors.go:205 | PORTED | src/NATS.Server/NatsClient.cs | Subscribe permission check |
|
||||
| `ErrNoTransforms` | golang/nats-server/server/errors.go:208 | MISSING | — | Transform selection logic not yet fully ported |
|
||||
| `ErrCertNotPinned` | golang/nats-server/server/errors.go:211 | PORTED | src/NATS.Server/Tls/TlsHelper.cs | Pinned cert validation |
|
||||
| `ErrDuplicateServerName` | golang/nats-server/server/errors.go:215 | PORTED | src/NATS.Server/Routes/ | Duplicate server name on cluster connect |
|
||||
| `ErrMinimumVersionRequired` | golang/nats-server/server/errors.go:218 | PORTED | src/NATS.Server/Routes/ | Minimum version enforcement on cluster |
|
||||
| `ErrInvalidMappingDestination` | golang/nats-server/server/errors.go:221 | PORTED | src/NATS.Server/Subscriptions/SubjectTransform.cs | Transform destination validation |
|
||||
| `ErrInvalidMappingDestinationSubject` | golang/nats-server/server/errors.go:223 | PORTED | src/NATS.Server/Subscriptions/SubjectTransform.cs | Invalid destination subject |
|
||||
| `ErrMappingDestinationNotUsingAllWildcards` | golang/nats-server/server/errors.go:226 | PORTED | src/NATS.Server/Subscriptions/SubjectTransform.cs | Wildcard coverage check |
|
||||
| `ErrUnknownMappingDestinationFunction` | golang/nats-server/server/errors.go:229 | PORTED | src/NATS.Server/Subscriptions/SubjectTransform.cs | Unknown function error |
|
||||
| `ErrMappingDestinationIndexOutOfRange` | golang/nats-server/server/errors.go:232 | PORTED | src/NATS.Server/Subscriptions/SubjectTransform.cs | Index out of range |
|
||||
| `ErrMappingDestinationNotEnoughArgs` | golang/nats-server/server/errors.go:235 | PORTED | src/NATS.Server/Subscriptions/SubjectTransform.cs | Not enough args error |
|
||||
| `ErrMappingDestinationInvalidArg` | golang/nats-server/server/errors.go:238 | PORTED | src/NATS.Server/Subscriptions/SubjectTransform.cs | Invalid arg error |
|
||||
| `ErrMappingDestinationTooManyArgs` | golang/nats-server/server/errors.go:241 | PORTED | src/NATS.Server/Subscriptions/SubjectTransform.cs | Too many args error |
|
||||
| `ErrMappingDestinationNotSupportedForImport` | golang/nats-server/server/errors.go:244 | PORTED | src/NATS.Server/Subscriptions/SubjectTransform.cs | Import-only wildcard restriction |
|
||||
| `mappingDestinationErr` (type) | golang/nats-server/server/errors.go:248 | PORTED | src/NATS.Server/Subscriptions/SubjectTransform.cs | Equivalent validation exceptions in transform code |
|
||||
| `mappingDestinationErr.Error` | golang/nats-server/server/errors.go:253 | PORTED | src/NATS.Server/Subscriptions/SubjectTransform.cs | Exception message formatting |
|
||||
| `mappingDestinationErr.Is` | golang/nats-server/server/errors.go:257 | NOT_APPLICABLE | — | Go `errors.Is` interface; .NET uses `is` and `catch (SpecificException)` instead |
|
||||
| `configErr` (type) | golang/nats-server/server/errors.go:261 | PORTED | src/NATS.Server/Configuration/ConfigProcessor.cs:1434 | `ConfigProcessorException` with error list |
|
||||
| `configErr.Source` | golang/nats-server/server/errors.go:267 | PARTIAL | src/NATS.Server/Configuration/ConfigProcessor.cs | Source file/line tracking in errors is partial; exception message includes context but not file:line:col |
|
||||
| `configErr.Error` | golang/nats-server/server/errors.go:272 | PORTED | src/NATS.Server/Configuration/ConfigProcessor.cs | Exception `Message` property |
|
||||
| `unknownConfigFieldErr` (type) | golang/nats-server/server/errors.go:280 | PARTIAL | src/NATS.Server/Configuration/ConfigProcessor.cs | Unknown fields trigger `ConfigProcessorException` but without the specific `unknownConfigFieldErr` type distinction |
|
||||
| `configWarningErr` (type) | golang/nats-server/server/errors.go:292 | MISSING | — | No distinction between warnings and errors in .NET config processor; all surfaced as exceptions |
|
||||
| `processConfigErr` (type) | golang/nats-server/server/errors.go:303 | PORTED | src/NATS.Server/Configuration/ConfigProcessor.cs:1434 | `ConfigProcessorException` accumulates all errors |
|
||||
| `processConfigErr.Error` | golang/nats-server/server/errors.go:310 | PORTED | src/NATS.Server/Configuration/ConfigProcessor.cs | `Message` + `Errors` list |
|
||||
| `processConfigErr.Warnings` | golang/nats-server/server/errors.go:322 | MISSING | — | No separate warnings list; warnings folded into errors or logged |
|
||||
| `processConfigErr.Errors` | golang/nats-server/server/errors.go:327 | PORTED | src/NATS.Server/Configuration/ConfigProcessor.cs:1437 | `ConfigProcessorException.Errors` property |
|
||||
| `errCtx` (type) | golang/nats-server/server/errors.go:332 | PORTED | tests/NATS.Server.Tests/InfrastructureGoParityTests.cs:1069 | `WrappedNatsException` (test-file-scoped) mirrors the error context wrapping; production code uses standard .NET exception chaining |
|
||||
| `NewErrorCtx` | golang/nats-server/server/errors.go:338 | PORTED | tests/NATS.Server.Tests/InfrastructureGoParityTests.cs:1069 | Equivalent via `new WrappedNatsException(inner, ctx)` in tests; production uses `new Exception(msg, inner)` |
|
||||
| `errCtx.Unwrap` | golang/nats-server/server/errors.go:343 | NOT_APPLICABLE | — | Go `errors.Unwrap` interface; .NET uses `Exception.InnerException` |
|
||||
| `errCtx.Context` | golang/nats-server/server/errors.go:350 | PORTED | tests/NATS.Server.Tests/InfrastructureGoParityTests.cs:1069 | `WrappedNatsException.FullTrace()` extracts and returns context |
|
||||
| `UnpackIfErrorCtx` | golang/nats-server/server/errors.go:358 | PORTED | tests/NATS.Server.Tests/InfrastructureGoParityTests.cs:1075 | `WrappedNatsException.FullTrace()` — recursive trace assembly matches Go's `UnpackIfErrorCtx` logic |
|
||||
| `errorsUnwrap` (unexported) | golang/nats-server/server/errors.go:371 | NOT_APPLICABLE | — | Go 1.13 compatibility shim; .NET uses `Exception.InnerException` directly |
|
||||
| `ErrorIs` | golang/nats-server/server/errors.go:382 | NOT_APPLICABLE | — | Go 1.13 compatibility shim for `errors.Is`; .NET uses `is`, `catch`, or `Exception.GetType()` |
|
||||
|
||||
### `golang/nats-server/server/errors_gen.go`
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| (entire file — code generator) | golang/nats-server/server/errors_gen.go:1 | NOT_APPLICABLE | — | This is a `//go:build ignore` code generator that produces `jetstream_errors_generated.go`. The generated JetStream API error codes are ported directly as `JetStreamApiError` and integer error code constants in `src/NATS.Server/JetStream/Api/`. No need to port the generator itself. |
|
||||
|
||||
### `golang/nats-server/server/sdm.go`
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `SDMMeta` (type) | golang/nats-server/server/sdm.go:22 | MISSING | — | Subject Delete Marker metadata tracker; used inside `stream.go` for SDM/KV purge tracking. MsgScheduling not yet ported so SDMMeta is also absent. |
|
||||
| `SDMBySeq` (type) | golang/nats-server/server/sdm.go:27 | MISSING | — | Per-sequence SDM state (last flag + timestamp); needed by `SDMMeta` |
|
||||
| `newSDMMeta` | golang/nats-server/server/sdm.go:33 | MISSING | — | Constructor for `SDMMeta` |
|
||||
| `isSubjectDeleteMarker` (unexported) | golang/nats-server/server/sdm.go:42 | MISSING | — | Header inspection to detect SDM or KV purge markers; needed for FileStore tombstone handling |
|
||||
| `SDMMeta.empty` | golang/nats-server/server/sdm.go:47 | MISSING | — | Clear all tracking state |
|
||||
| `SDMMeta.trackPending` | golang/nats-server/server/sdm.go:56 | MISSING | — | Cache a pending SDM sequence |
|
||||
| `SDMMeta.removeSeqAndSubject` | golang/nats-server/server/sdm.go:66 | MISSING | — | Remove a confirmed SDM from tracking |
|
||||
|
||||
### `golang/nats-server/server/scheduler.go`
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `ErrMsgScheduleInvalidVersion` | golang/nats-server/server/scheduler.go:29 | MISSING | — | Error for unknown schedule snapshot version; needed when decoding binary schedule state |
|
||||
| `MsgScheduling` (type) | golang/nats-server/server/scheduler.go:35 | MISSING | — | Core message scheduler type — manages per-subject schedules, timer, THW integration; tests in FileStoreTombstoneTests.cs note this is not yet ported |
|
||||
| `MsgSchedule` (type) | golang/nats-server/server/scheduler.go:46 | MISSING | — | Per-subject schedule entry (seq + timestamp) |
|
||||
| `newMsgScheduling` | golang/nats-server/server/scheduler.go:51 | MISSING | — | Constructor with run callback |
|
||||
| `MsgScheduling.add` | golang/nats-server/server/scheduler.go:61 | MISSING | — | Add/replace a schedule entry |
|
||||
| `MsgScheduling.init` (unexported) | golang/nats-server/server/scheduler.go:66 | MISSING | — | Internal init with THW integration |
|
||||
| `MsgScheduling.update` | golang/nats-server/server/scheduler.go:81 | MISSING | — | Update timestamp of existing schedule |
|
||||
| `MsgScheduling.markInflight` | golang/nats-server/server/scheduler.go:93 | MISSING | — | Mark subject as currently being delivered |
|
||||
| `MsgScheduling.isInflight` | golang/nats-server/server/scheduler.go:99 | MISSING | — | Check if subject is in-flight |
|
||||
| `MsgScheduling.remove` | golang/nats-server/server/scheduler.go:104 | MISSING | — | Remove by sequence number |
|
||||
| `MsgScheduling.removeSubject` | golang/nats-server/server/scheduler.go:111 | MISSING | — | Remove by subject |
|
||||
| `MsgScheduling.clearInflight` | golang/nats-server/server/scheduler.go:119 | MISSING | — | Reset all in-flight tracking |
|
||||
| `MsgScheduling.resetTimer` (unexported) | golang/nats-server/server/scheduler.go:123 | MISSING | — | Recalculate next fire time from THW |
|
||||
| `MsgScheduling.getScheduledMessages` (unexported) | golang/nats-server/server/scheduler.go:158 | MISSING | — | Evaluate expired schedules; complex — includes pattern parsing, header manipulation |
|
||||
| `MsgScheduling.encode` | golang/nats-server/server/scheduler.go:249 | MISSING | — | Binary snapshot serialization |
|
||||
| `MsgScheduling.decode` | golang/nats-server/server/scheduler.go:267 | MISSING | — | Binary snapshot deserialization |
|
||||
| `parseMsgSchedule` (unexported) | golang/nats-server/server/scheduler.go:302 | MISSING | — | Parse `@at ...` / `@every ...` schedule patterns |
|
||||
|
||||
### `golang/nats-server/server/ats/ats.go`
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `TickInterval` (const) | golang/nats-server/server/ats/ats.go:25 | MISSING | — | 100ms tick interval constant for access-time service |
|
||||
| `Register` | golang/nats-server/server/ats/ats.go:42 | MISSING | — | Start the access-time goroutine (ref-counted). Used by FileStore on creation to get efficient nanosecond timestamps without calling `time.Now()` on every access. |
|
||||
| `Unregister` | golang/nats-server/server/ats/ats.go:64 | MISSING | — | Stop the access-time goroutine when last user unregisters |
|
||||
| `AccessTime` | golang/nats-server/server/ats/ats.go:76 | MISSING | — | Return cached Unix nanosecond timestamp (atomic load). .NET equivalent could use `Interlocked`-cached `DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()` but no such service exists yet |
|
||||
|
||||
### `golang/nats-server/server/elastic/elastic.go`
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `Pointer[T]` (generic type) | golang/nats-server/server/elastic/elastic.go:26 | MISSING | — | Elastic (weak/strong) reference wrapper using Go 1.24 `weak.Pointer`. Used for FileStore block caching to allow GC to reclaim blocks while keeping a strong ref when needed. No .NET equivalent ported. |
|
||||
| `Make[T]` | golang/nats-server/server/elastic/elastic.go:20 | MISSING | — | Factory for `Pointer[T]` from a strong pointer |
|
||||
| `Pointer[T].Set` | golang/nats-server/server/elastic/elastic.go:31 | MISSING | — | Update the weak/strong pointer |
|
||||
| `Pointer[T].Strengthen` | golang/nats-server/server/elastic/elastic.go:38 | MISSING | — | Promote weak ref to strong |
|
||||
| `Pointer[T].Weaken` | golang/nats-server/server/elastic/elastic.go:45 | MISSING | — | Demote strong ref to weak (allow GC) |
|
||||
| `Pointer[T].Value` | golang/nats-server/server/elastic/elastic.go:52 | MISSING | — | Get current value (strong if available, else weak) |
|
||||
|
||||
### `golang/nats-server/server/tpm/js_ek_tpm_windows.go` and `js_ek_tpm_other.go`
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `JsKeyTPMVersion` | golang/nats-server/server/tpm/js_ek_tpm_windows.go:34 | DEFERRED | — | TPM version constant; entire TPM subsystem deferred |
|
||||
| `regenerateSRK` (unexported) | golang/nats-server/server/tpm/js_ek_tpm_windows.go:51 | DEFERRED | — | Windows-only TPM2 SRK creation |
|
||||
| `natsTPMPersistedKeys` (type) | golang/nats-server/server/tpm/js_ek_tpm_windows.go:77 | DEFERRED | — | JSON-serializable key blob struct |
|
||||
| `writeTPMKeysToFile` (unexported) | golang/nats-server/server/tpm/js_ek_tpm_windows.go:85 | DEFERRED | — | Persist TPM key blobs to disk |
|
||||
| `readTPMKeysFromFile` (unexported) | golang/nats-server/server/tpm/js_ek_tpm_windows.go:115 | DEFERRED | — | Read TPM key blobs from disk |
|
||||
| `createAndSealJsEncryptionKey` (unexported) | golang/nats-server/server/tpm/js_ek_tpm_windows.go:146 | DEFERRED | — | Create NKey-based JetStream encryption key and seal to TPM |
|
||||
| `unsealJsEncrpytionKey` (unexported) | golang/nats-server/server/tpm/js_ek_tpm_windows.go:178 | DEFERRED | — | Unseal JetStream key from TPM |
|
||||
| `policyPCRPasswordSession` (unexported) | golang/nats-server/server/tpm/js_ek_tpm_windows.go:203 | DEFERRED | — | TPM2 policy session with PCR + password |
|
||||
| `LoadJetStreamEncryptionKeyFromTPM` | golang/nats-server/server/tpm/js_ek_tpm_windows.go:245 | DEFERRED | — | Main entry point; Windows only. Non-Windows stub returns error. Deferred: requires `Microsoft.TPM` or `Tss.Net` NuGet and Windows-only API surface. |
|
||||
| `LoadJetStreamEncryptionKeyFromTPM` (stub) | golang/nats-server/server/tpm/js_ek_tpm_other.go:21 | NOT_APPLICABLE | — | Non-Windows stub; .NET would use `PlatformNotSupportedException` equivalently |
|
||||
|
||||
### `golang/nats-server/internal/fastrand/fastrand.go`
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `Uint32` | golang/nats-server/internal/fastrand/fastrand.go:12 | NOT_APPLICABLE | — | Links to `runtime.fastrand` via `go:linkname` — a Go runtime internal. .NET uses `Random.Shared.Next()` or `RandomNumberGenerator` equivalently. No need to port this internal. |
|
||||
| `Uint32n` | golang/nats-server/internal/fastrand/fastrand.go:17 | NOT_APPLICABLE | — | Same as above; maps to `Random.Shared.Next(0, n)` |
|
||||
| `Uint64` | golang/nats-server/internal/fastrand/fastrand.go:20 | NOT_APPLICABLE | — | Same as above; maps to `Random.Shared.NextInt64()` |
|
||||
|
||||
### `golang/nats-server/internal/ldap/dn.go`
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `AttributeTypeAndValue` (type) | golang/nats-server/internal/ldap/dn.go:30 | PARTIAL | src/NATS.Server/Auth/TlsMapAuthenticator.cs | .NET `X500DistinguishedName` used directly; no standalone `AttributeTypeAndValue` type |
|
||||
| `RelativeDN` (type) | golang/nats-server/internal/ldap/dn.go:37 | PARTIAL | src/NATS.Server/Auth/TlsMapAuthenticator.cs | `X500DistinguishedName.EnumerateRelativeDistinguishedNames()` provides equivalent; no custom `RelativeDN` type |
|
||||
| `DN` (type) | golang/nats-server/internal/ldap/dn.go:43 | PARTIAL | src/NATS.Server/Auth/TlsMapAuthenticator.cs | `X500DistinguishedName` (.NET BCL) used instead; no custom `DN` type |
|
||||
| `FromCertSubject` | golang/nats-server/internal/ldap/dn.go:48 | PARTIAL | src/NATS.Server/Auth/TlsMapAuthenticator.cs:31 | Certificate subject extracted via `X509Certificate2.SubjectName`; does not support multi-value RDNs or full RFC 4514 ordering |
|
||||
| `FromRawCertSubject` | golang/nats-server/internal/ldap/dn.go:80 | MISSING | — | Raw ASN.1 subject parsing preserving original order; not implemented |
|
||||
| `ParseDN` | golang/nats-server/internal/ldap/dn.go:126 | MISSING | — | RFC 4514 DN string parser; not implemented. .NET uses `X500DistinguishedName` which parses RFC 2253 format but lacks the Go implementation's special-case handling |
|
||||
| `DN.Equal` | golang/nats-server/internal/ldap/dn.go:220 | MISSING | — | RFC 4517 distinguishedNameMatch comparison |
|
||||
| `DN.RDNsMatch` | golang/nats-server/internal/ldap/dn.go:234 | MISSING | — | Order-independent RDN matching |
|
||||
| `DN.AncestorOf` | golang/nats-server/internal/ldap/dn.go:258 | MISSING | — | DN ancestry check |
|
||||
| `RelativeDN.Equal` | golang/nats-server/internal/ldap/dn.go:278 | MISSING | — | RFC 4517 RDN equivalence |
|
||||
| `RelativeDN.hasAllAttributes` (unexported) | golang/nats-server/internal/ldap/dn.go:285 | MISSING | — | Attribute superset check |
|
||||
| `AttributeTypeAndValue.Equal` | golang/nats-server/internal/ldap/dn.go:302 | MISSING | — | Case-insensitive type + value comparison |
|
||||
|
||||
### `golang/nats-server/internal/antithesis/`
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `AssertUnreachable` (noop build) | golang/nats-server/internal/antithesis/noop.go:24 | NOT_APPLICABLE | — | Chaos testing assertion (noop when Antithesis SDK not active); .NET has no Antithesis SDK. xUnit's `Assert.Fail()` covers the reachability assertion pattern in tests. |
|
||||
| `Assert` (noop build) | golang/nats-server/internal/antithesis/noop.go:27 | NOT_APPLICABLE | — | Same as above; .NET uses Shouldly `condition.ShouldBeTrue()` in tests |
|
||||
| `AssertUnreachable` (SDK build) | golang/nats-server/internal/antithesis/test_assert.go:49 | NOT_APPLICABLE | — | Antithesis SDK wrapper; no .NET Antithesis SDK exists |
|
||||
| `Assert` (SDK build) | golang/nats-server/internal/antithesis/test_assert.go:83 | NOT_APPLICABLE | — | Same as above |
|
||||
|
||||
### `golang/nats-server/internal/testhelper/` (ocsp.go / logging.go)
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `NewOCSPResponder` | golang/nats-server/internal/testhelper/ocsp.go (ocsp.go):45 | PORTED | tests/NATS.Server.Tests/OcspStaplingTests.cs | .NET OCSP test helper implemented inline in test files using ASP.NET Core minimal APIs |
|
||||
| `NewOCSPResponderCustomAddress` | golang/nats-server/internal/testhelper/ocsp.go:40 | PORTED | tests/NATS.Server.Tests/OcspStaplingTests.cs | Equivalent test helper |
|
||||
| `NewOCSPResponderDesignatedCustomAddress` | golang/nats-server/internal/testhelper/ocsp.go:50 | PORTED | tests/NATS.Server.Tests/OcspStaplingTests.cs | Designated responder variant |
|
||||
| `NewOCSPResponderPreferringHTTPMethod` | golang/nats-server/internal/testhelper/ocsp.go:55 | PORTED | tests/NATS.Server.Tests/OcspStaplingTests.cs | HTTP method preference variant |
|
||||
| `NewOCSPResponderCustomTimeout` | golang/nats-server/internal/testhelper/ocsp.go:60 | PORTED | tests/NATS.Server.Tests/OcspStaplingTests.cs | Custom TTL variant |
|
||||
| `NewOCSPResponderBase` | golang/nats-server/internal/testhelper/ocsp.go:65 | PORTED | tests/NATS.Server.Tests/OcspStaplingTests.cs | Base factory; all OCSP responder variants collapse to this |
|
||||
| `GetOCSPStatus` | golang/nats-server/internal/testhelper/ocsp.go:230 | PORTED | tests/NATS.Server.Tests/OcspStaplingTests.cs | OCSP status extraction from TLS connection state |
|
||||
| `SetOCSPStatus` | golang/nats-server/internal/testhelper/ocsp.go:251 | PORTED | tests/NATS.Server.Tests/OcspStaplingTests.cs | HTTP POST to set cert status on mock OCSP responder |
|
||||
|
||||
---
|
||||
|
||||
## .NET-Only Implementations (No Direct Go Equivalent)
|
||||
|
||||
The following .NET types were added to support the porting but have no exact Go counterpart:
|
||||
|
||||
| .NET Type | File | Notes |
|
||||
|-----------|------|-------|
|
||||
| `OutboundBufferPool` | `src/NATS.Server/IO/OutboundBufferPool.cs` | Tiered byte-array pool (512/4096/65536) replacing Go's `sync.Pool` + `[]byte` dynamic sizing |
|
||||
| `AdaptiveReadBuffer` | `src/NATS.Server/IO/AdaptiveReadBuffer.cs` | Buffer sizing state machine; mirrors `client.go` readLoop buffer growth/shrink logic |
|
||||
| `AcceptLoopErrorHandler` | `src/NATS.Server/Server/AcceptLoopErrorHandler.cs` | Callback-based accept error delegation (no Go equivalent; Go logs inline) |
|
||||
| `TlsRateLimiter` | `src/NATS.Server/Tls/TlsRateLimiter.cs` | Async token-bucket rate limiter for TLS handshakes |
|
||||
|
||||
---
|
||||
|
||||
## 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/IO/ src/NATS.Server/Server/ -name '*.cs' -type f -exec cat {} + | wc -l
|
||||
# Re-count .NET test LOC for this module
|
||||
find tests/NATS.Server.Tests/IO/ -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-25 | File created with LLM analysis instructions | auto |
|
||||
| 2026-02-25 | Full gap inventory populated — all Go source files analyzed, 131 symbols classified | auto |
|
||||
188
gaps/websocket.md
Normal file
188
gaps/websocket.md
Normal file
@@ -0,0 +1,188 @@
|
||||
# WebSocket — Gap Analysis
|
||||
|
||||
> This file tracks what has and hasn't been ported from Go to .NET for the **WebSocket** 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 WebSocket
|
||||
|
||||
- WebSocket is at **99% LOC parity** — likely near-complete.
|
||||
- Focus analysis on edge cases: compression negotiation, frame fragmentation, close handshake.
|
||||
- WebSocket connections wrap a standard NATS connection with WS framing.
|
||||
|
||||
---
|
||||
|
||||
## Go Reference Files (Source)
|
||||
|
||||
- `golang/nats-server/server/websocket.go` — WebSocket transport support (~1,550 lines). WS framing, upgrade handshake, compression, masking.
|
||||
|
||||
## Go Reference Files (Tests)
|
||||
|
||||
- `golang/nats-server/server/websocket_test.go`
|
||||
|
||||
## .NET Implementation Files (Source)
|
||||
|
||||
- `src/NATS.Server/WebSocket/` (all files)
|
||||
|
||||
## .NET Implementation Files (Tests)
|
||||
|
||||
- `tests/NATS.Server.Tests/WebSocket/`
|
||||
|
||||
---
|
||||
|
||||
## Gap Inventory
|
||||
|
||||
<!-- After analysis, fill in this table. Group rows by Go source file. -->
|
||||
|
||||
### `golang/nats-server/server/websocket.go`
|
||||
|
||||
#### Types and Structs
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `wsOpCode` (type) | websocket.go:41 | PORTED | `src/NATS.Server/WebSocket/WsConstants.cs:1` | Int constants replace Go type alias; `WsConstants.TextMessage`, `BinaryMessage`, etc. |
|
||||
| `websocket` (struct) | websocket.go:108 | PORTED | `src/NATS.Server/WebSocket/WsConnection.cs:8` | Fields mapped: `compress`, `maskread`/`maskwrite`, `browser`, `nocompfrag`. Cookie fields moved to `WsUpgradeResult`. `frames`/`fs` buffer management is now in `WsConnection.WriteAsync`. `compressor` (reuse) is NOT pooled — recreated per call. |
|
||||
| `srvWebsocket` (struct) | websocket.go:126 | PARTIAL | `src/NATS.Server/NatsServer.cs:538` | `_wsListener`, `_options.WebSocket` cover port/host/tls; `allowedOrigins` managed via `WsOriginChecker`. `connectURLsMap` ref-count URL set and `authOverride` flag are MISSING — not ported. |
|
||||
| `allowedOrigin` (struct) | websocket.go:145 | PORTED | `src/NATS.Server/WebSocket/WsOriginChecker.cs:80` | Private `AllowedOrigin` record struct with `Scheme` and `Port`. |
|
||||
| `wsUpgradeResult` (struct) | websocket.go:150 | PORTED | `src/NATS.Server/WebSocket/WsUpgrade.cs:346` | `WsUpgradeResult` readonly record struct with equivalent fields. `kind` maps to `WsClientKind` enum. |
|
||||
| `wsReadInfo` (struct) | websocket.go:156 | PORTED | `src/NATS.Server/WebSocket/WsReadInfo.cs:10` | All fields ported: `rem`→`Remaining`, `fs`→`FrameStart`, `ff`→`FirstFrame`, `fc`→`FrameCompressed`, `mask`→`ExpectMask`, `mkpos`→`MaskKeyPos`, `mkey`→`MaskKey`, `cbufs`→`CompressedBuffers`, `coff`→`CompressedOffset`. Extra .NET fields added for control frame output. |
|
||||
| `WsDeflateParams` (struct) | websocket.go:885–916 | PORTED | `src/NATS.Server/WebSocket/WsCompression.cs:10` | New .NET-specific struct capturing `permessage-deflate` negotiated parameters. No Go equivalent struct — Go stores compress bool only. |
|
||||
|
||||
#### Package-Level Variables / Constants
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `wsOpCode` constants | websocket.go:43–96 | PORTED | `src/NATS.Server/WebSocket/WsConstants.cs:9–65` | All opcode, bit, size, close-status, and header-string constants are present. |
|
||||
| `decompressorPool` | websocket.go:99 | PARTIAL | `src/NATS.Server/WebSocket/WsCompression.cs:193` | Go uses `sync.Pool` for `flate.Reader` reuse. .NET creates a new `DeflateStream` per decompression call — no pooling. Functional but slightly less efficient under high load. |
|
||||
| `compressLastBlock` | websocket.go:100 | PORTED | `src/NATS.Server/WebSocket/WsConstants.cs:62` | .NET uses 4-byte `DecompressTrailer` (sync marker only); Go uses 9-byte block. Both work correctly — difference is .NET `DeflateStream` does not need the final stored block. |
|
||||
| `wsGUID` | websocket.go:103 | PORTED | `src/NATS.Server/WebSocket/WsUpgrade.cs:177` | Inline string literal in `ComputeAcceptKey`. |
|
||||
| `wsTestRejectNoMasking` | websocket.go:106 | MISSING | — | Test-only hook to force masking rejection. No equivalent test hook in .NET. |
|
||||
|
||||
#### Methods on `wsReadInfo`
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `wsReadInfo.init()` | websocket.go:168 | PORTED | `src/NATS.Server/WebSocket/WsReadInfo.cs:28` | Constructor initializes `FrameStart=true`, `FirstFrame=true` (same as Go `init()`). |
|
||||
| `wsReadInfo.Read()` | websocket.go:353 | PORTED | `src/NATS.Server/WebSocket/WsReadInfo.cs:100` | Consumed data from `CompressedBuffers` is handled inside `ReadFrames` rather than via an `io.Reader` interface; functionally equivalent. |
|
||||
| `wsReadInfo.nextCBuf()` | websocket.go:376 | PORTED | `src/NATS.Server/WebSocket/WsReadInfo.cs:100` | Buffer cycling logic is inline within the `Decompress` method — no explicit `nextCBuf` helper needed since .NET uses list indexing. |
|
||||
| `wsReadInfo.ReadByte()` | websocket.go:393 | PORTED | `src/NATS.Server/WebSocket/WsReadInfo.cs:100` | Used by Go's `flate.Resetter`; .NET `DeflateStream` reads a `MemoryStream` directly — no `ReadByte` interface needed. |
|
||||
| `wsReadInfo.decompress()` | websocket.go:408 | PORTED | `src/NATS.Server/WebSocket/WsCompression.cs:193` | `WsCompression.Decompress()` — appends trailer, decompresses with `DeflateStream`, enforces `maxPayload` limit. |
|
||||
| `wsReadInfo.unmask()` | websocket.go:509 | PORTED | `src/NATS.Server/WebSocket/WsReadInfo.cs:56` | `WsReadInfo.Unmask()` — exact port including 8-byte bulk XOR optimization for buffers >= 16 bytes. |
|
||||
|
||||
#### Methods on `client` (WebSocket-related)
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `client.isWebsocket()` | websocket.go:197 | PORTED | `src/NATS.Server/NatsClient.cs:107` | `NatsClient.IsWebSocket` bool property. |
|
||||
| `client.wsRead()` | websocket.go:208 | PORTED | `src/NATS.Server/WebSocket/WsReadInfo.cs:100` | `WsReadInfo.ReadFrames()` — full state machine port with frame-type validation, extended-length decoding, mask read, control frame dispatch, and compression accumulation. |
|
||||
| `client.wsHandleControlFrame()` | websocket.go:445 | PORTED | `src/NATS.Server/WebSocket/WsReadInfo.cs:246` | `WsReadInfo.HandleControlFrame()` — ping→pong, close→echo-close, pong→no-op. Close UTF-8 body validation is MISSING (Go validates UTF-8 in close body and downgrades status to 1007 on failure; .NET does not). |
|
||||
| `client.wsEnqueueControlMessage()` | websocket.go:600 | PORTED | `src/NATS.Server/WebSocket/WsConnection.cs:127` | Control frames collected in `PendingControlFrames` and flushed via `FlushControlFramesAsync`. Go uses per-client write buffer queuing with `flushSignal`; .NET writes directly — functionally equivalent. |
|
||||
| `client.wsEnqueueControlMessageLocked()` | websocket.go:631 | PORTED | `src/NATS.Server/WebSocket/WsConnection.cs:127` | Combined with `FlushControlFramesAsync`. Lock semantics differ (Go client mu, .NET `_writeLock`). |
|
||||
| `client.wsEnqueueCloseMessage()` | websocket.go:668 | PORTED | `src/NATS.Server/WebSocket/WsFrameWriter.cs:152` | `WsFrameWriter.MapCloseStatus()` covers the same `ClosedState` → status mapping. `WsConnection.SendCloseAsync()` performs the send. `BadClientProtocolVersion`, `MaxAccountConnectionsExceeded`, `MaxConnectionsExceeded`, `MaxControlLineExceeded`, `MissingAccount`, `Revocation` close-reason mappings are MISSING from .NET `ClientClosedReason` enum. |
|
||||
| `client.wsHandleProtocolError()` | websocket.go:700 | PORTED | `src/NATS.Server/WebSocket/WsReadInfo.cs:135` | Protocol errors throw `InvalidOperationException` in `ReadFrames` — caller (WsConnection.ReadAsync) treats any exception as a connection close. Direct close-frame send on protocol error is implicit via the close path. |
|
||||
| `client.wsCollapsePtoNB()` | websocket.go:1367 | PARTIAL | `src/NATS.Server/WebSocket/WsConnection.cs:91` | `WsConnection.WriteFramedAsync()` handles browser fragmentation and compression. However, Go's `wsCollapsePtoNB` is a low-level buffer-collapse routine that operates on `net.Buffers` (scatter/gather I/O) and reuses a persistent per-connection `flate.Writer` compressor. .NET recreates a `DeflateStream` per write and does not use scatter/gather — less efficient but functionally correct. |
|
||||
|
||||
#### Standalone Functions
|
||||
|
||||
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
||||
|-----------|:-------------|--------|:----------------|-------|
|
||||
| `wsGet()` | websocket.go:178 | PORTED | `src/NATS.Server/WebSocket/WsReadInfo.cs:299` | `WsReadInfo.WsGet()` (private) — returns `(byte[], newPos)` tuple. Same buffer-then-reader fallback logic. |
|
||||
| `wsIsControlFrame()` | websocket.go:539 | PORTED | `src/NATS.Server/WebSocket/WsConstants.cs:64` | `WsConstants.IsControlFrame(int opcode)` — same `>= CloseMessage` check. |
|
||||
| `wsCreateFrameHeader()` | websocket.go:545 | PORTED | `src/NATS.Server/WebSocket/WsFrameWriter.cs:17` | `WsFrameWriter.CreateFrameHeader()` — first=true, final=true wrapper. |
|
||||
| `wsFillFrameHeader()` | websocket.go:551 | PORTED | `src/NATS.Server/WebSocket/WsFrameWriter.cs:30` | `WsFrameWriter.FillFrameHeader()` — exact port with 2/4/10-byte length encoding and optional masking. |
|
||||
| `wsMaskBuf()` | websocket.go:607 | PORTED | `src/NATS.Server/WebSocket/WsFrameWriter.cs:77` | `WsFrameWriter.MaskBuf()` — simple XOR loop. |
|
||||
| `wsMaskBufs()` | websocket.go:614 | PORTED | `src/NATS.Server/WebSocket/WsFrameWriter.cs:86` | `WsFrameWriter.MaskBufs()` — multi-buffer contiguous XOR. |
|
||||
| `wsCreateCloseMessage()` | websocket.go:711 | PORTED | `src/NATS.Server/WebSocket/WsFrameWriter.cs:103` | `WsFrameWriter.CreateCloseMessage()` — 2-byte status + body, body truncated with "..." at `MaxControlPayloadSize-2`. .NET additionally validates UTF-8 boundary when truncating. |
|
||||
| `wsUpgrade()` (Server method) | websocket.go:731 | PORTED | `src/NATS.Server/WebSocket/WsUpgrade.cs:13` | `WsUpgrade.TryUpgradeAsync()` — full RFC 6455 handshake: method, host, upgrade, connection, key, version checks; origin; compression negotiation; no-masking; browser detection; cookie extraction; X-Forwarded-For; custom headers; 101 response. Go uses `http.Hijacker` pattern; .NET reads raw TCP stream. MQTT `Sec-Websocket-Protocol` header in response is MISSING. |
|
||||
| `wsHeaderContains()` | websocket.go:872 | PORTED | `src/NATS.Server/WebSocket/WsUpgrade.cs:315` | `WsUpgrade.HeaderContains()` (private) — same comma-split, case-insensitive token matching. |
|
||||
| `wsPMCExtensionSupport()` | websocket.go:885 | PORTED | `src/NATS.Server/WebSocket/WsCompression.cs:60` | `WsDeflateNegotiator.Negotiate()` — parses `Sec-WebSocket-Extensions`, detects `permessage-deflate`, extracts `server_no_context_takeover`, `client_no_context_takeover`, and window bits. |
|
||||
| `wsReturnHTTPError()` | websocket.go:921 | PORTED | `src/NATS.Server/WebSocket/WsUpgrade.cs:226` | `WsUpgrade.FailAsync()` (private) — sends HTTP error response and returns `WsUpgradeResult.Failed`. |
|
||||
| `srvWebsocket.checkOrigin()` | websocket.go:933 | PORTED | `src/NATS.Server/WebSocket/WsOriginChecker.cs:32` | `WsOriginChecker.CheckOrigin()` — same-origin and allowed-list checks. Go checks `r.TLS != nil` for TLS detection; .NET uses `isTls` parameter passed at call site. |
|
||||
| `wsGetHostAndPort()` | websocket.go:985 | PORTED | `src/NATS.Server/WebSocket/WsOriginChecker.cs:65` | `WsOriginChecker.GetHostAndPort()` and `ParseHostPort()` — missing-port defaults to 80/443 by TLS flag. |
|
||||
| `wsAcceptKey()` | websocket.go:1004 | PORTED | `src/NATS.Server/WebSocket/WsUpgrade.cs:175` | `WsUpgrade.ComputeAcceptKey()` — SHA-1 of key + GUID, base64 encoded. |
|
||||
| `wsMakeChallengeKey()` | websocket.go:1011 | MISSING | — | Generates a random 16-byte base64 client key for outbound WS connections (leaf node acting as WS client). No .NET equivalent. Needed when .NET server connects outbound as a WS leaf. |
|
||||
| `validateWebsocketOptions()` | websocket.go:1020 | PARTIAL | `src/NATS.Server/WebSocket/WebSocketTlsConfig.cs:24` | `WebSocketTlsConfig.Validate()` checks cert+key pair consistency. Full Go validation (TLS required unless NoTLS, AllowedOrigins parseable, NoAuthUser in users list, Token/Username incompatible with users/nkeys, JWTCookie requires TrustedOperators, TLSPinnedCerts, reserved header names) is MISSING from .NET. |
|
||||
| `Server.wsSetOriginOptions()` | websocket.go:1083 | PARTIAL | `src/NATS.Server/WebSocket/WsUpgrade.cs:49` | Origin checking is constructed inline in `TryUpgradeAsync` from `options.SameOrigin` and `options.AllowedOrigins`. The Go method persists parsed origins in `srvWebsocket.allowedOrigins` map and supports hot-reload. .NET constructs a `WsOriginChecker` per request — no hot-reload support, but functionally equivalent for initial config. |
|
||||
| `Server.wsSetHeadersOptions()` | websocket.go:1111 | PORTED | `src/NATS.Server/WebSocket/WsUpgrade.cs:141` | Custom headers applied inline in `TryUpgradeAsync` from `options.Headers`. |
|
||||
| `Server.wsConfigAuth()` | websocket.go:1131 | MISSING | — | Sets `srvWebsocket.authOverride` flag based on username/token/noAuthUser presence. No equivalent flag computation in .NET — `WebSocketOptions` properties are read directly. Functionally equivalent but the explicit override flag is absent. |
|
||||
| `Server.startWebsocketServer()` | websocket.go:1137 | PORTED | `src/NATS.Server/NatsServer.cs:538` | `NatsServer.StartAsync()` section at line 538 sets up the WS listener, logs, and launches `RunWebSocketAcceptLoopAsync`. Go uses `http.Server` + mux; .NET uses raw `TcpListener`/`Socket.AcceptAsync`. LEAF and MQTT routing at connection time is PARTIAL — LEAF path is wired (`WsClientKind.Leaf`) but MQTT is not handled in `AcceptWebSocketClientAsync`. `lame-duck` / `ldmCh` signaling is MISSING. |
|
||||
| `Server.wsGetTLSConfig()` | websocket.go:1264 | PARTIAL | `src/NATS.Server/NatsServer.cs:807` | TLS is applied once at accept time via `TlsConnectionWrapper.NegotiateAsync`. Go uses `GetConfigForClient` callback for hot-reload TLS config. .NET does not support hot TLS config reload for WS. |
|
||||
| `Server.createWSClient()` | websocket.go:1273 | PORTED | `src/NATS.Server/NatsServer.cs:799` | `AcceptWebSocketClientAsync()` — creates `WsConnection`, constructs `NatsClient`, wires `IsWebSocket`/`WsInfo`, registers client. Go also sends INFO immediately and sets auth timer; .NET's `NatsClient.RunAsync()` handles INFO send and auth timer. |
|
||||
| `isWSURL()` | websocket.go:1544 | MISSING | — | Helper to detect `ws://` scheme in a URL. Used by leaf node / route URL parsing. No .NET equivalent — not yet needed since outbound WS connections are not implemented. |
|
||||
| `isWSSURL()` | websocket.go:1548 | MISSING | — | Helper to detect `wss://` scheme in a URL. Same as above — not yet needed. |
|
||||
|
||||
---
|
||||
|
||||
## 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/WebSocket/ -name '*.cs' -type f -exec cat {} + | wc -l
|
||||
# Re-count .NET test LOC for this module
|
||||
find tests/NATS.Server.Tests/WebSocket/ -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-25 | File created with LLM analysis instructions | auto |
|
||||
| 2026-02-25 | Full gap inventory populated: 37 Go symbols classified (26 PORTED, 7 PARTIAL, 4 MISSING, 0 NOT_APPLICABLE, 0 DEFERRED) | auto |
|
||||
Reference in New Issue
Block a user