From f86ea57f437d96b9d6fbc4984c74be47169cd688 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Mon, 23 Feb 2026 00:17:24 -0500 Subject: [PATCH] docs: add design for sections 3-6 gaps implementation --- .../2026-02-23-sections3-6-gaps-design.md | 190 ++++++++++++++++++ 1 file changed, 190 insertions(+) create mode 100644 docs/plans/2026-02-23-sections3-6-gaps-design.md diff --git a/docs/plans/2026-02-23-sections3-6-gaps-design.md b/docs/plans/2026-02-23-sections3-6-gaps-design.md new file mode 100644 index 0000000..f438dab --- /dev/null +++ b/docs/plans/2026-02-23-sections3-6-gaps-design.md @@ -0,0 +1,190 @@ +# 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 op, ReadOnlySpan 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 \r\n` status line + `Key: Value\r\n` pairs. + +Returns `NatsHeaders` readonly struct with `Status` (int), `Description` (string), and key-value `Dictionary` 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` 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` 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)` | Single write-lock, batch removes, single generation bump. | +| `All()` | Depth-first trie walk, yield all subscriptions. Returns `IReadOnlyList`. | +| `MatchBytes(ReadOnlySpan)` | 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`. | +| `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` with custom `PermissionLruCache` (128 entries). `Dictionary` + `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` 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? | 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.