191 lines
8.3 KiB
Markdown
191 lines
8.3 KiB
Markdown
# 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.
|