8.3 KiB
Sections 3-6 Gaps Implementation Design
Approved 2026-02-23. Implements all remaining gaps in Protocol Parsing, Subscriptions & Subject Matching, Authentication & Authorization, and Configuration.
Section 3 — Protocol Parsing
3a. INFO Serialization Caching
Add byte[] _infoJsonCache on NatsServer. Build once in StartAsync() after ServerInfo is populated. Rebuild only on config reload.
NatsClient.SendInfo() uses cached bytes for non-nonce connections. For NKey connections (which need a per-connection nonce), clone ServerInfo, set nonce, serialize fresh.
Files: NatsServer.cs, NatsClient.cs
3b. Protocol Tracing
Add ILogger to NatsParser. Add TraceInOp(ReadOnlySpan<byte> op, ReadOnlySpan<byte> arg) that logs at LogLevel.Trace after each command dispatch in TryParse().
Controlled by NatsOptions.Trace (which sets log level filter).
Files: NatsParser.cs, NatsOptions.cs
3c. MIME Header Parsing
New NatsHeaderParser static class in Protocol/. Parses NATS/1.0 <status> <description>\r\n status line + Key: Value\r\n pairs.
Returns NatsHeaders readonly struct with Status (int), Description (string), and key-value Dictionary<string, string[]> lookup.
Used in ProcessMessage() for header inspection (no-responders status, future message tracing).
Files: New Protocol/NatsHeaderParser.cs, NatsServer.cs
3d. MSG/HMSG Construction Optimization
Pre-allocate buffers for MSG/HMSG prefix using Span<byte> and Utf8Formatter instead of string interpolation + Encoding.UTF8.GetBytes(). Reduces per-message allocations.
Files: NatsClient.cs
Section 4 — Subscriptions & Subject Matching
4a. Atomic Generation ID for Cache Invalidation
Add long _generation to SubList. Increment via Interlocked.Increment on every Insert() and Remove(). Cache entries store generation at computation time. On Match(), stale generation = cache miss.
Replaces current explicit per-key cache removal. O(1) invalidation.
Files: SubList.cs
4b. Async Background Cache Sweep
Replace inline sweep (runs under write lock) with PeriodicTimer-based background task. Acquires write lock briefly to snapshot + evict stale entries. Triggered when cache count > 1024, sweeps to 256.
Files: SubList.cs
4c. plist Optimization for High-Fanout Nodes
On TrieNode, when PlainSubs.Count > 256, convert HashSet<Subscription> to Subscription[] flat array in PList field. Match() iterates PList when present. On count < 128, convert back to HashSet.
Files: SubList.cs
4d. SubList Utility Methods
| Method | Description |
|---|---|
Stats() |
Returns SubListStats record with NumSubs, NumCache, NumInserts, NumRemoves, NumMatches, CacheHitRate, MaxFanout. Track via Interlocked counters. |
HasInterest(string) |
Walk trie without building result, return true on first hit. |
NumInterest(string) |
Walk trie, count without allocating result arrays. |
ReverseMatch(string) |
Walk trie with literal tokens, collect all subscription patterns matching the literal. |
RemoveBatch(IEnumerable<Subscription>) |
Single write-lock, batch removes, single generation bump. |
All() |
Depth-first trie walk, yield all subscriptions. Returns IReadOnlyList<Subscription>. |
MatchBytes(ReadOnlySpan<byte>) |
Zero-copy match using byte-based TokenEnumerator. |
Files: SubList.cs, new SubListStats.cs
4e. SubjectMatch Utilities
| Method | Description |
|---|---|
SubjectsCollide(string, string) |
Token-by-token comparison handling * and >. Two patterns collide if any literal could match both. |
TokenAt(string, int) |
Return nth dot-delimited token as ReadOnlySpan<char>. |
NumTokens(string) |
Count dots + 1. |
| UTF-8/null validation | Add checkRunes parameter to IsValidSubject(). When true, scan for \0 bytes and validate UTF-8. |
Files: SubjectMatch.cs
4f. Per-Account Subscription Limits
Add MaxSubscriptions to Account. Track SubscriptionCount via Interlocked. Enforce in ProcessSub(). Close with ClientClosedReason.MaxSubscriptionsExceeded.
Files: Account.cs, NatsClient.cs
Section 5 — Authentication & Authorization
5a. Deny List Enforcement at Delivery Time
In NatsServer.DeliverMessage(), before sending MSG, check subscriber.Client.Permissions?.IsDeliveryAllowed(subject). New method checks publish deny list for the receiving client ("msg delivery filter"). Cache results in pub cache.
Files: NatsServer.cs, ClientPermissions.cs
5b. Permission Caching with 128-Entry LRU
Replace ConcurrentDictionary<string, bool> with custom PermissionLruCache (128 entries). Dictionary<string, LinkedListNode> + LinkedList for LRU ordering. Lock-protected (per-client, low contention).
Files: ClientPermissions.cs, new Auth/PermissionLruCache.cs
5c. Subscribe Deny Queue-Group Checking
IsSubscribeAllowed(subject, queue) checks queue group against subscribe deny list (currently ignores queue parameter).
Files: ClientPermissions.cs
5d. Response Permissions (Reply Tracking)
New ResponseTracker class on NatsClient. Created when Permissions.Response is non-null. Tracks reply subjects with TTL (ResponsePermission.Expires) and max count (ResponsePermission.MaxMsgs). IsPublishAllowed() consults tracker for reply subjects not in static allow list. Expired entries cleaned lazily + in ping timer.
Files: New Auth/ResponseTracker.cs, ClientPermissions.cs, NatsClient.cs
5e. Per-Account Connection Limits
Add MaxConnections to Account. Enforce in ProcessConnectAsync() after account assignment. Reject with -ERR maximum connections for account exceeded.
Files: Account.cs, NatsClient.cs
5f. Multi-Account User Resolution
Add NatsOptions.Accounts as Dictionary<string, AccountConfig> with per-account MaxConnections, MaxSubscriptions, DefaultPermissions. AuthService resolves account name to configured Account with limits.
Files: NatsOptions.cs, new Auth/AccountConfig.cs, AuthService.cs, NatsServer.cs
5g. Auth Expiry Enforcement
In ProcessConnectAsync(), if AuthResult.Expiry is set, start CancellationTokenSource.CancelAfter(expiry - now). Link to client lifetime. On fire, close with ClientClosedReason.AuthenticationExpired.
Files: NatsClient.cs
5h. Auto-Unsub Cleanup
In DeliverMessage(), when sub.MessageCount >= sub.MaxMessages, call sub.Client.RemoveSubscription(sub.Sid) and subList.Remove(sub) to clean up both tracking dict and trie. Currently only skips delivery.
Files: NatsServer.cs, NatsClient.cs
Section 6 — Configuration
6a. Debug/Trace CLI Flags
Add -D/--debug, -V/--trace, -DV to Program.cs. Set NatsOptions.Debug and NatsOptions.Trace. Wire into Serilog minimum level.
Files: Program.cs
6b. New NatsOptions Fields
| Field | Type | Default | Purpose |
|---|---|---|---|
MaxSubs |
int | 0 (unlimited) | Per-connection subscription limit |
MaxSubTokens |
int | 0 (unlimited) | Max tokens in a subject |
Debug |
bool | false | Enable debug-level logging |
Trace |
bool | false | Enable trace-level protocol logging |
LogFile |
string? | null | Log to file instead of console |
LogSizeLimit |
long | 0 | Max log file size before rotation |
Tags |
Dictionary<string, string>? | null | Server metadata tags |
Files: NatsOptions.cs
6c. Logging Options Wiring
In Program.cs, if LogFile is set, add Serilog File sink with LogSizeLimit. If Debug/Trace, override Serilog minimum level.
Files: Program.cs
Implementation Strategy
Execute in a git worktree. Parallelize where files don't overlap:
- Parallel batch 1: SubjectMatch utilities (4e) | NatsOptions + CLI flags (6a, 6b) | NatsHeaderParser (3c) | PermissionLruCache (5b) | SubListStats (4d stats class)
- Parallel batch 2: SubList overhaul (4a, 4b, 4c, 4d methods) | Account limits + config (4f, 5e, 5f, 5g) | Response tracker (5d)
- Parallel batch 3: Protocol tracing (3b) | INFO caching (3a) | MSG optimization (3d)
- Sequential: Delivery-time enforcement (5a, 5c, 5h) — touches NatsServer.cs + ClientPermissions.cs, must be coordinated
- Final: Logging wiring (6c) | differences.md update
Tests added alongside each feature in the appropriate test file.