- Fix pull consumer fetch: send original stream subject in HMSG (not inbox) so NATS client distinguishes data messages from control messages - Fix MaxAge expiry: add background timer in StreamManager for periodic pruning - Fix JetStream wire format: Go-compatible anonymous objects with string enums, proper offset-based pagination for stream/consumer list APIs - Add 42 E2E black-box tests (core messaging, auth, TLS, accounts, JetStream) - Add ~1000 parity tests across all subsystems (gaps closure) - Update gap inventory docs to reflect implementation status
404 lines
36 KiB
Markdown
404 lines
36 KiB
Markdown
# 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` | PORTED | `src/NATS.Server/Internal/Avl/SequenceSet.cs:266` | Added destination-buffer overload (`Encode(byte[] destination)`) enabling caller buffer reuse parity |
|
|
| `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` | PORTED | `src/NATS.Server/Internal/SubjectTree/SubjectTree.cs:675` | Added GSL intersection traversal for subject tree entries |
|
|
| `_intersectGSL` (internal) | `golang/nats-server/server/stree/stree.go:496` | PORTED | `src/NATS.Server/Internal/SubjectTree/SubjectTree.cs:686` | Added recursive intersection helper |
|
|
| `hasInterestForTokens` (internal) | `golang/nats-server/server/stree/stree.go:521` | PORTED | `src/NATS.Server/Internal/SubjectTree/SubjectTree.cs:717` | Added token-boundary short-circuit using `HasInterestStartingIn` |
|
|
| `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` | PORTED | `src/NATS.Server/Internal/SubjectTree/SubjectTree.cs:172` | Added debug dump writer with root traversal and trailing newline |
|
|
| `SubjectTree.dump` (internal) | `golang/nats-server/server/stree/dump.go:29` | PORTED | `src/NATS.Server/Internal/SubjectTree/SubjectTree.cs:180` | Added recursive node/leaf dump helper |
|
|
| `dumpPre` | `golang/nats-server/server/stree/dump.go:59` | PORTED | `src/NATS.Server/Internal/SubjectTree/SubjectTree.cs:205` | Added indentation helper matching Go depth formatting |
|
|
| `leaf.kind`, `node4.kind`, etc. | `golang/nats-server/server/stree/dump.go:51` | PORTED | `src/NATS.Server/Internal/SubjectTree/Nodes.cs:21` | `INode.Kind` values are now actively consumed by `SubjectTree.Dump` output |
|
|
|
|
---
|
|
|
|
### 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` | PORTED | `src/NATS.Server/Internal/TimeHashWheel/HashWheel.cs:407` | Added `HashWheelEntry` record struct (`Sequence`, `Expires`) |
|
|
| `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` | PORTED | `src/NATS.Server/Internal/Gsl/GenericSubjectList.cs:656` | `SimpleSubjectList` now aliases `GenericSubjectList<SimpleSublistValue>` where marker value mirrors Go's empty struct payload |
|
|
| `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` | PORTED | `src/NATS.Server/Monitoring/VarzHandler.cs:30` | Added 1s background timer sampler (`SampleCpuUsage`) with synchronized cached CPU reads in `/varz`, matching periodic semantics instead of request-only sampling |
|
|
|
|
---
|
|
|
|
### sysmem/ (System Memory Queries)
|
|
|
|
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
|
|-----------|:-------------|--------|:----------------|-------|
|
|
| `Memory` (darwin) | `golang/nats-server/server/sysmem/mem_darwin.go:18` | PORTED | `src/NATS.Server/Internal/SysMem/SystemMemory.cs:12` | Added cross-platform `SystemMemory.Memory()` backed by runtime memory info |
|
|
| `Memory` (linux) | `golang/nats-server/server/sysmem/mem_linux.go:20` | PORTED | `src/NATS.Server/Internal/SysMem/SystemMemory.cs:12` | Added cross-platform `SystemMemory.Memory()` backed by runtime memory info |
|
|
| `Memory` (windows) | `golang/nats-server/server/sysmem/mem_windows.go` | PORTED | `src/NATS.Server/Internal/SysMem/SystemMemory.cs:12` | Added cross-platform `SystemMemory.Memory()` backed by runtime memory info |
|
|
| `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 |
|
|
| 2026-02-25 | Completed periodic PSE parity by moving CPU sampling to a 1s background timer in `VarzHandler` and adding targeted parity test coverage | codex |
|