This commit is contained in:
bluepython508 committed 2026-10-22 12:27:19 +01:00
1 parent 3530afb854
commit 6903fcff27
2146 files changed
+112256 -190703

No files matched your search

+112
View File
@@ -0,0 +1,112 @@
language: en-US
tone_instructions: ''
early_access: true
enable_free_tier: true
reviews:
profile: chill
request_changes_workflow: false
high_level_summary: true
high_level_summary_placeholder: '@coderabbitai summary'
high_level_summary_in_walkthrough: false
auto_title_placeholder: '@coderabbitai'
auto_title_instructions: ''
review_status: true
commit_status: true
fail_commit_status: false
collapse_walkthrough: true
changed_files_summary: true
sequence_diagrams: true
estimate_code_review_effort: true
assess_linked_issues: true
related_issues: true
related_prs: true
suggested_labels: true
auto_apply_labels: false
suggested_reviewers: false
auto_assign_reviewers: false
poem: false
labeling_instructions: []
path_filters:
- '!**/testdata/**'
path_instructions: []
abort_on_close: true
disable_cache: false
auto_review:
enabled: true
auto_incremental_review: true
ignore_title_keywords: ['skip ci']
labels: []
drafts: false
base_branches: []
ignore_usernames: []
finishing_touches:
docstrings:
enabled: true
unit_tests:
enabled: true
pre_merge_checks:
docstrings:
mode: warning
threshold: 80
title:
mode: warning
requirements: ''
description:
mode: warning
issue_assessment:
mode: warning
tools:
markdownlint:
enabled: true
github-checks:
enabled: true
timeout_ms: 300000
languagetool:
enabled: true
enabled_rules: []
disabled_rules: []
enabled_categories: []
disabled_categories: []
enabled_only: false
level: default
golangci-lint:
enabled: true
config_file: .golangci.yml
yamllint:
enabled: true
gitleaks:
enabled: true
actionlint:
enabled: true
chat:
art: false
auto_reply: true
integrations: {}
knowledge_base:
opt_out: false
web_search:
enabled: true
code_guidelines:
enabled: true
filePatterns: []
learnings:
scope: auto
issues:
scope: auto
jira:
usage: disabled
project_keys: []
linear:
usage: disabled
team_keys: []
pull_requests:
scope: auto
mcp:
usage: auto
disabled_servers: []
code_generation:
docstrings:
language: en-US
path_instructions: []
unit_tests:
path_instructions: []
+2 -2
View File
@@ -23,5 +23,5 @@
# ...even if they are in subdirectories
!*/
!doc/*
!testdata/*
!scripts/**
!**/testdata/**
+54 -25
View File
@@ -1,34 +1,63 @@
version: '2'
run:
timeout: 5m
linters:
enable:
- gofmt
- gofumpt
- goimports
- govet
- gosimple
- gocyclo
- gosmopolitan
- errcheck
- staticcheck
- intrange
- ineffassign
- unused
- typecheck
- unconvert
- unparam
- misspell
- decorder
- errorlint
- gocyclo
- govet
- gosec
- gosmopolitan
- intrange
- misspell
- predeclared
- reassign
- stylecheck
- staticcheck
- unconvert
- unparam
- usestdlibvars
- wastedassign
- nakedret
#
# - gosec
# - dupl
# - wrapcheck
- copyloopvar
- tparallel
- gochecknoglobals
- nolintlint
- modernize
- gocritic
settings:
govet:
enable:
- shadow
gocyclo:
min-complexity: 30
gosec:
excludes:
- G404 # deterministic RNG in tests/benchmarks only
exclusions:
generated: lax
rules:
- linters:
- gochecknoglobals
- gosec
- gocyclo
path: _test\.go
presets:
- comments
- common-false-positives
- legacy
- std-error-handling
paths:
- third_party$
- builtin$
- examples$
linters-settings:
gocyclo:
min-complexity: 30
formatters:
enable:
- gofmt
- goimports
exclusions:
generated: lax
paths:
- third_party$
- builtin$
- examples$
+14 -1
View File
@@ -1 +1,14 @@
Contributions are welcome, but please open an issue for discussion before sending a pull request.
## Contributing
Contributions are welcome! Please open an issue first to discuss substantial changes.
### Pull Request Checklist
- Follow Go formatting and lint rules (golangci-lint passes locally).
- Run `go test ./...` before sending PRs.
- Add/adjust tests with good coverage.
- Update README when behavior or APIs change.
- Keep commits focused; prefer small, reviewable PRs.
### Governance
- Be respectful.
- License your contributions under MIT.
+1 -1
View File
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2024 Karl Gaissmaier
Copyright (c) 2026 Karl Gaissmaier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
+131
View File
@@ -0,0 +1,131 @@
# package bart
# Node Types Comparison
BART implements three different node types, each optimized for specific use cases:
## Memory Footprint (64-bit systems)
**Base Components:**
- `BitSet256`: `[4]uint64` = **32 bytes**
- `sparse.Array256[T]`: `BitSet256 + []T` = **56 bytes + n×sizeof(T)**
**Child Reference Sizing:**
- `childRef`: 8 bytes (pointer) or 16 bytes (interface value storage)
- The actual size depends on implementation: 8B for `*node` pointers, 16B for `interface{}` values
### BartNode[V] - Dynamic Sparse Node
```go
type BartNode[V any] struct {
prefixes sparse.Array256[V] // 56 + n×sizeof(V)
children sparse.Array256[childRef] // 56 + m×sizeof(childRef)
}
```
**Memory Usage:** **112 bytes + n×sizeof(V) + m×sizeof(childRef)**
### LiteNode - Dynamic Sparse, prefixes Bitset-Only Node
```go
type LiteNode struct {
prefixes bitset.BitSet256 // 32 bytes (presence only)
children sparse.Array256[childRef] // 56 + m×sizeof(childRef)
pfxCount uint16 // 2 bytes + padding
}
```
**Memory Usage:** **96 bytes + m×sizeof(childRef)** (no value storage)
### FastNode[V] - Fixed Array Node
```go
type FastNode[V any] struct {
prefixes sparse.Array256[V] // 56 + n×sizeof(V)
children sparse.Array256[childRef] // 56 + m×sizeof(childRef)
childRankCache [256]uint8 // 256
}
```
**Memory Usage:** **256 + 112 bytes + n×sizeof(V) + m×sizeof(childRef)**
## Real-World Example
**Scenario:** Node with 10 prefixes, 5 children
| Node Type | Base | *Payload | +Children | Total | **Bytes/Prefix** ¹ |
|-----------|------|----------|----------|-----------|------------------|
| LiteNode | 96 | 0 | 5×16=80 | 176 bytes | **17** |
| BartNode[int] | 112 | 10×8=80 | 5×16=80 | 272 bytes | **27** |
| FastNode[int] | 368 | 10×8=80 | 5×16=80 | 528 bytes | **53** |
¹ Values assume childRef = 16 bytes and pointer to payload = 8 bytes
## Extended Memory Example with Path Compression
### Path-Compressed Child Types
**Note:** In realistic scenarios, the value type `V` is typically a **pointer to a
payload struct** (e.g., `*RouteInfo`, `*Metadata`).
The calculations below assume `V = 8 bytes` (pointer size on 64-bit systems).
The actual payload struct referenced by the pointer is **not included** in these per-node calculations.
**LiteNode:**
- **LeafNode**: `netip.Prefix` only = **32 bytes** (Value field unused)
- **FringeNode**: **0 bytes** payload (prefix implicit from position, Value field unused)
**BartNode[V] / FastNode[V]:** (assuming V is a pointer = 8 bytes)
- **LeafNode[V]**: `Value (8B) + Prefix (32B)` = **40 bytes**
- **FringeNode[V]**: `Value (8B)` = **8 bytes** (prefix implicit from position)
### Realistic Scenario: Mixed Child Types
**Setup:** Node with 10 prefixes + 5 child nodes + 5 LeafNodes + 5 FringeNodes
**Total prefix items:** 10 (in-node) + 5 (leaves) + 5 (fringes) = **20 prefix items**
| Node Type | Base | Prefixes¹ | Children² | Subtotal | + Leaves³ | + Fringes⁴ | **Bytes/Prefix**⁵ |
|-----------|------|-----------|-----------|----------|-----------|------------|-------------------|
| **LiteNode** | 96 B | 0 | 15×16=240 B | 336 B | +160 B | +0 B | **16.8** |
| **BartNode[V]** | 112 B | 10×8=80 B | 15×16=240 B | 432 B | +200 B | +40 B | **21.6** |
| **FastNode[V]** | 368 B | 10×8=80 B | 15×16=240 B | 688 B | +200 B | +40 B | **34.4** |
**Notes:**
1. Prefixes: LiteNode stores no values (0B); BartNode/FastNode store 10×8B pointers to payload structs
2. Children: 15 total (5 nodes + 5 leaves + 5 fringes), each as `interface{}` = 16 bytes
3. Leaves: LiteNode stores 5×32B LeafNode (unused Value field), BartNode/FastNode store
5×40B LeafNode[V] (with 8B pointer each)
4. Fringes: LiteNode stores 0B (prefix implicit, unused Value field), BartNode/FastNode store
5×8B FringeNode[V] (8B pointer)
5. Calculated as: `Subtotal / 20` (node itself only, excluding referenced child nodes and external
payload structs)
### Memory Efficiency Insights
**LiteNode advantages:**
- **No value storage** in LeafNode: saves 8 bytes per leaf (32B vs 40B)
- **No value storage** in FringeNode: saves 8 bytes per fringe (0B vs 8B)
- **No in-node prefix values**: saves 80 bytes for 10 prefixes vs BartNode
- **Total savings:** ~22% smaller than BartNode for typical routing tables
**Path compression benefits:**
- **LeafNode**: Eliminates intermediate nodes for isolated prefixes
- **FringeNode**: Compresses /8, /16, /24 boundaries without node overhead
- **Reduced trie depth**: Fewer levels = fewer lookups per route
**Important:** The actual payload structs (e.g., routing information, metadata) are stored externally and referenced by the 8-byte pointers. Their sizes are not included in the per-node calculations above, as they are shared or application-specific.
## Lookup Performance Deep Dive
All three node types achieve **O(1) per-level lookup performance**, but must traverse trie levels:
### Trie Structure & Performance
- **8-bit strides per level**: Each trie level handles 8 bits of the IP address
- **IPv4 traversal**: Worst case 4 levels (32÷8), real-world typically 3 levels for /24 routes
- **IPv6 traversal**: Worst case 16 levels (128÷8), real-world typically 6 levels for /48 routes
- **Performance characteristic**: O(trie_depth) not O(number_of_routes)
- **IPv6 vs IPv4**: IPv6 inherently ~2× slower due to deeper tree structure
### BartNode[V] & LiteNode - Optimized Level Operations
- **Precomputed lookup tables** (`lpm.LookupTbl[idx]`) eliminate search within each level
- **BitSet256 intersections** via `IntersectionTop()` for instant prefix matching
- **Rank-based indirection**: Bitset-to-slice mapping uses precomputed Rank masks
- **Pipeline-friendly**: Only 4 bitset operations (4×uint64) per level, optimized for CPU pipelining
- **No backtracking**: Traditional longest-prefix-match backtracking replaced with direct table lookups
### FastNode[V] - Cached Child Access per Level
- **No rank recomputation per level**: Uses `childRankCache[octet]` to index sparse child storage quickly.
- **Performance advantage**: ~25% faster level traversal
+230 -134
View File
@@ -1,183 +1,279 @@
# package bart
![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/gaissmai/bart)
[![Go Reference](https://pkg.go.dev/badge/github.com/gaissmai/bart.svg)](https://pkg.go.dev/github.com/gaissmai/bart#section-documentation)
[![Mentioned in Awesome Go](https://awesome.re/mentioned-badge-flat.svg)](https://github.com/avelino/awesome-go)
[![CI](https://github.com/gaissmai/bart/actions/workflows/go.yml/badge.svg)](https://github.com/gaissmai/bart/actions/workflows/go.yml)
[![Coverage Status](https://coveralls.io/repos/github/gaissmai/bart/badge.svg)](https://coveralls.io/github/gaissmai/bart)
[![Go Report Card](https://goreportcard.com/badge/github.com/gaissmai/bart)](https://goreportcard.com/report/github.com/gaissmai/bart)
[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT)
[![Stand With Ukraine](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/badges/StandWithUkraine.svg)](https://stand-with-ukraine.pp.ua)
# package bart
The bart package provides some **Balanced Routing Tables (BART)** for
fastest IP-to-CIDR lookups and related tasks such as:
- **ACL** determine extremely fast whether an IP address matches any of millions of CIDR rules.
- **RIB** handle very large routing tables with low memory overhead, while keeping lookups fast.
- **FIB** high-speed lookups, achieve LPM in constant-time for packet forwarding in the datapath.
BART is designed for workloads where both speed and/or memory efficiency matter,
making it a best fit for firewalls, routers, or any system that needs large-scale
IP prefix matching.
## Overview
`package bart` provides a Balanced-Routing-Table (BART).
BART is implemented as a multibit trie with a fixed stride of 8 bits,
using a fast mapping function derived from Donald E. Knuth’s
**Allotment Routing Table** (ART) algorithm, to map the possible prefixes
at each level into a complete binary tree.
BART is balanced in terms of memory usage and lookup time
for the longest-prefix match.
BART implements three different routing tables, each optimized for specific
use cases:
- **bart.Lite**
- **bart.Table**
- **bart.Fast**
BART is a multibit-trie with fixed stride length of 8 bits,
using the _baseIndex_ function from the ART algorithm to
build the complete-binary-tree (CBT) of prefixes for each stride.
For **bart.Table** this binary tree is represented with popcount‑compressed
sparse arrays for **level compression**.
Combined with a **novel path and fringe compression**, this design reduces
memory consumption by nearly two orders of magnitude compared to classical ART.
The CBT is implemented as a bit-vector, backtracking is just
a matter of fast cache friendly bitmask operations.
For **bart.Fast**, an additional 256 bytes per node are used to
achieve faster level traversal in the multibit trie.
The Table is implemented with popcount compressed sparse arrays
together with path compression. This reduces storage consumption
by almost two orders of magnitude in comparison to ART with
comparable or even better lookup times for longest prefix match.
**bart.Lite** is a special form of **bart.Table**, but without a payload, and therefore
has the lowest memory overhead while maintaining the same lookup times.
The algorithm is also excellent for determining whether two tables
contain overlapping IP addresses.
## Getting Started
## Example
### Example: simple ACL with bart.Lite
```golang
func ExampleTable_Contains() {
// Create a new routing table
table := new(bart.Table[struct{}])
```go
package main
// Insert some prefixes
prefixes := []string{
"192.168.0.0/16", // corporate
"192.168.1.0/24", // department
"2001:7c0:3100::/40", // corporate
"2001:7c0:3100:1::/64", // department
"fc00::/7", // unique local
}
import (
"net/netip"
for _, s := range prefixes {
pfx := netip.MustParsePrefix(s)
table.Insert(pfx, struct{}{})
}
"github.com/gaissmai/bart"
)
// Test some IP addresses for black/whitelist containment
ips := []string{
"192.168.1.100", // must match, department
"192.168.2.1", // must match, corporate
"2001:7c0:3100:1::1", // must match, department
"2001:7c0:3100:2::1", // must match, corporate
"fc00::1", // must match, unique local
//
"172.16.0.1", // must NOT match
"2003:dead:beef::1", // must NOT match
}
var a = netip.MustParseAddr
var p = netip.MustParsePrefix
for _, s := range ips {
ip := netip.MustParseAddr(s)
fmt.Printf("%-20s is contained: %t\n", ip, table.Contains(ip))
}
func main() {
allowList := new(bart.Lite)
// Output:
// 192.168.1.100 is contained: true
// 192.168.2.1 is contained: true
// 2001:7c0:3100:1::1 is contained: true
// 2001:7c0:3100:2::1 is contained: true
// fc00::1 is contained: true
// 172.16.0.1 is contained: false
// 2003:dead:beef::1 is contained: false
// Add allowed networks
allowList.Insert(p("192.168.0.0/16"))
allowList.Insert(p("2001:db8::/32"))
allowList.Insert(p("10.0.0.0/24"))
// Test some IPs with Contains (longest-prefix match)
testIPs := []netip.Addr{
a("192.168.1.100"), // allowed (matches 192.168.0.0/16)
a("2001:db8::1"), // allowed (matches 2001:db8::/32)
a("172.16.0.1"), // denied (no match)
}
for _, ip := range testIPs {
if allowList.Contains(ip) {
fmt.Printf("✅ %s is allowed\n", ip)
} else {
fmt.Printf("❌ %s is denied\n", ip)
}
}
// Exact-prefix checks with Get (not longest-prefix match)
exactPrefixes := []netip.Prefix{
p("192.168.0.0/16"), // exists exactly
p("192.168.1.0/24"), // doesn't exist (would be covered by /16, but not stored exactly)
p("10.0.0.0/24"), // exists exactly
p("10.0.0.0/16"), // doesn't exist (only /24 is stored)
}
fmt.Println("\nExact-prefix checks:")
for _, pfx := range exactPrefixes {
if allowList.Get(pfx) {
fmt.Printf("✅ Exact prefix %s exists in table\n", pfx)
} else {
fmt.Printf("❌ Exact prefix %s not found in table\n", pfx)
}
}
}
```
## Comparison
| Aspect | Table | Lite | Fast |
|--------|-------------|-------------|-------------|
| **Per-level Speed** | ⚡ **O(1)** | ⚡ **O(1)** | 🚀 **O(1), ~25% faster** |
| **Overall Lookup** | O(trie_depth) | O(trie_depth) | O(trie_depth) |
| **IPv4 Performance** | ~3 level traversals | ~3 level traversals | ~3 level traversals |
| **IPv6 Performance** | ~6 level traversals | ~6 level traversals | ~6 level traversals |
| **IPv6 vs IPv4** | ~2× slower | ~2× slower | ~2× slower |
| **Memory** | efficient | very efficient | less efficient |
| **Update** | very fast | best | fast |
A more detailed description can be found in [NODETYPES.md](NODETYPES.md).
## When to Use Each Type
### 🎯 **bart.Table[V]** - The Balanced Choice
- **Recommended** for most routing table use cases
- Near-optimal per-level performance with excellent memory efficiency
- Very fast updates (insert/delete)
- Perfect balance for both IPv4 and IPv6 routing tables (use it for RIB)
### 🪶 **bart.Lite** - The Minimalist
- **Specialized** for prefix-only operations, no payload
- Same per-level performance as *bart.Table[V]* but ~35% less memory
- Optimal updates (insert/delete), ~25% faster than *bart.Table[V]*
- Ideal for IPv4/IPv6 allowlists and set-based operations (use it for ACL)
### 🚀 **bart.Fast[V]** - The Lookup Performance Champion
- **~25% faster** when memory constraints allow
- Fast updates (insert/delete), ~50% slower than *bart.Table[V]*
- Best choice for lookup-intensive applications (use it for FIB)
## Bitset Efficiency
The BART algorithm is based on fixed-size bit vectors and precomputed lookup tables.
Lookups are executed entirely with fast, cache-resident bitmask operations, which
modern CPUs accelerate using specialized instructions such as POPCNT, LZCNT, and TZCNT.
For maximum performance, specify the CPU feature set when compiling.
See the [Go minimum requirements](https://go.dev/wiki/MinimumRequirements#architectures) for details.
```bash
# On ARM64, Go auto-selects CPU instructions.
# Example for AMD64, choose v2/v3/v4 to match your CPU features.
GOAMD64=v3 go build
```
Critical loops over these fixed-size bitsets can be unrolled for additional speed,
ensuring predictable memory access and efficient use of CPU pipelines.
```go
func (b *BitSet256) popcnt() (cnt int) {
cnt += bits.OnesCount64(b[0])
cnt += bits.OnesCount64(b[1])
cnt += bits.OnesCount64(b[2])
cnt += bits.OnesCount64(b[3])
return
}
```
Future Go versions with SIMD intrinsics for `uint64` vectors may unlock
additional speedups on compatible hardware.
## Concurrency model
There are examples demonstrating how to use bart concurrently with multiple readers and writers.
Readers can always access the table lock‑free. Writers synchronize with a mutex so that only one writer
modifies the persistent table at a time, without relying on CAS, which can be problematic with multiple
long‑running writers.
The combination of lock-free concurrency, fast lookup and update times and low memory consumption
provides clear advantages for any routing daemon.
But as always, it depends on the specific use case.
See the concurrent tests for concrete examples of this pattern:
- [ExampleFast](example_fast_concurrent_test.go)
- [ExampleLite](example_lite_concurrent_test.go)
- [ExampleTable](example_table_concurrent_test.go)
## Additional Use Cases
Beyond high-performance prefix matching, BART also excels at detecting overlaps
between two routing tables.
In internal benchmarks the check runs in a few nanoseconds per query with zero
heap allocations on a modern CPU.
## API
Release v0.18 requires at least go1.23 and we use the `iter.Seq2[netip.Prefix, V]` types for iterators.
The lock-free versions of insert, update and delete are added, but still experimentell.
BART has a rich API for CRUD, lookup, comparison, iteration,
serialization and persistence.
```golang
import "github.com/gaissmai/bart"
type Table[V any] struct {
// Has unexported fields.
}
Table is an IPv4 and IPv6 routing table with payload V. The zero value is
ready to use.
**Table** and **Fast** expose the identical API, while **Lite** deviates in
its methods from the common API when it comes to the payload, since *Lite*
has no payload.
The Table is safe for concurrent readers but not for concurrent readers
and/or writers. Either the update operations must be protected by an
external lock mechanism or the various ...Persist functions must be used
which return a modified routing table by leaving the original unchanged
```go
import "github.com/gaissmai/bart"
A Table must not be copied by value, see Table.Clone.
type Table[V any] struct {
// Has unexported fields.
}
func (t *Table[V]) Insert(pfx netip.Prefix, val V)
func (t *Table[V]) Delete(pfx netip.Prefix)
func (t *Table[V]) Update(pfx netip.Prefix, cb func(val V, ok bool) V) (newVal V)
func (t *Table[V]) Contains(netip.Addr) bool
func (t *Table[V]) Lookup(netip.Addr) (V, bool)
func (t *Table[V]) InsertPersist(pfx netip.Prefix, val V) *Table[V]
func (t *Table[V]) DeletePersist(pfx netip.Prefix) *Table[V]
func (t *Table[V]) UpdatePersist(pfx netip.Prefix, cb func(val V, ok bool) V) (pt *Table[V], newVal V)
func (t *Table[V]) LookupPrefix(netip.Prefix) (V, bool)
func (t *Table[V]) LookupPrefixLPM(netip.Prefix) (netip.Prefix, V, bool)
func (t *Table[V]) Get(pfx netip.Prefix) (val V, ok bool)
func (t *Table[V]) GetAndDelete(pfx netip.Prefix) (val V, ok bool)
func (t *Table[V]) GetAndDeletePersist(pfx netip.Prefix) (pt *Table[V], val V, ok bool)
func (t *Table[V]) Get(netip.Prefix) (V, bool)
func (t *Table[V]) Insert(netip.Prefix, V)
func (t *Table[V]) Delete(netip.Prefix)
func (t *Table[V]) Modify(netip.Prefix, cb func(V, bool) (V, bool))
func (t *Table[V]) Union(o *Table[V])
func (t *Table[V]) Clone() *Table[V]
func (t *Table[V]) InsertPersist(netip.Prefix, V) *Table[V]
func (t *Table[V]) DeletePersist(netip.Prefix) *Table[V]
func (t *Table[V]) ModifyPersist(netip.Prefix, cb func(V, bool) (V, bool)) *Table[V]
func (t *Table[V]) Contains(ip netip.Addr) bool
func (t *Table[V]) Lookup(ip netip.Addr) (val V, ok bool)
func (t *Table[V]) LookupPrefix(pfx netip.Prefix) (val V, ok bool)
func (t *Table[V]) LookupPrefixLPM(pfx netip.Prefix) (lpm netip.Prefix, val V, ok bool)
func (t *Table[V]) Clone() *Table[V]
func (t *Table[V]) Union(o *Table[V])
func (t *Table[V]) UnionPersist(o *Table[V]) *Table[V]
func (t *Table[V]) OverlapsPrefix(pfx netip.Prefix) bool
func (t *Table[V]) OverlapsPrefix(netip.Prefix) bool
func (t *Table[V]) Overlaps(o *Table[V]) bool
func (t *Table[V]) Overlaps4(o *Table[V]) bool
func (t *Table[V]) Overlaps6(o *Table[V]) bool
func (t *Table[V]) Overlaps(o *Table[V]) bool
func (t *Table[V]) Overlaps4(o *Table[V]) bool
func (t *Table[V]) Overlaps6(o *Table[V]) bool
func (t *Table[V]) Subnets(pfx netip.Prefix) iter.Seq2[netip.Prefix, V]
func (t *Table[V]) Supernets(pfx netip.Prefix) iter.Seq2[netip.Prefix, V]
func (t *Table[V]) Equal(o *Table[V]) bool
func (t *Table[V]) All() iter.Seq2[netip.Prefix, V]
func (t *Table[V]) All4() iter.Seq2[netip.Prefix, V]
func (t *Table[V]) All6() iter.Seq2[netip.Prefix, V]
func (t *Table[V]) Subnets(netip.Prefix) iter.Seq2[netip.Prefix, V]
func (t *Table[V]) Supernets(netip.Prefix) iter.Seq2[netip.Prefix, V]
func (t *Table[V]) AllSorted() iter.Seq2[netip.Prefix, V]
func (t *Table[V]) AllSorted4() iter.Seq2[netip.Prefix, V]
func (t *Table[V]) AllSorted6() iter.Seq2[netip.Prefix, V]
func (t *Table[V]) All() iter.Seq2[netip.Prefix, V]
func (t *Table[V]) All4() iter.Seq2[netip.Prefix, V]
func (t *Table[V]) All6() iter.Seq2[netip.Prefix, V]
func (t *Table[V]) Size() int
func (t *Table[V]) Size4() int
func (t *Table[V]) Size6() int
func (t *Table[V]) AllSorted() iter.Seq2[netip.Prefix, V]
func (t *Table[V]) AllSorted4() iter.Seq2[netip.Prefix, V]
func (t *Table[V]) AllSorted6() iter.Seq2[netip.Prefix, V]
func (t *Table[V]) String() string
func (t *Table[V]) Fprint(w io.Writer) error
func (t *Table[V]) MarshalText() ([]byte, error)
func (t *Table[V]) MarshalJSON() ([]byte, error)
func (t *Table[V]) Size() int
func (t *Table[V]) Size4() int
func (t *Table[V]) Size6() int
func (t *Table[V]) DumpList4() []DumpListNode[V]
func (t *Table[V]) DumpList6() []DumpListNode[V]
func (t *Table[V]) Fprint(w io.Writer) error
func (t *Table[V]) MarshalText() ([]byte, error)
func (t *Table[V]) MarshalJSON() ([]byte, error)
func (t *Table[V]) DumpList4() []DumpListNode[V]
func (t *Table[V]) DumpList6() []DumpListNode[V]
```
## benchmarks
## Benchmarks
Please see the extensive [benchmarks](https://github.com/gaissmai/iprbench) comparing `bart` with other IP routing table implementations.
Please see the extensive [benchmarks](https://github.com/gaissmai/iprbench)
comparing `bart` with other IP routing table implementations.
Just a teaser, Contains and Lookups against the full Internet routing table with random IP address probes:
Just a teaser, `Fast.Lookup` against the Tier1 full
Internet routing table with random IP address probes:
```
```text
$ GOAMD64=v3 go test -run=xxx -bench=FullFastM/Lookup$ -cpu=1
goos: linux
goarch: amd64
pkg: github.com/gaissmai/bart
cpu: Intel(R) Core(TM) i5-8250U CPU @ 1.60GHz
BenchmarkFullMatchV4/Contains 55906228 21.39 ns/op 0 B/op 0 allocs/op
BenchmarkFullMatchV6/Contains 100000000 11.38 ns/op 0 B/op 0 allocs/op
BenchmarkFullMissV4/Contains 52927538 22.69 ns/op 0 B/op 0 allocs/op
BenchmarkFullMissV6/Contains 250386540 4.883 ns/op 0 B/op 0 allocs/op
cpu: AMD Ryzen 7 PRO 4750U with Radeon Graphics
BenchmarkFullFastMatch4/Lookup 129484557 9.274 ns/op
BenchmarkFullFastMatch6/Lookup 65185531 17.99 ns/op
BenchmarkFullFastMiss4/Lookup 92448910 12.81 ns/op
BenchmarkFullFastMiss6/Lookup 64323817 18.66 ns/op
PASS
ok github.com/gaissmai/bart 11.291s
goos: linux
goarch: amd64
pkg: github.com/gaissmai/bart
cpu: Intel(R) Core(TM) i5-8250U CPU @ 1.60GHz
BenchmarkFullMatchV4/Lookup 53578766 21.77 ns/op 0 B/op 0 allocs/op
BenchmarkFullMatchV6/Lookup 57054618 21.07 ns/op 0 B/op 0 allocs/op
BenchmarkFullMissV4/Lookup 48289306 25.60 ns/op 0 B/op 0 allocs/op
BenchmarkFullMissV6/Lookup 142917571 8.387 ns/op 0 B/op 0 allocs/op
PASS
ok github.com/gaissmai/bart 10.455s
ok github.com/gaissmai/bart 6.647s
```
## Compatibility Guarantees
@@ -188,20 +284,20 @@ to be revisited to better support the use cases that the library solves for.
These occurrences are expected to be rare in frequency and the API is already quite stable.
## CONTRIBUTION
## Contribution
Please open an issue for discussion before sending a pull request.
## CREDIT
## Credit
Standing on the shoulders of giants.
Credits for many inspirations go to
- the clever guys at tailscale,
- the clever folks at Tailscale,
- to Daniel Lemire for his inspiring blog,
- to Donald E. Knuth for the **ART** routing algorithm and
- to Yoichi Hariguchi who deciphered it for us mere mortals
- to Donald E. Knuth for the Allotment Routing Table [ART](https://github.com/hariguchi/art/tree/master/orig) algorithm and
- to Yoichi Hariguchi who [deciphered it](https://github.com/hariguchi/art/blob/master/docs/art.pdf) for us mere mortals
And last but not least to the Go team who do a wonderful job!
+1 -1
View File
@@ -2,4 +2,4 @@
## Reporting a Vulnerability
You can report privately a vulnerability by email at karl.gaissmaier@uni-ulm.de (author and maintainer).
You can report vulnerabilities privately via email: <mailto:karl.gaissmaier@uni-ulm.de>.
-542
View File
@@ -1,542 +0,0 @@
// Copyright (c) 2024 Karl Gaissmaier
// SPDX-License-Identifier: MIT
package bart
import "github.com/gaissmai/bart/internal/bitset"
// allotLookupTbl, as precalculated bitsets,
// map the baseIndex to bitset with precomputed complete binary tree.
//
// // 1 <= idx <= 511
// func allotRec(aTbl *bitset.BitSet, idx uint) {
// aTbl = aTbl.Set(idx)
// if idx >= 256 {
// return
// }
// allotRec(aTbl, idx<<1)
// allotRec(aTbl, idx<<1+1)
// }
//
// Only used for fast bitset intersections instead of
// range loops in table overlaps methods.
//
// Please read the ART paper ./doc/artlookup.pdf to understand the allotment algorithm.
var allotLookupTbl = [512]bitset.BitSet{
/* idx: 0 */ {}, // idx is invalid
/* idx: 1 */ {0xfffffffffffffffe, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff},
/* idx: 2 */ {0xffff00ff0f34, 0xffffffff, 0xffffffffffffffff, 0x0, 0xffffffffffffffff, 0xffffffffffffffff},
/* idx: 3 */ {0xffff0000ff00f0c8, 0xffffffff00000000, 0x0, 0xffffffffffffffff, 0x0, 0x0, 0xffffffffffffffff, 0xffffffffffffffff},
/* idx: 4 */ {0xff000f0310, 0xffff, 0xffffffff, 0x0, 0xffffffffffffffff},
/* idx: 5 */ {0xff0000f00c20, 0xffff0000, 0xffffffff00000000, 0x0, 0x0, 0xffffffffffffffff},
/* idx: 6 */ {0xff00000f003040, 0xffff00000000, 0x0, 0xffffffff, 0x0, 0x0, 0xffffffffffffffff},
/* idx: 7 */ {0xff000000f000c080, 0xffff000000000000, 0x0, 0xffffffff00000000, 0x0, 0x0, 0x0, 0xffffffffffffffff},
/* idx: 8 */ {0xf00030100, 0xff, 0xffff, 0x0, 0xffffffff},
/* idx: 9 */ {0xf0000c0200, 0xff00, 0xffff0000, 0x0, 0xffffffff00000000},
/* idx: 10 */ {0xf0000300400, 0xff0000, 0xffff00000000, 0x0, 0x0, 0xffffffff},
/* idx: 11 */ {0xf00000c00800, 0xff000000, 0xffff000000000000, 0x0, 0x0, 0xffffffff00000000},
/* idx: 12 */ {0xf000003001000, 0xff00000000, 0x0, 0xffff, 0x0, 0x0, 0xffffffff},
/* idx: 13 */ {0xf000000c002000, 0xff0000000000, 0x0, 0xffff0000, 0x0, 0x0, 0xffffffff00000000},
/* idx: 14 */ {0xf00000030004000, 0xff000000000000, 0x0, 0xffff00000000, 0x0, 0x0, 0x0, 0xffffffff},
/* idx: 15 */ {0xf0000000c0008000, 0xff00000000000000, 0x0, 0xffff000000000000, 0x0, 0x0, 0x0, 0xffffffff00000000},
/* idx: 16 */ {0x300010000, 0xf, 0xff, 0x0, 0xffff},
/* idx: 17 */ {0xc00020000, 0xf0, 0xff00, 0x0, 0xffff0000},
/* idx: 18 */ {0x3000040000, 0xf00, 0xff0000, 0x0, 0xffff00000000},
/* idx: 19 */ {0xc000080000, 0xf000, 0xff000000, 0x0, 0xffff000000000000},
/* idx: 20 */ {0x30000100000, 0xf0000, 0xff00000000, 0x0, 0x0, 0xffff},
/* idx: 21 */ {0xc0000200000, 0xf00000, 0xff0000000000, 0x0, 0x0, 0xffff0000},
/* idx: 22 */ {0x300000400000, 0xf000000, 0xff000000000000, 0x0, 0x0, 0xffff00000000},
/* idx: 23 */ {0xc00000800000, 0xf0000000, 0xff00000000000000, 0x0, 0x0, 0xffff000000000000},
/* idx: 24 */ {0x3000001000000, 0xf00000000, 0x0, 0xff, 0x0, 0x0, 0xffff},
/* idx: 25 */ {0xc000002000000, 0xf000000000, 0x0, 0xff00, 0x0, 0x0, 0xffff0000},
/* idx: 26 */ {0x30000004000000, 0xf0000000000, 0x0, 0xff0000, 0x0, 0x0, 0xffff00000000},
/* idx: 27 */ {0xc0000008000000, 0xf00000000000, 0x0, 0xff000000, 0x0, 0x0, 0xffff000000000000},
/* idx: 28 */ {0x300000010000000, 0xf000000000000, 0x0, 0xff00000000, 0x0, 0x0, 0x0, 0xffff},
/* idx: 29 */ {0xc00000020000000, 0xf0000000000000, 0x0, 0xff0000000000, 0x0, 0x0, 0x0, 0xffff0000},
/* idx: 30 */ {0x3000000040000000, 0xf00000000000000, 0x0, 0xff000000000000, 0x0, 0x0, 0x0, 0xffff00000000},
/* idx: 31 */ {0xc000000080000000, 0xf000000000000000, 0x0, 0xff00000000000000, 0x0, 0x0, 0x0, 0xffff000000000000},
/* idx: 32 */ {0x100000000, 0x3, 0xf, 0x0, 0xff},
/* idx: 33 */ {0x200000000, 0xc, 0xf0, 0x0, 0xff00},
/* idx: 34 */ {0x400000000, 0x30, 0xf00, 0x0, 0xff0000},
/* idx: 35 */ {0x800000000, 0xc0, 0xf000, 0x0, 0xff000000},
/* idx: 36 */ {0x1000000000, 0x300, 0xf0000, 0x0, 0xff00000000},
/* idx: 37 */ {0x2000000000, 0xc00, 0xf00000, 0x0, 0xff0000000000},
/* idx: 38 */ {0x4000000000, 0x3000, 0xf000000, 0x0, 0xff000000000000},
/* idx: 39 */ {0x8000000000, 0xc000, 0xf0000000, 0x0, 0xff00000000000000},
/* idx: 40 */ {0x10000000000, 0x30000, 0xf00000000, 0x0, 0x0, 0xff},
/* idx: 41 */ {0x20000000000, 0xc0000, 0xf000000000, 0x0, 0x0, 0xff00},
/* idx: 42 */ {0x40000000000, 0x300000, 0xf0000000000, 0x0, 0x0, 0xff0000},
/* idx: 43 */ {0x80000000000, 0xc00000, 0xf00000000000, 0x0, 0x0, 0xff000000},
/* idx: 44 */ {0x100000000000, 0x3000000, 0xf000000000000, 0x0, 0x0, 0xff00000000},
/* idx: 45 */ {0x200000000000, 0xc000000, 0xf0000000000000, 0x0, 0x0, 0xff0000000000},
/* idx: 46 */ {0x400000000000, 0x30000000, 0xf00000000000000, 0x0, 0x0, 0xff000000000000},
/* idx: 47 */ {0x800000000000, 0xc0000000, 0xf000000000000000, 0x0, 0x0, 0xff00000000000000},
/* idx: 48 */ {0x1000000000000, 0x300000000, 0x0, 0xf, 0x0, 0x0, 0xff},
/* idx: 49 */ {0x2000000000000, 0xc00000000, 0x0, 0xf0, 0x0, 0x0, 0xff00},
/* idx: 50 */ {0x4000000000000, 0x3000000000, 0x0, 0xf00, 0x0, 0x0, 0xff0000},
/* idx: 51 */ {0x8000000000000, 0xc000000000, 0x0, 0xf000, 0x0, 0x0, 0xff000000},
/* idx: 52 */ {0x10000000000000, 0x30000000000, 0x0, 0xf0000, 0x0, 0x0, 0xff00000000},
/* idx: 53 */ {0x20000000000000, 0xc0000000000, 0x0, 0xf00000, 0x0, 0x0, 0xff0000000000},
/* idx: 54 */ {0x40000000000000, 0x300000000000, 0x0, 0xf000000, 0x0, 0x0, 0xff000000000000},
/* idx: 55 */ {0x80000000000000, 0xc00000000000, 0x0, 0xf0000000, 0x0, 0x0, 0xff00000000000000},
/* idx: 56 */ {0x100000000000000, 0x3000000000000, 0x0, 0xf00000000, 0x0, 0x0, 0x0, 0xff},
/* idx: 57 */ {0x200000000000000, 0xc000000000000, 0x0, 0xf000000000, 0x0, 0x0, 0x0, 0xff00},
/* idx: 58 */ {0x400000000000000, 0x30000000000000, 0x0, 0xf0000000000, 0x0, 0x0, 0x0, 0xff0000},
/* idx: 59 */ {0x800000000000000, 0xc0000000000000, 0x0, 0xf00000000000, 0x0, 0x0, 0x0, 0xff000000},
/* idx: 60 */ {0x1000000000000000, 0x300000000000000, 0x0, 0xf000000000000, 0x0, 0x0, 0x0, 0xff00000000},
/* idx: 61 */ {0x2000000000000000, 0xc00000000000000, 0x0, 0xf0000000000000, 0x0, 0x0, 0x0, 0xff0000000000},
/* idx: 62 */ {0x4000000000000000, 0x3000000000000000, 0x0, 0xf00000000000000, 0x0, 0x0, 0x0, 0xff000000000000},
/* idx: 63 */ {0x8000000000000000, 0xc000000000000000, 0x0, 0xf000000000000000, 0x0, 0x0, 0x0, 0xff00000000000000},
/* idx: 64 */ {0x0, 0x1, 0x3, 0x0, 0xf},
/* idx: 65 */ {0x0, 0x2, 0xc, 0x0, 0xf0},
/* idx: 66 */ {0x0, 0x4, 0x30, 0x0, 0xf00},
/* idx: 67 */ {0x0, 0x8, 0xc0, 0x0, 0xf000},
/* idx: 68 */ {0x0, 0x10, 0x300, 0x0, 0xf0000},
/* idx: 69 */ {0x0, 0x20, 0xc00, 0x0, 0xf00000},
/* idx: 70 */ {0x0, 0x40, 0x3000, 0x0, 0xf000000},
/* idx: 71 */ {0x0, 0x80, 0xc000, 0x0, 0xf0000000},
/* idx: 72 */ {0x0, 0x100, 0x30000, 0x0, 0xf00000000},
/* idx: 73 */ {0x0, 0x200, 0xc0000, 0x0, 0xf000000000},
/* idx: 74 */ {0x0, 0x400, 0x300000, 0x0, 0xf0000000000},
/* idx: 75 */ {0x0, 0x800, 0xc00000, 0x0, 0xf00000000000},
/* idx: 76 */ {0x0, 0x1000, 0x3000000, 0x0, 0xf000000000000},
/* idx: 77 */ {0x0, 0x2000, 0xc000000, 0x0, 0xf0000000000000},
/* idx: 78 */ {0x0, 0x4000, 0x30000000, 0x0, 0xf00000000000000},
/* idx: 79 */ {0x0, 0x8000, 0xc0000000, 0x0, 0xf000000000000000},
/* idx: 80 */ {0x0, 0x10000, 0x300000000, 0x0, 0x0, 0xf},
/* idx: 81 */ {0x0, 0x20000, 0xc00000000, 0x0, 0x0, 0xf0},
/* idx: 82 */ {0x0, 0x40000, 0x3000000000, 0x0, 0x0, 0xf00},
/* idx: 83 */ {0x0, 0x80000, 0xc000000000, 0x0, 0x0, 0xf000},
/* idx: 84 */ {0x0, 0x100000, 0x30000000000, 0x0, 0x0, 0xf0000},
/* idx: 85 */ {0x0, 0x200000, 0xc0000000000, 0x0, 0x0, 0xf00000},
/* idx: 86 */ {0x0, 0x400000, 0x300000000000, 0x0, 0x0, 0xf000000},
/* idx: 87 */ {0x0, 0x800000, 0xc00000000000, 0x0, 0x0, 0xf0000000},
/* idx: 88 */ {0x0, 0x1000000, 0x3000000000000, 0x0, 0x0, 0xf00000000},
/* idx: 89 */ {0x0, 0x2000000, 0xc000000000000, 0x0, 0x0, 0xf000000000},
/* idx: 90 */ {0x0, 0x4000000, 0x30000000000000, 0x0, 0x0, 0xf0000000000},
/* idx: 91 */ {0x0, 0x8000000, 0xc0000000000000, 0x0, 0x0, 0xf00000000000},
/* idx: 92 */ {0x0, 0x10000000, 0x300000000000000, 0x0, 0x0, 0xf000000000000},
/* idx: 93 */ {0x0, 0x20000000, 0xc00000000000000, 0x0, 0x0, 0xf0000000000000},
/* idx: 94 */ {0x0, 0x40000000, 0x3000000000000000, 0x0, 0x0, 0xf00000000000000},
/* idx: 95 */ {0x0, 0x80000000, 0xc000000000000000, 0x0, 0x0, 0xf000000000000000},
/* idx: 96 */ {0x0, 0x100000000, 0x0, 0x3, 0x0, 0x0, 0xf},
/* idx: 97 */ {0x0, 0x200000000, 0x0, 0xc, 0x0, 0x0, 0xf0},
/* idx: 98 */ {0x0, 0x400000000, 0x0, 0x30, 0x0, 0x0, 0xf00},
/* idx: 99 */ {0x0, 0x800000000, 0x0, 0xc0, 0x0, 0x0, 0xf000},
/* idx: 100 */ {0x0, 0x1000000000, 0x0, 0x300, 0x0, 0x0, 0xf0000},
/* idx: 101 */ {0x0, 0x2000000000, 0x0, 0xc00, 0x0, 0x0, 0xf00000},
/* idx: 102 */ {0x0, 0x4000000000, 0x0, 0x3000, 0x0, 0x0, 0xf000000},
/* idx: 103 */ {0x0, 0x8000000000, 0x0, 0xc000, 0x0, 0x0, 0xf0000000},
/* idx: 104 */ {0x0, 0x10000000000, 0x0, 0x30000, 0x0, 0x0, 0xf00000000},
/* idx: 105 */ {0x0, 0x20000000000, 0x0, 0xc0000, 0x0, 0x0, 0xf000000000},
/* idx: 106 */ {0x0, 0x40000000000, 0x0, 0x300000, 0x0, 0x0, 0xf0000000000},
/* idx: 107 */ {0x0, 0x80000000000, 0x0, 0xc00000, 0x0, 0x0, 0xf00000000000},
/* idx: 108 */ {0x0, 0x100000000000, 0x0, 0x3000000, 0x0, 0x0, 0xf000000000000},
/* idx: 109 */ {0x0, 0x200000000000, 0x0, 0xc000000, 0x0, 0x0, 0xf0000000000000},
/* idx: 110 */ {0x0, 0x400000000000, 0x0, 0x30000000, 0x0, 0x0, 0xf00000000000000},
/* idx: 111 */ {0x0, 0x800000000000, 0x0, 0xc0000000, 0x0, 0x0, 0xf000000000000000},
/* idx: 112 */ {0x0, 0x1000000000000, 0x0, 0x300000000, 0x0, 0x0, 0x0, 0xf},
/* idx: 113 */ {0x0, 0x2000000000000, 0x0, 0xc00000000, 0x0, 0x0, 0x0, 0xf0},
/* idx: 114 */ {0x0, 0x4000000000000, 0x0, 0x3000000000, 0x0, 0x0, 0x0, 0xf00},
/* idx: 115 */ {0x0, 0x8000000000000, 0x0, 0xc000000000, 0x0, 0x0, 0x0, 0xf000},
/* idx: 116 */ {0x0, 0x10000000000000, 0x0, 0x30000000000, 0x0, 0x0, 0x0, 0xf0000},
/* idx: 117 */ {0x0, 0x20000000000000, 0x0, 0xc0000000000, 0x0, 0x0, 0x0, 0xf00000},
/* idx: 118 */ {0x0, 0x40000000000000, 0x0, 0x300000000000, 0x0, 0x0, 0x0, 0xf000000},
/* idx: 119 */ {0x0, 0x80000000000000, 0x0, 0xc00000000000, 0x0, 0x0, 0x0, 0xf0000000},
/* idx: 120 */ {0x0, 0x100000000000000, 0x0, 0x3000000000000, 0x0, 0x0, 0x0, 0xf00000000},
/* idx: 121 */ {0x0, 0x200000000000000, 0x0, 0xc000000000000, 0x0, 0x0, 0x0, 0xf000000000},
/* idx: 122 */ {0x0, 0x400000000000000, 0x0, 0x30000000000000, 0x0, 0x0, 0x0, 0xf0000000000},
/* idx: 123 */ {0x0, 0x800000000000000, 0x0, 0xc0000000000000, 0x0, 0x0, 0x0, 0xf00000000000},
/* idx: 124 */ {0x0, 0x1000000000000000, 0x0, 0x300000000000000, 0x0, 0x0, 0x0, 0xf000000000000},
/* idx: 125 */ {0x0, 0x2000000000000000, 0x0, 0xc00000000000000, 0x0, 0x0, 0x0, 0xf0000000000000},
/* idx: 126 */ {0x0, 0x4000000000000000, 0x0, 0x3000000000000000, 0x0, 0x0, 0x0, 0xf00000000000000},
/* idx: 127 */ {0x0, 0x8000000000000000, 0x0, 0xc000000000000000, 0x0, 0x0, 0x0, 0xf000000000000000},
/* idx: 128 */ {0x0, 0x0, 0x1, 0x0, 0x3},
/* idx: 129 */ {0x0, 0x0, 0x2, 0x0, 0xc},
/* idx: 130 */ {0x0, 0x0, 0x4, 0x0, 0x30},
/* idx: 131 */ {0x0, 0x0, 0x8, 0x0, 0xc0},
/* idx: 132 */ {0x0, 0x0, 0x10, 0x0, 0x300},
/* idx: 133 */ {0x0, 0x0, 0x20, 0x0, 0xc00},
/* idx: 134 */ {0x0, 0x0, 0x40, 0x0, 0x3000},
/* idx: 135 */ {0x0, 0x0, 0x80, 0x0, 0xc000},
/* idx: 136 */ {0x0, 0x0, 0x100, 0x0, 0x30000},
/* idx: 137 */ {0x0, 0x0, 0x200, 0x0, 0xc0000},
/* idx: 138 */ {0x0, 0x0, 0x400, 0x0, 0x300000},
/* idx: 139 */ {0x0, 0x0, 0x800, 0x0, 0xc00000},
/* idx: 140 */ {0x0, 0x0, 0x1000, 0x0, 0x3000000},
/* idx: 141 */ {0x0, 0x0, 0x2000, 0x0, 0xc000000},
/* idx: 142 */ {0x0, 0x0, 0x4000, 0x0, 0x30000000},
/* idx: 143 */ {0x0, 0x0, 0x8000, 0x0, 0xc0000000},
/* idx: 144 */ {0x0, 0x0, 0x10000, 0x0, 0x300000000},
/* idx: 145 */ {0x0, 0x0, 0x20000, 0x0, 0xc00000000},
/* idx: 146 */ {0x0, 0x0, 0x40000, 0x0, 0x3000000000},
/* idx: 147 */ {0x0, 0x0, 0x80000, 0x0, 0xc000000000},
/* idx: 148 */ {0x0, 0x0, 0x100000, 0x0, 0x30000000000},
/* idx: 149 */ {0x0, 0x0, 0x200000, 0x0, 0xc0000000000},
/* idx: 150 */ {0x0, 0x0, 0x400000, 0x0, 0x300000000000},
/* idx: 151 */ {0x0, 0x0, 0x800000, 0x0, 0xc00000000000},
/* idx: 152 */ {0x0, 0x0, 0x1000000, 0x0, 0x3000000000000},
/* idx: 153 */ {0x0, 0x0, 0x2000000, 0x0, 0xc000000000000},
/* idx: 154 */ {0x0, 0x0, 0x4000000, 0x0, 0x30000000000000},
/* idx: 155 */ {0x0, 0x0, 0x8000000, 0x0, 0xc0000000000000},
/* idx: 156 */ {0x0, 0x0, 0x10000000, 0x0, 0x300000000000000},
/* idx: 157 */ {0x0, 0x0, 0x20000000, 0x0, 0xc00000000000000},
/* idx: 158 */ {0x0, 0x0, 0x40000000, 0x0, 0x3000000000000000},
/* idx: 159 */ {0x0, 0x0, 0x80000000, 0x0, 0xc000000000000000},
/* idx: 160 */ {0x0, 0x0, 0x100000000, 0x0, 0x0, 0x3},
/* idx: 161 */ {0x0, 0x0, 0x200000000, 0x0, 0x0, 0xc},
/* idx: 162 */ {0x0, 0x0, 0x400000000, 0x0, 0x0, 0x30},
/* idx: 163 */ {0x0, 0x0, 0x800000000, 0x0, 0x0, 0xc0},
/* idx: 164 */ {0x0, 0x0, 0x1000000000, 0x0, 0x0, 0x300},
/* idx: 165 */ {0x0, 0x0, 0x2000000000, 0x0, 0x0, 0xc00},
/* idx: 166 */ {0x0, 0x0, 0x4000000000, 0x0, 0x0, 0x3000},
/* idx: 167 */ {0x0, 0x0, 0x8000000000, 0x0, 0x0, 0xc000},
/* idx: 168 */ {0x0, 0x0, 0x10000000000, 0x0, 0x0, 0x30000},
/* idx: 169 */ {0x0, 0x0, 0x20000000000, 0x0, 0x0, 0xc0000},
/* idx: 170 */ {0x0, 0x0, 0x40000000000, 0x0, 0x0, 0x300000},
/* idx: 171 */ {0x0, 0x0, 0x80000000000, 0x0, 0x0, 0xc00000},
/* idx: 172 */ {0x0, 0x0, 0x100000000000, 0x0, 0x0, 0x3000000},
/* idx: 173 */ {0x0, 0x0, 0x200000000000, 0x0, 0x0, 0xc000000},
/* idx: 174 */ {0x0, 0x0, 0x400000000000, 0x0, 0x0, 0x30000000},
/* idx: 175 */ {0x0, 0x0, 0x800000000000, 0x0, 0x0, 0xc0000000},
/* idx: 176 */ {0x0, 0x0, 0x1000000000000, 0x0, 0x0, 0x300000000},
/* idx: 177 */ {0x0, 0x0, 0x2000000000000, 0x0, 0x0, 0xc00000000},
/* idx: 178 */ {0x0, 0x0, 0x4000000000000, 0x0, 0x0, 0x3000000000},
/* idx: 179 */ {0x0, 0x0, 0x8000000000000, 0x0, 0x0, 0xc000000000},
/* idx: 180 */ {0x0, 0x0, 0x10000000000000, 0x0, 0x0, 0x30000000000},
/* idx: 181 */ {0x0, 0x0, 0x20000000000000, 0x0, 0x0, 0xc0000000000},
/* idx: 182 */ {0x0, 0x0, 0x40000000000000, 0x0, 0x0, 0x300000000000},
/* idx: 183 */ {0x0, 0x0, 0x80000000000000, 0x0, 0x0, 0xc00000000000},
/* idx: 184 */ {0x0, 0x0, 0x100000000000000, 0x0, 0x0, 0x3000000000000},
/* idx: 185 */ {0x0, 0x0, 0x200000000000000, 0x0, 0x0, 0xc000000000000},
/* idx: 186 */ {0x0, 0x0, 0x400000000000000, 0x0, 0x0, 0x30000000000000},
/* idx: 187 */ {0x0, 0x0, 0x800000000000000, 0x0, 0x0, 0xc0000000000000},
/* idx: 188 */ {0x0, 0x0, 0x1000000000000000, 0x0, 0x0, 0x300000000000000},
/* idx: 189 */ {0x0, 0x0, 0x2000000000000000, 0x0, 0x0, 0xc00000000000000},
/* idx: 190 */ {0x0, 0x0, 0x4000000000000000, 0x0, 0x0, 0x3000000000000000},
/* idx: 191 */ {0x0, 0x0, 0x8000000000000000, 0x0, 0x0, 0xc000000000000000},
/* idx: 192 */ {0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x3},
/* idx: 193 */ {0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0xc},
/* idx: 194 */ {0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x30},
/* idx: 195 */ {0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0xc0},
/* idx: 196 */ {0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x300},
/* idx: 197 */ {0x0, 0x0, 0x0, 0x20, 0x0, 0x0, 0xc00},
/* idx: 198 */ {0x0, 0x0, 0x0, 0x40, 0x0, 0x0, 0x3000},
/* idx: 199 */ {0x0, 0x0, 0x0, 0x80, 0x0, 0x0, 0xc000},
/* idx: 200 */ {0x0, 0x0, 0x0, 0x100, 0x0, 0x0, 0x30000},
/* idx: 201 */ {0x0, 0x0, 0x0, 0x200, 0x0, 0x0, 0xc0000},
/* idx: 202 */ {0x0, 0x0, 0x0, 0x400, 0x0, 0x0, 0x300000},
/* idx: 203 */ {0x0, 0x0, 0x0, 0x800, 0x0, 0x0, 0xc00000},
/* idx: 204 */ {0x0, 0x0, 0x0, 0x1000, 0x0, 0x0, 0x3000000},
/* idx: 205 */ {0x0, 0x0, 0x0, 0x2000, 0x0, 0x0, 0xc000000},
/* idx: 206 */ {0x0, 0x0, 0x0, 0x4000, 0x0, 0x0, 0x30000000},
/* idx: 207 */ {0x0, 0x0, 0x0, 0x8000, 0x0, 0x0, 0xc0000000},
/* idx: 208 */ {0x0, 0x0, 0x0, 0x10000, 0x0, 0x0, 0x300000000},
/* idx: 209 */ {0x0, 0x0, 0x0, 0x20000, 0x0, 0x0, 0xc00000000},
/* idx: 210 */ {0x0, 0x0, 0x0, 0x40000, 0x0, 0x0, 0x3000000000},
/* idx: 211 */ {0x0, 0x0, 0x0, 0x80000, 0x0, 0x0, 0xc000000000},
/* idx: 212 */ {0x0, 0x0, 0x0, 0x100000, 0x0, 0x0, 0x30000000000},
/* idx: 213 */ {0x0, 0x0, 0x0, 0x200000, 0x0, 0x0, 0xc0000000000},
/* idx: 214 */ {0x0, 0x0, 0x0, 0x400000, 0x0, 0x0, 0x300000000000},
/* idx: 215 */ {0x0, 0x0, 0x0, 0x800000, 0x0, 0x0, 0xc00000000000},
/* idx: 216 */ {0x0, 0x0, 0x0, 0x1000000, 0x0, 0x0, 0x3000000000000},
/* idx: 217 */ {0x0, 0x0, 0x0, 0x2000000, 0x0, 0x0, 0xc000000000000},
/* idx: 218 */ {0x0, 0x0, 0x0, 0x4000000, 0x0, 0x0, 0x30000000000000},
/* idx: 219 */ {0x0, 0x0, 0x0, 0x8000000, 0x0, 0x0, 0xc0000000000000},
/* idx: 220 */ {0x0, 0x0, 0x0, 0x10000000, 0x0, 0x0, 0x300000000000000},
/* idx: 221 */ {0x0, 0x0, 0x0, 0x20000000, 0x0, 0x0, 0xc00000000000000},
/* idx: 222 */ {0x0, 0x0, 0x0, 0x40000000, 0x0, 0x0, 0x3000000000000000},
/* idx: 223 */ {0x0, 0x0, 0x0, 0x80000000, 0x0, 0x0, 0xc000000000000000},
/* idx: 224 */ {0x0, 0x0, 0x0, 0x100000000, 0x0, 0x0, 0x0, 0x3},
/* idx: 225 */ {0x0, 0x0, 0x0, 0x200000000, 0x0, 0x0, 0x0, 0xc},
/* idx: 226 */ {0x0, 0x0, 0x0, 0x400000000, 0x0, 0x0, 0x0, 0x30},
/* idx: 227 */ {0x0, 0x0, 0x0, 0x800000000, 0x0, 0x0, 0x0, 0xc0},
/* idx: 228 */ {0x0, 0x0, 0x0, 0x1000000000, 0x0, 0x0, 0x0, 0x300},
/* idx: 229 */ {0x0, 0x0, 0x0, 0x2000000000, 0x0, 0x0, 0x0, 0xc00},
/* idx: 230 */ {0x0, 0x0, 0x0, 0x4000000000, 0x0, 0x0, 0x0, 0x3000},
/* idx: 231 */ {0x0, 0x0, 0x0, 0x8000000000, 0x0, 0x0, 0x0, 0xc000},
/* idx: 232 */ {0x0, 0x0, 0x0, 0x10000000000, 0x0, 0x0, 0x0, 0x30000},
/* idx: 233 */ {0x0, 0x0, 0x0, 0x20000000000, 0x0, 0x0, 0x0, 0xc0000},
/* idx: 234 */ {0x0, 0x0, 0x0, 0x40000000000, 0x0, 0x0, 0x0, 0x300000},
/* idx: 235 */ {0x0, 0x0, 0x0, 0x80000000000, 0x0, 0x0, 0x0, 0xc00000},
/* idx: 236 */ {0x0, 0x0, 0x0, 0x100000000000, 0x0, 0x0, 0x0, 0x3000000},
/* idx: 237 */ {0x0, 0x0, 0x0, 0x200000000000, 0x0, 0x0, 0x0, 0xc000000},
/* idx: 238 */ {0x0, 0x0, 0x0, 0x400000000000, 0x0, 0x0, 0x0, 0x30000000},
/* idx: 239 */ {0x0, 0x0, 0x0, 0x800000000000, 0x0, 0x0, 0x0, 0xc0000000},
/* idx: 240 */ {0x0, 0x0, 0x0, 0x1000000000000, 0x0, 0x0, 0x0, 0x300000000},
/* idx: 241 */ {0x0, 0x0, 0x0, 0x2000000000000, 0x0, 0x0, 0x0, 0xc00000000},
/* idx: 242 */ {0x0, 0x0, 0x0, 0x4000000000000, 0x0, 0x0, 0x0, 0x3000000000},
/* idx: 243 */ {0x0, 0x0, 0x0, 0x8000000000000, 0x0, 0x0, 0x0, 0xc000000000},
/* idx: 244 */ {0x0, 0x0, 0x0, 0x10000000000000, 0x0, 0x0, 0x0, 0x30000000000},
/* idx: 245 */ {0x0, 0x0, 0x0, 0x20000000000000, 0x0, 0x0, 0x0, 0xc0000000000},
/* idx: 246 */ {0x0, 0x0, 0x0, 0x40000000000000, 0x0, 0x0, 0x0, 0x300000000000},
/* idx: 247 */ {0x0, 0x0, 0x0, 0x80000000000000, 0x0, 0x0, 0x0, 0xc00000000000},
/* idx: 248 */ {0x0, 0x0, 0x0, 0x100000000000000, 0x0, 0x0, 0x0, 0x3000000000000},
/* idx: 249 */ {0x0, 0x0, 0x0, 0x200000000000000, 0x0, 0x0, 0x0, 0xc000000000000},
/* idx: 250 */ {0x0, 0x0, 0x0, 0x400000000000000, 0x0, 0x0, 0x0, 0x30000000000000},
/* idx: 251 */ {0x0, 0x0, 0x0, 0x800000000000000, 0x0, 0x0, 0x0, 0xc0000000000000},
/* idx: 252 */ {0x0, 0x0, 0x0, 0x1000000000000000, 0x0, 0x0, 0x0, 0x300000000000000},
/* idx: 253 */ {0x0, 0x0, 0x0, 0x2000000000000000, 0x0, 0x0, 0x0, 0xc00000000000000},
/* idx: 254 */ {0x0, 0x0, 0x0, 0x4000000000000000, 0x0, 0x0, 0x0, 0x3000000000000000},
/* idx: 255 */ {0x0, 0x0, 0x0, 0x8000000000000000, 0x0, 0x0, 0x0, 0xc000000000000000},
//
// START of HOST ROUTES, pfxLen == 8
// [:4] are all 0 and only one bit is set in [4:]
//
/* idx: 256 */ {0x0, 0x0, 0x0, 0x0, 0x1},
/* idx: 257 */ {0x0, 0x0, 0x0, 0x0, 0x2},
/* idx: 258 */ {0x0, 0x0, 0x0, 0x0, 0x4},
/* idx: 259 */ {0x0, 0x0, 0x0, 0x0, 0x8},
/* idx: 260 */ {0x0, 0x0, 0x0, 0x0, 0x10},
/* idx: 261 */ {0x0, 0x0, 0x0, 0x0, 0x20},
/* idx: 262 */ {0x0, 0x0, 0x0, 0x0, 0x40},
/* idx: 263 */ {0x0, 0x0, 0x0, 0x0, 0x80},
/* idx: 264 */ {0x0, 0x0, 0x0, 0x0, 0x100},
/* idx: 265 */ {0x0, 0x0, 0x0, 0x0, 0x200},
/* idx: 266 */ {0x0, 0x0, 0x0, 0x0, 0x400},
/* idx: 267 */ {0x0, 0x0, 0x0, 0x0, 0x800},
/* idx: 268 */ {0x0, 0x0, 0x0, 0x0, 0x1000},
/* idx: 269 */ {0x0, 0x0, 0x0, 0x0, 0x2000},
/* idx: 270 */ {0x0, 0x0, 0x0, 0x0, 0x4000},
/* idx: 271 */ {0x0, 0x0, 0x0, 0x0, 0x8000},
/* idx: 272 */ {0x0, 0x0, 0x0, 0x0, 0x10000},
/* idx: 273 */ {0x0, 0x0, 0x0, 0x0, 0x20000},
/* idx: 274 */ {0x0, 0x0, 0x0, 0x0, 0x40000},
/* idx: 275 */ {0x0, 0x0, 0x0, 0x0, 0x80000},
/* idx: 276 */ {0x0, 0x0, 0x0, 0x0, 0x100000},
/* idx: 277 */ {0x0, 0x0, 0x0, 0x0, 0x200000},
/* idx: 278 */ {0x0, 0x0, 0x0, 0x0, 0x400000},
/* idx: 279 */ {0x0, 0x0, 0x0, 0x0, 0x800000},
/* idx: 280 */ {0x0, 0x0, 0x0, 0x0, 0x1000000},
/* idx: 281 */ {0x0, 0x0, 0x0, 0x0, 0x2000000},
/* idx: 282 */ {0x0, 0x0, 0x0, 0x0, 0x4000000},
/* idx: 283 */ {0x0, 0x0, 0x0, 0x0, 0x8000000},
/* idx: 284 */ {0x0, 0x0, 0x0, 0x0, 0x10000000},
/* idx: 285 */ {0x0, 0x0, 0x0, 0x0, 0x20000000},
/* idx: 286 */ {0x0, 0x0, 0x0, 0x0, 0x40000000},
/* idx: 287 */ {0x0, 0x0, 0x0, 0x0, 0x80000000},
/* idx: 288 */ {0x0, 0x0, 0x0, 0x0, 0x100000000},
/* idx: 289 */ {0x0, 0x0, 0x0, 0x0, 0x200000000},
/* idx: 290 */ {0x0, 0x0, 0x0, 0x0, 0x400000000},
/* idx: 291 */ {0x0, 0x0, 0x0, 0x0, 0x800000000},
/* idx: 292 */ {0x0, 0x0, 0x0, 0x0, 0x1000000000},
/* idx: 293 */ {0x0, 0x0, 0x0, 0x0, 0x2000000000},
/* idx: 294 */ {0x0, 0x0, 0x0, 0x0, 0x4000000000},
/* idx: 295 */ {0x0, 0x0, 0x0, 0x0, 0x8000000000},
/* idx: 296 */ {0x0, 0x0, 0x0, 0x0, 0x10000000000},
/* idx: 297 */ {0x0, 0x0, 0x0, 0x0, 0x20000000000},
/* idx: 298 */ {0x0, 0x0, 0x0, 0x0, 0x40000000000},
/* idx: 299 */ {0x0, 0x0, 0x0, 0x0, 0x80000000000},
/* idx: 300 */ {0x0, 0x0, 0x0, 0x0, 0x100000000000},
/* idx: 301 */ {0x0, 0x0, 0x0, 0x0, 0x200000000000},
/* idx: 302 */ {0x0, 0x0, 0x0, 0x0, 0x400000000000},
/* idx: 303 */ {0x0, 0x0, 0x0, 0x0, 0x800000000000},
/* idx: 304 */ {0x0, 0x0, 0x0, 0x0, 0x1000000000000},
/* idx: 305 */ {0x0, 0x0, 0x0, 0x0, 0x2000000000000},
/* idx: 306 */ {0x0, 0x0, 0x0, 0x0, 0x4000000000000},
/* idx: 307 */ {0x0, 0x0, 0x0, 0x0, 0x8000000000000},
/* idx: 308 */ {0x0, 0x0, 0x0, 0x0, 0x10000000000000},
/* idx: 309 */ {0x0, 0x0, 0x0, 0x0, 0x20000000000000},
/* idx: 310 */ {0x0, 0x0, 0x0, 0x0, 0x40000000000000},
/* idx: 311 */ {0x0, 0x0, 0x0, 0x0, 0x80000000000000},
/* idx: 312 */ {0x0, 0x0, 0x0, 0x0, 0x100000000000000},
/* idx: 313 */ {0x0, 0x0, 0x0, 0x0, 0x200000000000000},
/* idx: 314 */ {0x0, 0x0, 0x0, 0x0, 0x400000000000000},
/* idx: 315 */ {0x0, 0x0, 0x0, 0x0, 0x800000000000000},
/* idx: 316 */ {0x0, 0x0, 0x0, 0x0, 0x1000000000000000},
/* idx: 317 */ {0x0, 0x0, 0x0, 0x0, 0x2000000000000000},
/* idx: 318 */ {0x0, 0x0, 0x0, 0x0, 0x4000000000000000},
/* idx: 319 */ {0x0, 0x0, 0x0, 0x0, 0x8000000000000000},
/* idx: 320 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
/* idx: 321 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x2},
/* idx: 322 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x4},
/* idx: 323 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x8},
/* idx: 324 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x10},
/* idx: 325 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x20},
/* idx: 326 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x40},
/* idx: 327 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x80},
/* idx: 328 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x100},
/* idx: 329 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x200},
/* idx: 330 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x400},
/* idx: 331 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x800},
/* idx: 332 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x1000},
/* idx: 333 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x2000},
/* idx: 334 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x4000},
/* idx: 335 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x8000},
/* idx: 336 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x10000},
/* idx: 337 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x20000},
/* idx: 338 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x40000},
/* idx: 339 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x80000},
/* idx: 340 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x100000},
/* idx: 341 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x200000},
/* idx: 342 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x400000},
/* idx: 343 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x800000},
/* idx: 344 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x1000000},
/* idx: 345 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x2000000},
/* idx: 346 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x4000000},
/* idx: 347 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x8000000},
/* idx: 348 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x10000000},
/* idx: 349 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x20000000},
/* idx: 350 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x40000000},
/* idx: 351 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x80000000},
/* idx: 352 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x100000000},
/* idx: 353 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x200000000},
/* idx: 354 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x400000000},
/* idx: 355 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x800000000},
/* idx: 356 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x1000000000},
/* idx: 357 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x2000000000},
/* idx: 358 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x4000000000},
/* idx: 359 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x8000000000},
/* idx: 360 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x10000000000},
/* idx: 361 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x20000000000},
/* idx: 362 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x40000000000},
/* idx: 363 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x80000000000},
/* idx: 364 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x100000000000},
/* idx: 365 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x200000000000},
/* idx: 366 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x400000000000},
/* idx: 367 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x800000000000},
/* idx: 368 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x1000000000000},
/* idx: 369 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x2000000000000},
/* idx: 370 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x4000000000000},
/* idx: 371 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x8000000000000},
/* idx: 372 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x10000000000000},
/* idx: 373 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x20000000000000},
/* idx: 374 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x40000000000000},
/* idx: 375 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x80000000000000},
/* idx: 376 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x100000000000000},
/* idx: 377 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x200000000000000},
/* idx: 378 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x400000000000000},
/* idx: 379 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x800000000000000},
/* idx: 380 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x1000000000000000},
/* idx: 381 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x2000000000000000},
/* idx: 382 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x4000000000000000},
/* idx: 383 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x8000000000000000},
/* idx: 384 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
/* idx: 385 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2},
/* idx: 386 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4},
/* idx: 387 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8},
/* idx: 388 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10},
/* idx: 389 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x20},
/* idx: 390 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40},
/* idx: 391 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80},
/* idx: 392 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x100},
/* idx: 393 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x200},
/* idx: 394 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x400},
/* idx: 395 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x800},
/* idx: 396 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1000},
/* idx: 397 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2000},
/* idx: 398 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4000},
/* idx: 399 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8000},
/* idx: 400 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10000},
/* idx: 401 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x20000},
/* idx: 402 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40000},
/* idx: 403 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80000},
/* idx: 404 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x100000},
/* idx: 405 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x200000},
/* idx: 406 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x400000},
/* idx: 407 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x800000},
/* idx: 408 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1000000},
/* idx: 409 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2000000},
/* idx: 410 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4000000},
/* idx: 411 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8000000},
/* idx: 412 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10000000},
/* idx: 413 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x20000000},
/* idx: 414 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40000000},
/* idx: 415 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80000000},
/* idx: 416 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x100000000},
/* idx: 417 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x200000000},
/* idx: 418 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x400000000},
/* idx: 419 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x800000000},
/* idx: 420 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1000000000},
/* idx: 421 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2000000000},
/* idx: 422 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4000000000},
/* idx: 423 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8000000000},
/* idx: 424 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10000000000},
/* idx: 425 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x20000000000},
/* idx: 426 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40000000000},
/* idx: 427 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80000000000},
/* idx: 428 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x100000000000},
/* idx: 429 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x200000000000},
/* idx: 430 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x400000000000},
/* idx: 431 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x800000000000},
/* idx: 432 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1000000000000},
/* idx: 433 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2000000000000},
/* idx: 434 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4000000000000},
/* idx: 435 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8000000000000},
/* idx: 436 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10000000000000},
/* idx: 437 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x20000000000000},
/* idx: 438 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40000000000000},
/* idx: 439 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80000000000000},
/* idx: 440 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x100000000000000},
/* idx: 441 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x200000000000000},
/* idx: 442 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x400000000000000},
/* idx: 443 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x800000000000000},
/* idx: 444 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1000000000000000},
/* idx: 445 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2000000000000000},
/* idx: 446 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4000000000000000},
/* idx: 447 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8000000000000000},
/* idx: 448 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
/* idx: 449 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2},
/* idx: 450 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4},
/* idx: 451 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8},
/* idx: 452 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10},
/* idx: 453 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x20},
/* idx: 454 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40},
/* idx: 455 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80},
/* idx: 456 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x100},
/* idx: 457 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x200},
/* idx: 458 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x400},
/* idx: 459 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x800},
/* idx: 460 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1000},
/* idx: 461 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2000},
/* idx: 462 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4000},
/* idx: 463 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8000},
/* idx: 464 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10000},
/* idx: 465 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x20000},
/* idx: 466 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40000},
/* idx: 467 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80000},
/* idx: 468 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x100000},
/* idx: 469 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x200000},
/* idx: 470 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x400000},
/* idx: 471 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x800000},
/* idx: 472 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1000000},
/* idx: 473 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2000000},
/* idx: 474 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4000000},
/* idx: 475 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8000000},
/* idx: 476 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10000000},
/* idx: 477 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x20000000},
/* idx: 478 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40000000},
/* idx: 479 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80000000},
/* idx: 480 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x100000000},
/* idx: 481 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x200000000},
/* idx: 482 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x400000000},
/* idx: 483 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x800000000},
/* idx: 484 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1000000000},
/* idx: 485 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2000000000},
/* idx: 486 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4000000000},
/* idx: 487 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8000000000},
/* idx: 488 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10000000000},
/* idx: 489 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x20000000000},
/* idx: 490 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40000000000},
/* idx: 491 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80000000000},
/* idx: 492 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x100000000000},
/* idx: 493 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x200000000000},
/* idx: 494 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x400000000000},
/* idx: 495 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x800000000000},
/* idx: 496 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1000000000000},
/* idx: 497 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2000000000000},
/* idx: 498 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4000000000000},
/* idx: 499 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8000000000000},
/* idx: 500 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10000000000000},
/* idx: 501 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x20000000000000},
/* idx: 502 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40000000000000},
/* idx: 503 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80000000000000},
/* idx: 504 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x100000000000000},
/* idx: 505 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x200000000000000},
/* idx: 506 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x400000000000000},
/* idx: 507 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x800000000000000},
/* idx: 508 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1000000000000000},
/* idx: 509 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2000000000000000},
/* idx: 510 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4000000000000000},
/* idx: 511 */ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8000000000000000},
}
+395
View File
@@ -0,0 +1,395 @@
// Copyright (c) 2026 Karl Gaissmaier
// SPDX-License-Identifier: MIT
package bart
import (
"net/netip"
"sync"
"github.com/gaissmai/bart/internal/art"
"github.com/gaissmai/bart/internal/lpm"
"github.com/gaissmai/bart/internal/nodes"
)
// Table represents an IPv4 and IPv6 routing table with payload V.
//
// The zero value is ready to use.
//
// A Table must not be copied by value; always pass by pointer.
// Nil pointers as receivers or arguments are forbidden and will panic.
//
// The Table is safe for concurrent reads, but concurrent reads and writes
// must be externally synchronized. Mutation via Insert/Delete requires locks,
// or alternatively, use ...Persist methods which return a modified copy
// without altering the original table (copy-on-write).
//
// Performance note: Do not pass IPv4-in-IPv6 addresses (e.g., ::ffff:192.0.2.1)
// as input. The methods do not perform automatic unmapping to avoid unnecessary
// overhead for the common case where native addresses are used.
// Users should unmap IPv4-in-IPv6 addresses to their native IPv4 form
// (e.g., 192.0.2.1) before calling these methods.
type Table[V any] struct {
// used by -copylocks checker from `go vet`.
_ [0]sync.Mutex
// the root nodes, implemented as popcount compressed multibit tries
root4 nodes.BartNode[V]
root6 nodes.BartNode[V]
// the number of prefixes in the routing table
size4 int
size6 int
}
// rootNodeByVersion, root node getter for ip version.
func (t *Table[V]) rootNodeByVersion(is4 bool) *nodes.BartNode[V] {
if is4 {
return &t.root4
}
return &t.root6
}
// Insert adds or updates a prefix-value pair in the routing table.
// If the prefix already exists, its value is updated; otherwise a new entry is created.
// Invalid prefixes are silently ignored.
//
// The prefix is automatically canonicalized using pfx.Masked() to ensure
// consistent behavior regardless of host bits in the input.
func (t *Table[V]) Insert(pfx netip.Prefix, val V) {
t.insert(pfx, val)
}
// InsertPersist is similar to Insert but the receiver isn't modified.
//
// All nodes touched during insert are cloned and a new Table is returned.
// This is not a full [Table.Clone], all untouched nodes are still referenced
// from both Tables.
//
// If the payload type V contains pointers or needs deep copying,
// it must implement the Clone method to support correct cloning.
//
// Due to cloning overhead this is significantly slower than Insert,
// typically taking μsec instead of nsec.
func (t *Table[V]) InsertPersist(pfx netip.Prefix, val V) *Table[V] {
return t.insertPersist(pfx, val)
}
// Modify applies an insert, update, or delete operation for the value
// associated with the given prefix. The supplied callback decides the
// operation: it is called with the current value (or zero if not found)
// and a boolean indicating whether the prefix exists. The callback must
// return a new value and a delete flag: del == false inserts or updates,
// del == true deletes the entry if it exists (otherwise no-op).
//
// The callback is invoked at most once per call.
//
// The operation is determined by the callback function, which is called with:
//
// val: the current value (or zero value if not found)
// found: true if the prefix currently exists, false otherwise
//
// The callback returns:
//
// val: the new value to insert or update (ignored if del == true)
// del: true to delete the entry, false to insert or update
//
// Summary of callback semantics:
//
// | cb-input | cb-return | Ops |
// ------------------------------------- --------
// | (zero, false) | (_, true) | no-op |
// | (zero, false) | (newVal, false) | insert |
// | (oldVal, true) | (newVal, false) | update |
// | (oldVal, true) | (_, true) | delete |
// ------------------------------------- --------
func (t *Table[V]) Modify(pfx netip.Prefix, cb func(_ V, ok bool) (_ V, del bool)) {
if !pfx.IsValid() {
return
}
// canonicalize prefix
pfx = pfx.Masked()
is4 := pfx.Addr().Is4()
n := t.rootNodeByVersion(is4)
delta := n.Modify(pfx, cb)
t.sizeUpdate(is4, delta)
}
// Contains reports whether any stored prefix covers the given IP address.
// Returns false for invalid IP addresses.
//
// This performs longest-prefix matching and returns true if any prefix
// in the routing table contains the IP address, regardless of the associated value.
//
// It does not return the value nor the prefix of the matching item,
// but as a test against an allow-/deny-list it's often sufficient
// and even few nanoseconds faster than [Table.Lookup].
func (t *Table[V]) Contains(ip netip.Addr) bool {
// speed is top priority: no explicit test for ip.IsValid
// if ip is invalid, AsSlice() returns nil, Contains returns false.
is4 := ip.Is4()
n := t.rootNodeByVersion(is4)
for _, octet := range ip.AsSlice() {
// for contains, any lpm match is good enough, no backtracking needed
if n.PrefixCount() != 0 && n.Contains(art.OctetToIdx(octet)) {
return true
}
// stop traversing?
if !n.Children.Test(octet) {
return false
}
kid := n.MustGetChild(octet)
// kid is node or leaf or fringe at octet
switch kid := kid.(type) {
case *nodes.BartNode[V]:
n = kid // descend down to next trie level
case *nodes.FringeNode[V]:
// fringe is the default-route for all possible octets below
return true
case *nodes.LeafNode[V]:
return kid.Prefix.Contains(ip)
}
}
return false
}
// Lookup performs a longest prefix match (LPM) lookup for the given address.
// It finds the most specific (longest) prefix in the routing table that
// contains the given address and returns its associated value.
//
// This is the fundamental operation for IP routing decisions, finding the
// best matching route for a destination address.
//
// Returns the associated value and true if a matching prefix is found.
// Returns zero value and false if no prefix contains the address.
func (t *Table[V]) Lookup(ip netip.Addr) (val V, ok bool) {
if !ip.IsValid() {
return val, ok
}
is4 := ip.Is4()
octets := ip.AsSlice()
n := t.rootNodeByVersion(is4)
// stack of the traversed nodes for fast backtracking, if needed
stack := [nodes.MaxTreeDepth]*nodes.BartNode[V]{}
// run variable, used after for loop
var depth int
var octet byte
LOOP:
// find leaf node
for depth, octet = range octets {
depth &= nodes.DepthMask // BCE, Lookup must be fast
// push current node on stack for fast backtracking
stack[depth] = n
// go down in tight loop to last octet
if !n.Children.Test(octet) {
// no more nodes below octet
break LOOP
}
kid := n.MustGetChild(octet)
// kid is node or leaf or fringe at octet
switch kid := kid.(type) {
case *nodes.BartNode[V]:
n = kid
continue LOOP // descend down to next trie level
case *nodes.FringeNode[V]:
// fringe is the default-route for all possible nodes below
return kid.Value, true
case *nodes.LeafNode[V]:
if kid.Prefix.Contains(ip) {
return kid.Value, true
}
// reached a path compressed prefix, stop traversing
break LOOP
}
}
// start backtracking, unwind the stack, bounds check eliminated
for ; depth >= 0; depth-- {
depth &= nodes.DepthMask // BCE
n = stack[depth]
// longest prefix match, skip if node has no prefixes
if n.PrefixCount() != 0 {
idx := art.OctetToIdx(octets[depth])
// lookupIdx() manually inlined
if lpmIdx, ok2 := n.Prefixes.IntersectionTop(&lpm.LookupTbl[idx]); ok2 {
return n.MustGetPrefix(lpmIdx), ok2
}
}
}
return val, ok
}
// LookupPrefix performs a longest prefix match lookup for any address within
// the given prefix. It finds the most specific routing table entry that would
// match any address in the provided prefix range.
//
// This is functionally identical to LookupPrefixLPM but returns only the
// associated value, not the matching prefix itself.
//
// Returns the value and true if a matching prefix is found.
// Returns zero value and false if no match exists.
func (t *Table[V]) LookupPrefix(pfx netip.Prefix) (val V, ok bool) {
_, val, ok = t.lookupPrefixLPM(pfx, false)
return val, ok
}
// LookupPrefixLPM performs a longest prefix match lookup for any address within
// the given prefix. It finds the most specific routing table entry that would
// match any address in the provided prefix range.
//
// This is functionally identical to LookupPrefix but additionally returns the
// matching LPM prefix itself along with the value.
//
// This method is slower than LookupPrefix and should only be used if the
// matching lpm entry is also required for other reasons.
//
// Returns the matching prefix, its associated value, and true if found.
// Returns zero values and false if no match exists.
func (t *Table[V]) LookupPrefixLPM(pfx netip.Prefix) (lpmPfx netip.Prefix, val V, ok bool) {
return t.lookupPrefixLPM(pfx, true)
}
func (t *Table[V]) lookupPrefixLPM(pfx netip.Prefix, withLPM bool) (lpmPfx netip.Prefix, val V, ok bool) {
if !pfx.IsValid() {
return lpmPfx, val, ok
}
// canonicalize the prefix
pfx = pfx.Masked()
ip := pfx.Addr()
pfxLen := pfx.Bits()
is4 := ip.Is4()
octets := ip.AsSlice()
strideCount, modBits := nodes.DivMod8(pfxLen)
n := t.rootNodeByVersion(is4)
// record path to leaf node
stack := [nodes.MaxTreeDepth]*nodes.BartNode[V]{}
var depth int
var octet byte
LOOP:
// find the last node on the octets path in the trie,
for depth, octet = range octets {
depth &= nodes.DepthMask // BCE
// stepped one past the last stride of interest; back up to last and break
if depth > strideCount {
depth--
break
}
// push current node on stack
stack[depth] = n
// go down in tight loop to leaf node
if !n.Children.Test(octet) {
break LOOP
}
kid := n.MustGetChild(octet)
// kid is node or leaf or fringe at octet
switch kid := kid.(type) {
case *nodes.BartNode[V]:
n = kid
continue LOOP // descend down to next trie level
case *nodes.LeafNode[V]:
// reached a path compressed prefix, stop traversing
if kid.Prefix.Bits() > pfxLen || !kid.Prefix.Contains(ip) {
break LOOP
}
return kid.Prefix, kid.Value, true
case *nodes.FringeNode[V]:
// the bits of the fringe are defined by the depth
// maybe the LPM isn't needed, saves some cycles
fringeBits := (depth + 1) << 3
if fringeBits > pfxLen {
break LOOP
}
// the LPM isn't needed, saves some cycles
if !withLPM {
return netip.Prefix{}, kid.Value, true
}
// get the LPM prefix back from ip and depth
// it's a fringe, bits are always /8, /16, /24, ...
fringePfx, _ := ip.Prefix((depth + 1) << 3)
return fringePfx, kid.Value, true
}
}
// start backtracking, unwind the stack
for ; depth >= 0; depth-- {
depth &= nodes.DepthMask // BCE
n = stack[depth]
// longest prefix match, skip if node has no prefixes
if n.Prefixes.Len() == 0 {
continue
}
var idx uint8
octet = octets[depth]
// only the final stride may have a different prefix len
// all others are just host routes
if depth == strideCount {
idx = art.PfxToIdx(octet, modBits)
} else {
idx = art.OctetToIdx(octet)
}
// manually inlined: lookupIdx(idx)
var topIdx uint8
if topIdx, ok = n.Prefixes.IntersectionTop(&lpm.LookupTbl[idx]); ok {
val = n.MustGetPrefix(topIdx)
// called from LookupPrefix
if !withLPM {
return netip.Prefix{}, val, ok
}
// called from LookupPrefixLPM
// get the bits from depth and top idx
pfxBits := int(art.PfxBits(depth, topIdx))
// calculate the lpmPfx from incoming ip and new mask
// netip.Addr.Prefix canonicalizes. Invariant: art.PfxBits(depth, topIdx)
// yields a valid mask (v4: 0..32, v6: 0..128), so error is impossible.
lpmPfx, _ = ip.Prefix(pfxBits)
return lpmPfx, val, ok
}
}
return lpmPfx, val, ok
}
+731
View File
@@ -0,0 +1,731 @@
// Code generated from file "commonmethods_tmpl.go"; DO NOT EDIT.
// Copyright (c) 2026 Karl Gaissmaier
// SPDX-License-Identifier: MIT
package bart
import (
"bytes"
"encoding/json"
"fmt"
"io"
"iter"
"net/netip"
"slices"
"strings"
"github.com/gaissmai/bart/internal/nodes"
"github.com/gaissmai/bart/internal/value"
)
func (t *Table[V]) sizeUpdate(is4 bool, delta int) {
if is4 {
t.size4 += delta
return
}
t.size6 += delta
}
// insert adds or updates a prefix-value pair in the routing table.
// If the prefix already exists, its value is updated; otherwise a new entry is created.
// Invalid prefixes are silently ignored.
//
// The prefix is automatically canonicalized using pfx.Masked() to ensure
// consistent behavior regardless of host bits in the input.
func (t *Table[V]) insert(pfx netip.Prefix, val V) {
if !pfx.IsValid() {
return
}
// canonicalize prefix
pfx = pfx.Masked()
is4 := pfx.Addr().Is4()
n := t.rootNodeByVersion(is4)
if exists := n.Insert(pfx, val, 0); exists {
return
}
// true insert, update size
t.sizeUpdate(is4, 1)
}
// insertPersist is similar to insert but the receiver isn't modified.
//
// All nodes touched during insert are cloned and a new Table is returned.
// This is not a full [Table.Clone], all untouched nodes are still referenced
// from both Tables.
//
// If the payload type V contains pointers or needs deep copying,
// implement:
//
// func (v V) Clone() V
//
// The bart package detects this via structural typing and deep‑copies
// values during persistent ops.
//
// Due to cloning overhead this is significantly slower than insert,
// typically taking μsec instead of nsec.
func (t *Table[V]) insertPersist(pfx netip.Prefix, val V) *Table[V] {
if !pfx.IsValid() {
return t
}
// canonicalize prefix
pfx = pfx.Masked()
is4 := pfx.Addr().Is4()
// share size counters; root nodes cloned selectively.
pt := &Table[V]{
size4: t.size4,
size6: t.size6,
}
// Create a cloning function for deep copying values;
// returns nil if V does not provide a Clone() V method.
cloneFn := value.CloneFnFactory[V]()
// Clone root node corresponding to the IP version, for copy-on-write.
n := &pt.root4
if is4 {
pt.root4 = *t.root4.CloneFlat(cloneFn)
pt.root6 = t.root6
} else {
pt.root4 = t.root4
pt.root6 = *t.root6.CloneFlat(cloneFn)
n = &pt.root6
}
if !n.InsertPersist(cloneFn, pfx, val, 0) {
pt.sizeUpdate(is4, 1)
}
return pt
}
// Delete removes the exact prefix pfx from the table in-place.
//
// This is an exact-match operation (no LPM). If pfx exists, the entry is
// removed. If pfx does not exist or pfx is invalid, the table is left unchanged.
//
// The prefix is canonicalized (Masked) before lookup.
func (t *Table[V]) Delete(pfx netip.Prefix) {
if !pfx.IsValid() {
return
}
// canonicalize prefix
pfx = pfx.Masked()
is4 := pfx.Addr().Is4()
n := t.rootNodeByVersion(is4)
if exists := n.Delete(pfx); exists {
t.sizeUpdate(is4, -1)
}
}
// Get performs an exact-prefix lookup and returns whether the exact
// prefix exists. The prefix is canonicalized (Masked) before lookup.
//
// This is an exact-match operation (no LPM). The prefix must match exactly
// in both address and prefix length to be found. If pfx exists, the
// associated value (zero value for Lite) and found=true is returned.
// If pfx does not exist or pfx is invalid, the zero value for V and
// exists=false is returned.
//
// For longest-prefix-match (LPM) lookups, use Contains(ip), Lookup(ip),
// LookupPrefix(pfx) or LookupPrefixLPM(pfx) instead.
func (t *Table[V]) Get(pfx netip.Prefix) (val V, exists bool) {
if !pfx.IsValid() {
return val, exists
}
// canonicalize prefix
pfx = pfx.Masked()
is4 := pfx.Addr().Is4()
n := t.rootNodeByVersion(is4)
return n.Get(pfx)
}
// DeletePersist is similar to Delete but does not modify the receiver.
//
// It performs a copy-on-write delete operation, cloning all nodes touched during
// deletion and returning a new Table reflecting the change.
//
// If the prefix is invalid or doesn't exist, the original table is
// returned unchanged.
//
// If the payload type V contains pointers or requires deep copying,
// it must implement the Clone method for correct cloning.
//
// Due to cloning overhead this is significantly slower than Delete,
// typically taking μsec instead of nsec.
func (t *Table[V]) DeletePersist(pfx netip.Prefix) *Table[V] {
if !pfx.IsValid() {
return t
}
// canonicalize prefix
pfx = pfx.Masked()
is4 := pfx.Addr().Is4()
// Preflight check: avoid cloning if prefix doesn't exist
n := t.rootNodeByVersion(is4)
if _, found := n.Get(pfx); !found {
return t
}
// share size counters; root nodes cloned selectively.
pt := &Table[V]{
size4: t.size4,
size6: t.size6,
}
// Create a cloning function for deep copying values;
// returns nil if V does not provide a Clone() V method.
cloneFn := value.CloneFnFactory[V]()
// Clone root node corresponding to the IP version, for copy-on-write.
if is4 {
pt.root4 = *t.root4.CloneFlat(cloneFn)
pt.root6 = t.root6
n = &pt.root4
} else {
pt.root4 = t.root4
pt.root6 = *t.root6.CloneFlat(cloneFn)
n = &pt.root6
}
if exists := n.DeletePersist(cloneFn, pfx); exists {
pt.sizeUpdate(is4, -1)
}
return pt
}
// ModifyPersist is similar to Modify but the receiver isn't modified and
// a new *Table is returned.
func (t *Table[V]) ModifyPersist(pfx netip.Prefix, cb func(_ V, ok bool) (_ V, del bool)) *Table[V] {
if !pfx.IsValid() {
return t
}
// make a cheap test in front of expensive operation
oldVal, ok := t.Get(pfx)
val := oldVal
// to clone or not to clone ...
cloneFn := value.CloneFnFactory[V]()
if cloneFn != nil && ok {
val = cloneFn(oldVal)
}
newVal, del := cb(val, ok)
switch {
case !ok && del: // no-op
return t
case !ok && !del: // insert
return t.InsertPersist(pfx, newVal)
case ok && !del: // update
return t.InsertPersist(pfx, newVal)
case ok && del: // delete
return t.DeletePersist(pfx)
}
panic("unreachable")
}
// Supernets returns an iterator over all supernet routes that cover the given prefix pfx.
//
// The traversal searches both exact-length and shorter (less specific) prefixes that
// include pfx. Starting from the most specific position in the trie,
// it walks upward through parent nodes and yields any matching entries found at each level.
//
// The iteration order is reverse-CIDR: from longest prefix match (LPM) towards
// least-specific routes.
//
// This can be used to enumerate all covering supernet routes in routing-based
// policy engines, diagnostics tools, or fallback resolution logic.
//
// Example:
//
// for supernet, val := range table.Supernets(netip.MustParsePrefix("192.0.2.128/25")) {
// fmt.Println("Covered by:", supernet, "->", val)
// }
//
// The iteration can be stopped early by breaking from the range loop.
// Returns an empty iterator if the prefix is invalid.
func (t *Table[V]) Supernets(pfx netip.Prefix) iter.Seq2[netip.Prefix, V] {
return func(yield func(netip.Prefix, V) bool) {
if !pfx.IsValid() {
return
}
// canonicalize the prefix
pfx = pfx.Masked()
is4 := pfx.Addr().Is4()
n := t.rootNodeByVersion(is4)
n.Supernets(pfx, yield)
}
}
// Subnets returns an iterator over all subnets of the given prefix
// in natural CIDR sort order. This includes prefixes of the same length
// (exact match) and longer (more specific) prefixes that are contained
// within the given prefix.
//
// Example:
//
// for sub, val := range table.Subnets(netip.MustParsePrefix("10.0.0.0/8")) {
// fmt.Println("Covered:", sub, "->", val)
// }
//
// The iteration can be stopped early by breaking from the range loop.
// Returns an empty iterator if the prefix is invalid.
func (t *Table[V]) Subnets(pfx netip.Prefix) iter.Seq2[netip.Prefix, V] {
return func(yield func(netip.Prefix, V) bool) {
if !pfx.IsValid() {
return
}
pfx = pfx.Masked()
is4 := pfx.Addr().Is4()
n := t.rootNodeByVersion(is4)
n.Subnets(pfx, yield)
}
}
// OverlapsPrefix reports whether any prefix in the routing table overlaps with
// the given prefix. Two prefixes overlap if they share any IP addresses.
//
// The check is bidirectional: it returns true if the input prefix is covered by an existing
// route, or if any stored route is itself contained within the input prefix.
//
// Internally, the function normalizes the prefix and descends the relevant trie branch,
// using stride-based logic to identify overlap without performing a full lookup.
//
// This is useful for containment tests, route validation, or policy checks using prefix
// semantics without retrieving exact matches.
func (t *Table[V]) OverlapsPrefix(pfx netip.Prefix) bool {
if !pfx.IsValid() {
return false
}
// canonicalize the prefix
pfx = pfx.Masked()
is4 := pfx.Addr().Is4()
n := t.rootNodeByVersion(is4)
return n.OverlapsPrefixAtDepth(pfx, 0)
}
// Overlaps reports whether any route in the receiver table overlaps
// with a route in the other table, in either direction.
//
// The overlap check is bidirectional: it returns true if any IP prefix
// in the receiver is covered by the other table, or vice versa.
// This includes partial overlaps, exact matches, and supernet/subnet relationships.
//
// Both IPv4 and IPv6 route trees are compared independently. If either
// tree has overlapping routes, the function returns true.
//
// This is useful for conflict detection, policy enforcement,
// or validating mutually exclusive routing domains.
func (t *Table[V]) Overlaps(o *Table[V]) bool {
return t.Overlaps4(o) || t.Overlaps6(o)
}
// Overlaps4 is like [Table.Overlaps] but for the v4 routing table only.
func (t *Table[V]) Overlaps4(o *Table[V]) bool {
if t.size4 == 0 || o.size4 == 0 {
return false
}
return t.root4.Overlaps(&o.root4, 0)
}
// Overlaps6 is like [Table.Overlaps] but for the v6 routing table only.
func (t *Table[V]) Overlaps6(o *Table[V]) bool {
if t.size6 == 0 || o.size6 == 0 {
return false
}
return t.root6.Overlaps(&o.root6, 0)
}
// Union merges another routing table into the receiver table, modifying it in-place.
//
// All prefixes and values from the other table (o) are inserted into the receiver.
// If a duplicate prefix exists in both tables, the value from o replaces the existing entry.
// This duplicate is shallow-copied by default, but if the value type V implements the
// Clone method, the value is deeply cloned before insertion. See also Table.Clone.
func (t *Table[V]) Union(o *Table[V]) {
// panics on nil receiver
_ = t.size4
// panics on nil argument
if o.size4 == 0 && o.size6 == 0 {
return
}
// t is unchanged
if o == t {
return
}
// Create a cloning function for deep copying values;
// returns nil if V does not provide a Clone() V method.
cloneFn := value.CloneFnFactory[V]()
dup4 := t.root4.UnionRec(cloneFn, &o.root4, 0)
dup6 := t.root6.UnionRec(cloneFn, &o.root6, 0)
t.size4 += o.size4 - dup4
t.size6 += o.size6 - dup6
}
// UnionPersist is similar to [Union] but the receiver isn't modified.
//
// All nodes touched during union are cloned and a new *Table is returned.
// If o is empty, no nodes are touched and the receiver may be
// returned unchanged.
func (t *Table[V]) UnionPersist(o *Table[V]) *Table[V] {
// panics on nil receiver
_ = t.size4
// panics on nil argument
if o.size4 == 0 && o.size6 == 0 {
return t
}
if o == t {
return t
}
// Create a cloning function for deep copying values;
// returns nil if V does not provide a Clone() V method.
cloneFn := value.CloneFnFactory[V]()
// new Table with root nodes just copied.
pt := &Table[V]{
root4: t.root4,
root6: t.root6,
//
size4: t.size4,
size6: t.size6,
}
// only clone the root node if there is something to union
if o.size4 != 0 {
pt.root4 = *t.root4.CloneFlat(cloneFn)
}
if o.size6 != 0 {
pt.root6 = *t.root6.CloneFlat(cloneFn)
}
dup4 := pt.root4.UnionRecPersist(cloneFn, &o.root4, 0)
dup6 := pt.root6.UnionRecPersist(cloneFn, &o.root6, 0)
pt.size4 += o.size4 - dup4
pt.size6 += o.size6 - dup6
return pt
}
// Equal checks whether two tables are structurally and semantically equal.
// It ensures both trees (IPv4-based and IPv6-based) have the same sizes and
// recursively compares their root nodes.
//
// If V implements an `Equal(V) bool` method, its custom equality logic is used.
// Otherwise, values are compared directly using the == operator.
//
// Note: If V implements `Equal(V) bool` with a pointer receiver, the Equal
// method should handle nil receivers gracefully.
//
// ATTENTION: If V is not comparable at runtime (such as a slice or map without an `Equal`
// method), a runtime panic will occur.
func (t *Table[V]) Equal(o *Table[V]) bool {
if t.size4 != o.size4 || t.size6 != o.size6 {
return false
}
if o == t {
return true
}
return t.root4.EqualRec(&o.root4) && t.root6.EqualRec(&o.root6)
}
// Clone returns a copy of the routing table.
// The payload of type V is shallow copied by default. To enable deep copying,
// implement the following method on your value type:
//
// Clone() V
//
// Example:
//
// type MyValue struct { Data []byte }
// func (v MyValue) Clone() MyValue {
// return MyValue{Data: slices.Clone(v.Data)}
// }
//
// The bart package will automatically detect and use this method via Go's
// structural typing.
//
// Note: If V implements Clone() V with a pointer receiver, the Clone
// method should handle nil receivers gracefully.
func (t *Table[V]) Clone() *Table[V] {
c := new(Table[V])
cloneFn := value.CloneFnFactory[V]()
c.root4 = *t.root4.CloneRec(cloneFn)
c.root6 = *t.root6.CloneRec(cloneFn)
c.size4 = t.size4
c.size6 = t.size6
return c
}
// Size returns the prefix count.
func (t *Table[V]) Size() int {
return t.size4 + t.size6
}
// Size4 returns the IPv4 prefix count.
func (t *Table[V]) Size4() int {
return t.size4
}
// Size6 returns the IPv6 prefix count.
func (t *Table[V]) Size6() int {
return t.size6
}
// All returns an iterator over all prefix–value pairs in the table.
//
// The iteration order is unspecified and may vary between calls; for a stable order,
// use [Table.AllSorted].
//
// IMPORTANT: Modifying the table during iteration is not allowed,
// as this would interfere with the internal traversal and may corrupt or
// prematurely terminate the iteration.
func (t *Table[V]) All() iter.Seq2[netip.Prefix, V] {
return func(yield func(netip.Prefix, V) bool) {
_ = t.root4.AllRec(stridePath{}, 0, true, yield) && t.root6.AllRec(stridePath{}, 0, false, yield)
}
}
// All4 is like [Table.All] but only for the v4 routing table.
func (t *Table[V]) All4() iter.Seq2[netip.Prefix, V] {
return func(yield func(netip.Prefix, V) bool) {
_ = t.root4.AllRec(stridePath{}, 0, true, yield)
}
}
// All6 is like [Table.All] but only for the v6 routing table.
func (t *Table[V]) All6() iter.Seq2[netip.Prefix, V] {
return func(yield func(netip.Prefix, V) bool) {
_ = t.root6.AllRec(stridePath{}, 0, false, yield)
}
}
// AllSorted is like [Table.All] but the iteration is ordered in canonical
// CIDR prefix sort order.
func (t *Table[V]) AllSorted() iter.Seq2[netip.Prefix, V] {
return func(yield func(netip.Prefix, V) bool) {
_ = t.root4.AllRecSorted(stridePath{}, 0, true, yield) &&
t.root6.AllRecSorted(stridePath{}, 0, false, yield)
}
}
// AllSorted4 is like [Table.AllSorted] but only for the v4 routing table.
func (t *Table[V]) AllSorted4() iter.Seq2[netip.Prefix, V] {
return func(yield func(netip.Prefix, V) bool) {
_ = t.root4.AllRecSorted(stridePath{}, 0, true, yield)
}
}
// AllSorted6 is like [Table.AllSorted] but only for the v6 routing table.
func (t *Table[V]) AllSorted6() iter.Seq2[netip.Prefix, V] {
return func(yield func(netip.Prefix, V) bool) {
_ = t.root6.AllRecSorted(stridePath{}, 0, false, yield)
}
}
// Fprint writes a hierarchical tree diagram of the ordered CIDRs
// with default formatted payload V to w.
//
// The order from top to bottom is in ascending order of the prefix address
// and the subtree structure is determined by the CIDRs coverage.
//
// ▼
// ├─ 10.0.0.0/8 (V)
// │ ├─ 10.0.0.0/24 (V)
// │ └─ 10.0.1.0/24 (V)
// ├─ 127.0.0.0/8 (V)
// │ └─ 127.0.0.1/32 (V)
// ├─ 169.254.0.0/16 (V)
// ├─ 172.16.0.0/12 (V)
// └─ 192.168.0.0/16 (V)
// └─ 192.168.1.0/24 (V)
// ▼
// └─ ::/0 (V)
// ├─ ::1/128 (V)
// ├─ 2000::/3 (V)
// │ └─ 2001:db8::/32 (V)
// └─ fe80::/10 (V)
func (t *Table[V]) Fprint(w io.Writer) error {
if w == nil && t != nil {
return fmt.Errorf("nil writer")
}
// v4
if err := t.fprint(w, true); err != nil {
return err
}
// v6
if err := t.fprint(w, false); err != nil {
return err
}
return nil
}
// fprint is the version dependent adapter to fprintRec.
func (t *Table[V]) fprint(w io.Writer, is4 bool) error {
n := t.rootNodeByVersion(is4)
if n.IsEmpty() {
return nil
}
if _, err := fmt.Fprint(w, "▼\n"); err != nil {
return err
}
startParent := nodes.TrieItem[V]{
Node: nil,
Idx: 0,
Path: stridePath{},
Is4: is4,
}
return n.FprintRec(w, startParent, "")
}
// MarshalText implements the [encoding.TextMarshaler] interface,
// just a wrapper for [Table.Fprint].
func (t *Table[V]) MarshalText() ([]byte, error) {
w := new(bytes.Buffer)
if err := t.Fprint(w); err != nil {
return nil, err
}
return w.Bytes(), nil
}
// MarshalJSON dumps the table into two sorted lists: for ipv4 and ipv6.
// Every root and subnet is an array, not a map, because the order matters.
func (t *Table[V]) MarshalJSON() ([]byte, error) {
result := struct {
Ipv4 []DumpListNode[V] `json:"ipv4,omitempty"`
Ipv6 []DumpListNode[V] `json:"ipv6,omitempty"`
}{
Ipv4: t.DumpList4(),
Ipv6: t.DumpList6(),
}
buf, err := json.Marshal(result)
if err != nil {
return nil, err
}
return buf, nil
}
// DumpList4 dumps the ipv4 tree into a list of roots and their subnets.
// It can be used to analyze the tree or build the text or JSON serialization.
func (t *Table[V]) DumpList4() []DumpListNode[V] {
return t.dumpListRec(&t.root4, 0, stridePath{}, 0, true)
}
// DumpList6 dumps the ipv6 tree into a list of roots and their subnets.
// It can be used to analyze the tree or build custom JSON representation.
func (t *Table[V]) DumpList6() []DumpListNode[V] {
return t.dumpListRec(&t.root6, 0, stridePath{}, 0, false)
}
// dumpListRec, build the data structure rec-descent with the help of directItemsRec.
// anyNode is nodes.BartNode, nodes.FastNode or nodes.LiteNode
func (t *Table[V]) dumpListRec(anyNode any, parentIdx uint8, path stridePath, depth int, is4 bool) []DumpListNode[V] {
// recursion stop condition
if anyNode == nil {
return nil
}
// the same method is generated for all table types, therefore
// type assert to the smallest needed interface.
// The panic on wrong type assertion is by intention, MUST NOT happen
n := anyNode.(interface {
DirectItemsRec(uint8, stridePath, int, bool) []nodes.TrieItem[V]
})
directItems := n.DirectItemsRec(parentIdx, path, depth, is4)
// sort the items by prefix
slices.SortFunc(directItems, func(a, b nodes.TrieItem[V]) int {
return nodes.CmpPrefix(a.Cidr, b.Cidr)
})
dumpNodes := make([]DumpListNode[V], 0, len(directItems))
for _, item := range directItems {
dumpNodes = append(dumpNodes, DumpListNode[V]{
CIDR: item.Cidr,
Value: item.Val,
// build it rec-descent, item.Node is also from type any
Subnets: t.dumpListRec(item.Node, item.Idx, item.Path, item.Depth, is4),
})
}
return dumpNodes
}
// dumpString is just a wrapper for dump.
func (t *Table[V]) dumpString() string {
w := new(strings.Builder)
t.dump(w)
return w.String()
}
// dump the table structure and all the nodes to w.
func (t *Table[V]) dump(w io.Writer) {
if t.size4 > 0 {
stats := t.root4.StatsRec()
fmt.Fprintln(w)
fmt.Fprintf(w, "### IPv4: size(%d), subnodes(%d), prefixes(%d), leaves(%d), fringes(%d)",
t.size4, stats.SubNodes, stats.Prefixes, stats.Leaves, stats.Fringes)
t.root4.DumpRec(w, stridePath{}, 0, true)
}
if t.size6 > 0 {
stats := t.root6.StatsRec()
fmt.Fprintln(w)
fmt.Fprintf(w, "### IPv6: size(%d), subnodes(%d), prefixes(%d), leaves(%d), fringes(%d)",
t.size6, stats.SubNodes, stats.Prefixes, stats.Leaves, stats.Fringes)
t.root6.DumpRec(w, stridePath{}, 0, false)
}
}
-595
View File
@@ -1,595 +0,0 @@
// Copyright (c) 2024 Karl Gaissmaier
// SPDX-License-Identifier: MIT
// Please read the ART paper ./doc/artlookup.pdf
// to understand the baseIndex algorithm.
package bart
// netmask for bits
//
// 0b0000_0000, // bits == 0
// 0b1000_0000, // bits == 1
// 0b1100_0000, // bits == 2
// 0b1110_0000, // bits == 3
// 0b1111_0000, // bits == 4
// 0b1111_1000, // bits == 5
// 0b1111_1100, // bits == 6
// 0b1111_1110, // bits == 7
// 0b1111_1111, // bits == 8
func netMask(bits int) uint8 {
return 0b1111_1111 << (8 - bits)
}
// baseIndex of the first host route 0/8: pfxToIdx(0,8)
const firstHostIdx = 256
// pfxToIdx, maps a prefix table as a 'complete binary tree'.
func pfxToIdx(octet byte, prefixLen int) uint {
return uint(octet>>(strideLen-prefixLen)) + (1 << prefixLen)
}
// hostIndex, just pfxToIdx(octet, 8) but faster.
func hostIndex(octet uint) uint {
return octet + firstHostIdx
}
// cmpIndexRank, sort indexes in prefix sort order.
func cmpIndexRank(aIdx, bIdx uint) int {
// convert idx to prefix
aOctet, aBits := idxToPfx(aIdx)
bOctet, bBits := idxToPfx(bIdx)
// cmp the prefixes, first by address and then by bits
if aOctet == bOctet {
if aBits <= bBits {
return -1
}
return 1
}
if aOctet < bOctet {
return -1
}
return 1
}
// idxToPfx returns the octet and prefix len of baseIdx.
// It's the inverse to pfxToIdx.
func idxToPfx(idx uint) (octet byte, pfxLen int) {
return baseIdxLookupTbl[idx].octet, int(baseIdxLookupTbl[idx].pfxLen)
}
// baseIdxLookupTbl, maps back from idx => octet/bits
//
// Use the pre computed lookup table, the func baseIndexToPrefix is too slow
// in comparison to an array access.
//
// func baseIndexToPrefix(idx uint) (octet byte, pfxLen int) {
// nlz := bits.LeadingZeros(idx)
// pfxLen = strconv.IntSize - nlz - 1
// octet = byte((idx & (0xFF >> (8 - pfxLen))) << (8 - pfxLen))
// return octet, pfxLen
// }
var baseIdxLookupTbl = [512]struct {
octet byte
pfxLen int8
}{
{0, -1}, // idx == 0, invalid
{0, 0}, // idx == 1
{0, 1}, // idx == 2
{128, 1}, // idx == 3, e.g. 128.0.0.0/1, 8000::/1
{0, 2}, // idx == 4
{64, 2}, // idx == 5
{128, 2}, // idx == 6
{192, 2}, // idx == 7
{0, 3}, // idx == 8
{32, 3}, // idx == 9
{64, 3}, // idx == 10
{96, 3}, // idx == 11
{128, 3}, // idx == 12
{160, 3}, // idx == 13
{192, 3}, // idx == 14
{224, 3}, // idx == 15
{0, 4}, // idx == 16
{16, 4}, // idx == 17
{32, 4}, // idx == 18
{48, 4}, // idx == 19
{64, 4}, // idx == 20
{80, 4}, // idx == 21
{96, 4}, // idx == 22
{112, 4}, // idx == 23
{128, 4}, // idx == 24
{144, 4}, // idx == 25
{160, 4}, // idx == 26
{176, 4}, // idx == 27
{192, 4}, // idx == 28
{208, 4}, // idx == 29
{224, 4}, // idx == 30
{240, 4}, // idx == 31
{0, 5}, // idx == 32
{8, 5}, // idx == 33
{16, 5}, // idx == 34
{24, 5}, // idx == 35
{32, 5}, // idx == 36
{40, 5}, // idx == 37
{48, 5}, // idx == 38
{56, 5}, // idx == 39
{64, 5}, // idx == 40
{72, 5}, // idx == 41
{80, 5}, // idx == 42
{88, 5}, // idx == 43
{96, 5}, // idx == 44
{104, 5}, // idx == 45
{112, 5}, // idx == 46
{120, 5}, // idx == 47
{128, 5}, // idx == 48
{136, 5}, // idx == 49
{144, 5}, // idx == 50
{152, 5}, // idx == 51
{160, 5}, // idx == 52
{168, 5}, // idx == 53
{176, 5}, // idx == 54
{184, 5}, // idx == 55
{192, 5}, // idx == 56
{200, 5}, // idx == 57
{208, 5}, // idx == 58
{216, 5}, // idx == 59
{224, 5}, // idx == 60
{232, 5}, // idx == 61
{240, 5}, // idx == 62
{248, 5}, // idx == 63
{0, 6}, // idx == 64
{4, 6}, // idx == 65
{8, 6}, // idx == 66
{12, 6}, // idx == 67
{16, 6}, // idx == 68
{20, 6}, // idx == 69
{24, 6}, // idx == 70
{28, 6}, // idx == 71
{32, 6}, // idx == 72
{36, 6}, // idx == 73
{40, 6}, // idx == 74
{44, 6}, // idx == 75
{48, 6}, // idx == 76
{52, 6}, // idx == 77
{56, 6}, // idx == 78
{60, 6}, // idx == 79
{64, 6}, // idx == 80
{68, 6}, // idx == 81
{72, 6}, // idx == 82
{76, 6}, // idx == 83
{80, 6}, // idx == 84
{84, 6}, // idx == 85
{88, 6}, // idx == 86
{92, 6}, // idx == 87
{96, 6}, // idx == 88
{100, 6}, // idx == 89
{104, 6}, // idx == 90
{108, 6}, // idx == 91
{112, 6}, // idx == 92
{116, 6}, // idx == 93
{120, 6}, // idx == 94
{124, 6}, // idx == 95
{128, 6}, // idx == 96
{132, 6}, // idx == 97
{136, 6}, // idx == 98
{140, 6}, // idx == 99
{144, 6}, // idx == 100
{148, 6}, // idx == 101
{152, 6}, // idx == 102
{156, 6}, // idx == 103
{160, 6}, // idx == 104
{164, 6}, // idx == 105
{168, 6}, // idx == 106
{172, 6}, // idx == 107
{176, 6}, // idx == 108
{180, 6}, // idx == 109
{184, 6}, // idx == 110
{188, 6}, // idx == 111
{192, 6}, // idx == 112
{196, 6}, // idx == 113
{200, 6}, // idx == 114
{204, 6}, // idx == 115
{208, 6}, // idx == 116
{212, 6}, // idx == 117
{216, 6}, // idx == 118
{220, 6}, // idx == 119
{224, 6}, // idx == 120
{228, 6}, // idx == 121
{232, 6}, // idx == 122
{236, 6}, // idx == 123
{240, 6}, // idx == 124
{244, 6}, // idx == 125
{248, 6}, // idx == 126
{252, 6}, // idx == 127
{0, 7}, // idx == 128
{2, 7}, // idx == 129
{4, 7}, // idx == 130
{6, 7}, // idx == 131
{8, 7}, // idx == 132
{10, 7}, // idx == 133
{12, 7}, // idx == 134
{14, 7}, // idx == 135
{16, 7}, // idx == 136
{18, 7}, // idx == 137
{20, 7}, // idx == 138
{22, 7}, // idx == 139
{24, 7}, // idx == 140
{26, 7}, // idx == 141
{28, 7}, // idx == 142
{30, 7}, // idx == 143
{32, 7}, // idx == 144
{34, 7}, // idx == 145
{36, 7}, // idx == 146
{38, 7}, // idx == 147
{40, 7}, // idx == 148
{42, 7}, // idx == 149
{44, 7}, // idx == 150
{46, 7}, // idx == 151
{48, 7}, // idx == 152
{50, 7}, // idx == 153
{52, 7}, // idx == 154
{54, 7}, // idx == 155
{56, 7}, // idx == 156
{58, 7}, // idx == 157
{60, 7}, // idx == 158
{62, 7}, // idx == 159
{64, 7}, // idx == 160
{66, 7}, // idx == 161
{68, 7}, // idx == 162
{70, 7}, // idx == 163
{72, 7}, // idx == 164
{74, 7}, // idx == 165
{76, 7}, // idx == 166
{78, 7}, // idx == 167
{80, 7}, // idx == 168
{82, 7}, // idx == 169
{84, 7}, // idx == 170
{86, 7}, // idx == 171
{88, 7}, // idx == 172
{90, 7}, // idx == 173
{92, 7}, // idx == 174
{94, 7}, // idx == 175
{96, 7}, // idx == 176
{98, 7}, // idx == 177
{100, 7}, // idx == 178
{102, 7}, // idx == 179
{104, 7}, // idx == 180
{106, 7}, // idx == 181
{108, 7}, // idx == 182
{110, 7}, // idx == 183
{112, 7}, // idx == 184
{114, 7}, // idx == 185
{116, 7}, // idx == 186
{118, 7}, // idx == 187
{120, 7}, // idx == 188
{122, 7}, // idx == 189
{124, 7}, // idx == 190
{126, 7}, // idx == 191
{128, 7}, // idx == 192
{130, 7}, // idx == 193
{132, 7}, // idx == 194
{134, 7}, // idx == 195
{136, 7}, // idx == 196
{138, 7}, // idx == 197
{140, 7}, // idx == 198
{142, 7}, // idx == 199
{144, 7}, // idx == 200
{146, 7}, // idx == 201
{148, 7}, // idx == 202
{150, 7}, // idx == 203
{152, 7}, // idx == 204
{154, 7}, // idx == 205
{156, 7}, // idx == 206
{158, 7}, // idx == 207
{160, 7}, // idx == 208
{162, 7}, // idx == 209
{164, 7}, // idx == 210
{166, 7}, // idx == 211
{168, 7}, // idx == 212
{170, 7}, // idx == 213
{172, 7}, // idx == 214
{174, 7}, // idx == 215
{176, 7}, // idx == 216
{178, 7}, // idx == 217
{180, 7}, // idx == 218
{182, 7}, // idx == 219
{184, 7}, // idx == 220
{186, 7}, // idx == 221
{188, 7}, // idx == 222
{190, 7}, // idx == 223
{192, 7}, // idx == 224
{194, 7}, // idx == 225
{196, 7}, // idx == 226
{198, 7}, // idx == 227
{200, 7}, // idx == 228
{202, 7}, // idx == 229
{204, 7}, // idx == 230
{206, 7}, // idx == 231
{208, 7}, // idx == 232
{210, 7}, // idx == 233
{212, 7}, // idx == 234
{214, 7}, // idx == 235
{216, 7}, // idx == 236
{218, 7}, // idx == 237
{220, 7}, // idx == 238
{222, 7}, // idx == 239
{224, 7}, // idx == 240
{226, 7}, // idx == 241
{228, 7}, // idx == 242
{230, 7}, // idx == 243
{232, 7}, // idx == 244
{234, 7}, // idx == 245
{236, 7}, // idx == 246
{238, 7}, // idx == 247
{240, 7}, // idx == 248
{242, 7}, // idx == 249
{244, 7}, // idx == 250
{246, 7}, // idx == 251
{248, 7}, // idx == 252
{250, 7}, // idx == 253
{252, 7}, // idx == 254
{254, 7}, // idx == 255
//
// host routes, pfxLen == 8
//
{0, 8}, // idx == 256, e.g. 0.0.0.0/8, ::/8
{1, 8}, // idx == 257
{2, 8}, // idx == 258
{3, 8}, // idx == 259
{4, 8}, // idx == 260
{5, 8}, // idx == 261
{6, 8}, // idx == 262
{7, 8}, // idx == 263
{8, 8}, // idx == 264
{9, 8}, // idx == 265
{10, 8}, // idx == 266, 10.0.0.0/8, 0a00::/8
{11, 8}, // idx == 267
{12, 8}, // idx == 268
{13, 8}, // idx == 269
{14, 8}, // idx == 270
{15, 8}, // idx == 271
{16, 8}, // idx == 272
{17, 8}, // idx == 273
{18, 8}, // idx == 274
{19, 8}, // idx == 275
{20, 8}, // idx == 276
{21, 8}, // idx == 277
{22, 8}, // idx == 278
{23, 8}, // idx == 279
{24, 8}, // idx == 280
{25, 8}, // idx == 281
{26, 8}, // idx == 282
{27, 8}, // idx == 283
{28, 8}, // idx == 284
{29, 8}, // idx == 285
{30, 8}, // idx == 286
{31, 8}, // idx == 287
{32, 8}, // idx == 288
{33, 8}, // idx == 289
{34, 8}, // idx == 290
{35, 8}, // idx == 291
{36, 8}, // idx == 292
{37, 8}, // idx == 293
{38, 8}, // idx == 294
{39, 8}, // idx == 295
{40, 8}, // idx == 296
{41, 8}, // idx == 297
{42, 8}, // idx == 298
{43, 8}, // idx == 299
{44, 8}, // idx == 300
{45, 8}, // idx == 301
{46, 8}, // idx == 302
{47, 8}, // idx == 303
{48, 8}, // idx == 304
{49, 8}, // idx == 305
{50, 8}, // idx == 306
{51, 8}, // idx == 307
{52, 8}, // idx == 308
{53, 8}, // idx == 309
{54, 8}, // idx == 310
{55, 8}, // idx == 311
{56, 8}, // idx == 312
{57, 8}, // idx == 313
{58, 8}, // idx == 314
{59, 8}, // idx == 315
{60, 8}, // idx == 316
{61, 8}, // idx == 317
{62, 8}, // idx == 318
{63, 8}, // idx == 319
{64, 8}, // idx == 320
{65, 8}, // idx == 321
{66, 8}, // idx == 322
{67, 8}, // idx == 323
{68, 8}, // idx == 324
{69, 8}, // idx == 325
{70, 8}, // idx == 326
{71, 8}, // idx == 327
{72, 8}, // idx == 328
{73, 8}, // idx == 329
{74, 8}, // idx == 330
{75, 8}, // idx == 331
{76, 8}, // idx == 332
{77, 8}, // idx == 333
{78, 8}, // idx == 334
{79, 8}, // idx == 335
{80, 8}, // idx == 336
{81, 8}, // idx == 337
{82, 8}, // idx == 338
{83, 8}, // idx == 339
{84, 8}, // idx == 340
{85, 8}, // idx == 341
{86, 8}, // idx == 342
{87, 8}, // idx == 343
{88, 8}, // idx == 344
{89, 8}, // idx == 345
{90, 8}, // idx == 346
{91, 8}, // idx == 347
{92, 8}, // idx == 348
{93, 8}, // idx == 349
{94, 8}, // idx == 350
{95, 8}, // idx == 351
{96, 8}, // idx == 352
{97, 8}, // idx == 353
{98, 8}, // idx == 354
{99, 8}, // idx == 355
{100, 8}, // idx == 356
{101, 8}, // idx == 357
{102, 8}, // idx == 358
{103, 8}, // idx == 359
{104, 8}, // idx == 360
{105, 8}, // idx == 361
{106, 8}, // idx == 362
{107, 8}, // idx == 363
{108, 8}, // idx == 364
{109, 8}, // idx == 365
{110, 8}, // idx == 366
{111, 8}, // idx == 367
{112, 8}, // idx == 368
{113, 8}, // idx == 369
{114, 8}, // idx == 370
{115, 8}, // idx == 371
{116, 8}, // idx == 372
{117, 8}, // idx == 373
{118, 8}, // idx == 374
{119, 8}, // idx == 375
{120, 8}, // idx == 376
{121, 8}, // idx == 377
{122, 8}, // idx == 378
{123, 8}, // idx == 379
{124, 8}, // idx == 380
{125, 8}, // idx == 381
{126, 8}, // idx == 382
{127, 8}, // idx == 383
{128, 8}, // idx == 384
{129, 8}, // idx == 385
{130, 8}, // idx == 386
{131, 8}, // idx == 387
{132, 8}, // idx == 388
{133, 8}, // idx == 389
{134, 8}, // idx == 390
{135, 8}, // idx == 391
{136, 8}, // idx == 392
{137, 8}, // idx == 393
{138, 8}, // idx == 394
{139, 8}, // idx == 395
{140, 8}, // idx == 396
{141, 8}, // idx == 397
{142, 8}, // idx == 398
{143, 8}, // idx == 399
{144, 8}, // idx == 400
{145, 8}, // idx == 401
{146, 8}, // idx == 402
{147, 8}, // idx == 403
{148, 8}, // idx == 404
{149, 8}, // idx == 405
{150, 8}, // idx == 406
{151, 8}, // idx == 407
{152, 8}, // idx == 408
{153, 8}, // idx == 409
{154, 8}, // idx == 410
{155, 8}, // idx == 411
{156, 8}, // idx == 412
{157, 8}, // idx == 413
{158, 8}, // idx == 414
{159, 8}, // idx == 415
{160, 8}, // idx == 416
{161, 8}, // idx == 417
{162, 8}, // idx == 418
{163, 8}, // idx == 419
{164, 8}, // idx == 420
{165, 8}, // idx == 421
{166, 8}, // idx == 422
{167, 8}, // idx == 423
{168, 8}, // idx == 424
{169, 8}, // idx == 425
{170, 8}, // idx == 426
{171, 8}, // idx == 427
{172, 8}, // idx == 428
{173, 8}, // idx == 429
{174, 8}, // idx == 430
{175, 8}, // idx == 431
{176, 8}, // idx == 432
{177, 8}, // idx == 433
{178, 8}, // idx == 434
{179, 8}, // idx == 435
{180, 8}, // idx == 436
{181, 8}, // idx == 437
{182, 8}, // idx == 438
{183, 8}, // idx == 439
{184, 8}, // idx == 440
{185, 8}, // idx == 441
{186, 8}, // idx == 442
{187, 8}, // idx == 443
{188, 8}, // idx == 444
{189, 8}, // idx == 445
{190, 8}, // idx == 446
{191, 8}, // idx == 447
{192, 8}, // idx == 448
{193, 8}, // idx == 449
{194, 8}, // idx == 450
{195, 8}, // idx == 451
{196, 8}, // idx == 452
{197, 8}, // idx == 453
{198, 8}, // idx == 454
{199, 8}, // idx == 455
{200, 8}, // idx == 456
{201, 8}, // idx == 457
{202, 8}, // idx == 458
{203, 8}, // idx == 459
{204, 8}, // idx == 460
{205, 8}, // idx == 461
{206, 8}, // idx == 462
{207, 8}, // idx == 463
{208, 8}, // idx == 464
{209, 8}, // idx == 465
{210, 8}, // idx == 466
{211, 8}, // idx == 467
{212, 8}, // idx == 468
{213, 8}, // idx == 469
{214, 8}, // idx == 470
{215, 8}, // idx == 471
{216, 8}, // idx == 472
{217, 8}, // idx == 473
{218, 8}, // idx == 474
{219, 8}, // idx == 475
{220, 8}, // idx == 476
{221, 8}, // idx == 477
{222, 8}, // idx == 478
{223, 8}, // idx == 479
{224, 8}, // idx == 480
{225, 8}, // idx == 481
{226, 8}, // idx == 482
{227, 8}, // idx == 483
{228, 8}, // idx == 484
{229, 8}, // idx == 485
{230, 8}, // idx == 486
{231, 8}, // idx == 487
{232, 8}, // idx == 488
{233, 8}, // idx == 489
{234, 8}, // idx == 490
{235, 8}, // idx == 491
{236, 8}, // idx == 492
{237, 8}, // idx == 493
{238, 8}, // idx == 494
{239, 8}, // idx == 495
{240, 8}, // idx == 496
{241, 8}, // idx == 497
{242, 8}, // idx == 498
{243, 8}, // idx == 499
{244, 8}, // idx == 500
{245, 8}, // idx == 501
{246, 8}, // idx == 502
{247, 8}, // idx == 503
{248, 8}, // idx == 504
{249, 8}, // idx == 505
{250, 8}, // idx == 506
{251, 8}, // idx == 507
{252, 8}, // idx == 508
{253, 8}, // idx == 509
{254, 8}, // idx == 510
{255, 8}, // idx == 511
}
+62
View File
@@ -0,0 +1,62 @@
// Copyright (c) 2026 Karl Gaissmaier
// SPDX-License-Identifier: MIT
// Package bart provides high-performance Balanced Routing Tables (BART)
// for fast IP-to-CIDR lookups on IPv4 and IPv6 addresses.
//
// BART offers three table variants optimized for different use cases:
//
// - Lite: Memory-optimized with popcount-compressed sparse arrays
// - Table: Full-featured with popcount-compressed sparse arrays
// - Fast: Speed-optimized with additional rank caching
//
// The implementation is based on Knuth's ART algorithm with novel
// optimizations for memory efficiency and lookup speed.
//
// `Table` and `Lite` use popcount compression for memory efficiency, while
// `Fast` trades additional memory and slower updates for maximum lookup
// speed: a child-rank cache is maintained on every insert/delete, so
// lookups avoid popcount but updates pay the caching cost.
//
// BART excels at efficient set operations on routing tables including Union,
// Overlaps, Equal, Subnets, and Supernets with optimal complexity, making it
// ideal for large-scale IP prefix matching in ACLs, RIBs, FIBs, firewalls,
// and routers.
//
// All variants also support copy-on-write persistence.
//
// For complex or pointer value types, you must provide custom deep cloning
// using Go's structural typing, implementing the following method on your
// value type V:
//
// func (v V) Clone() V
//
// For custom Equality implement the following method on your value type V:
//
// func (v V) Equal(other V) bool
//
// The bart package detects the interface satisfaction automatically at
// runtime using type assertions.
package bart
import (
"net/netip"
"github.com/gaissmai/bart/internal/nodes"
// inlining hint, see also the TestInlineBitSet256Functions.
// without this silent import the BitSet256 functions are not inlined
_ "github.com/gaissmai/bart/internal/bitset"
)
// stridePath is required in many places in the bart package and in
// internal/nodes. Aliased to keep the code readable.
type stridePath = nodes.StridePath
// DumpListNode contains CIDR, Value and Subnets, representing the trie
// in a sorted, recursive representation, especially useful for serialization.
type DumpListNode[V any] struct {
CIDR netip.Prefix `json:"cidr"`
Value V `json:"value"`
Subnets []DumpListNode[V] `json:"subnets,omitempty"`
}
+792
View File
@@ -0,0 +1,792 @@
//go:build generate
// Copyright (c) 2026 Karl Gaissmaier
// SPDX-License-Identifier: MIT
//go:generate ./scripts/generate-table-methods.sh
package bart
// ### GENERATE DELETE START ###
// stub code for generator types and methods
// useful for gopls during development, deleted during go generate
import (
"bytes"
"encoding/json"
"fmt"
"io"
"iter"
"net/netip"
"slices"
"strings"
"github.com/gaissmai/bart/internal/nodes"
"github.com/gaissmai/bart/internal/value"
)
type _NODE_TYPE[V any] struct{}
type _TABLE_TYPE[V any] struct {
root4 _NODE_TYPE[V]
root6 _NODE_TYPE[V]
size4 int
size6 int
}
func (n *_NODE_TYPE[V]) IsEmpty() (_ bool) { return }
func (n *_NODE_TYPE[V]) StatsRec() (_ nodes.StatsT) { return }
func (n *_NODE_TYPE[V]) PrefixCount() (_ int) { return }
func (n *_NODE_TYPE[V]) ChildCount() (_ int) { return }
func (n *_NODE_TYPE[V]) GetPrefix(uint8) (_ V, _ bool) { return }
func (n *_NODE_TYPE[V]) GetChild(uint8) (_ any, _ bool) { return }
func (n *_NODE_TYPE[V]) MustGetPrefix(uint8) (_ V) { return }
func (n *_NODE_TYPE[V]) MustGetChild(uint8) (_ any) { return }
func (n *_NODE_TYPE[V]) Insert(netip.Prefix, V, int) (_ bool) { return }
func (n *_NODE_TYPE[V]) Modify(netip.Prefix, func(V, bool) (V, bool)) (_ int) { return }
func (n *_NODE_TYPE[V]) Delete(netip.Prefix) (_ bool) { return }
func (n *_NODE_TYPE[V]) InsertPersist(nodes.CloneFunc[V], netip.Prefix, V, int) (_ bool) { return }
func (n *_NODE_TYPE[V]) DeletePersist(nodes.CloneFunc[V], netip.Prefix) (_ bool) { return }
func (n *_NODE_TYPE[V]) Get(netip.Prefix) (_ V, _ bool) { return }
func (n *_NODE_TYPE[V]) OverlapsPrefixAtDepth(netip.Prefix, int) (_ bool) { return }
func (n *_NODE_TYPE[V]) Overlaps(*_NODE_TYPE[V], int) (_ bool) { return }
func (n *_NODE_TYPE[V]) UnionRec(nodes.CloneFunc[V], *_NODE_TYPE[V], int) (_ int) { return }
func (n *_NODE_TYPE[V]) UnionRecPersist(nodes.CloneFunc[V], *_NODE_TYPE[V], int) (_ int) { return }
func (n *_NODE_TYPE[V]) EqualRec(*_NODE_TYPE[V]) (_ bool) { return }
func (n *_NODE_TYPE[V]) CloneRec(nodes.CloneFunc[V]) (_ *_NODE_TYPE[V]) { return }
func (n *_NODE_TYPE[V]) CloneFlat(nodes.CloneFunc[V]) (_ *_NODE_TYPE[V]) { return }
func (n *_NODE_TYPE[V]) GetChildAddrs(*[256]uint8) (_ []uint8) { return }
func (n *_NODE_TYPE[V]) GetIndices(*[256]uint8) (_ []uint8) { return }
func (n *_NODE_TYPE[V]) AllChildren() (_ iter.Seq2[uint8, any]) { return }
func (n *_NODE_TYPE[V]) AllIndices() (_ iter.Seq2[uint8, V]) { return }
func (n *_NODE_TYPE[V]) Contains(uint8) (_ bool) { return }
func (n *_NODE_TYPE[V]) Lookup(uint8) (_ V, _ bool) { return }
func (n *_NODE_TYPE[V]) LookupIdx(uint8) (_ uint8, _ V, _ bool) { return }
func (n *_NODE_TYPE[V]) Supernets(netip.Prefix, func(netip.Prefix, V) bool) { return }
func (n *_NODE_TYPE[V]) Subnets(netip.Prefix, func(netip.Prefix, V) bool) { return }
func (n *_NODE_TYPE[V]) FprintRec(io.Writer, nodes.TrieItem[V], string) (_ error) { return }
func (n *_NODE_TYPE[V]) DumpRec(io.Writer, stridePath, int, bool) { return }
func (n *_NODE_TYPE[V]) AllRec(stridePath, int, bool, func(netip.Prefix, V) bool) (_ bool) {
return
}
func (n *_NODE_TYPE[V]) AllRecSorted(stridePath, int, bool, func(netip.Prefix, V) bool) (_ bool) {
return
}
func (t *_TABLE_TYPE[V]) rootNodeByVersion(is4 bool) (_ *_NODE_TYPE[V]) { return }
func (t *_TABLE_TYPE[V]) InsertPersist(netip.Prefix, V) (_ *_TABLE_TYPE[V]) { return }
// ### GENERATE DELETE END ###
func (t *_TABLE_TYPE[V]) sizeUpdate(is4 bool, delta int) {
if is4 {
t.size4 += delta
return
}
t.size6 += delta
}
// insert adds or updates a prefix-value pair in the routing table.
// If the prefix already exists, its value is updated; otherwise a new entry is created.
// Invalid prefixes are silently ignored.
//
// The prefix is automatically canonicalized using pfx.Masked() to ensure
// consistent behavior regardless of host bits in the input.
func (t *_TABLE_TYPE[V]) insert(pfx netip.Prefix, val V) {
if !pfx.IsValid() {
return
}
// canonicalize prefix
pfx = pfx.Masked()
is4 := pfx.Addr().Is4()
n := t.rootNodeByVersion(is4)
if exists := n.Insert(pfx, val, 0); exists {
return
}
// true insert, update size
t.sizeUpdate(is4, 1)
}
// insertPersist is similar to insert but the receiver isn't modified.
//
// All nodes touched during insert are cloned and a new _TABLE_TYPE is returned.
// This is not a full [_TABLE_TYPE.Clone], all untouched nodes are still referenced
// from both Tables.
//
// If the payload type V contains pointers or needs deep copying,
// implement:
//
// func (v V) Clone() V
//
// The bart package detects this via structural typing and deep‑copies
// values during persistent ops.
//
// Due to cloning overhead this is significantly slower than insert,
// typically taking μsec instead of nsec.
func (t *_TABLE_TYPE[V]) insertPersist(pfx netip.Prefix, val V) *_TABLE_TYPE[V] {
if !pfx.IsValid() {
return t
}
// canonicalize prefix
pfx = pfx.Masked()
is4 := pfx.Addr().Is4()
// share size counters; root nodes cloned selectively.
pt := &_TABLE_TYPE[V]{
size4: t.size4,
size6: t.size6,
}
// Create a cloning function for deep copying values;
// returns nil if V does not provide a Clone() V method.
cloneFn := value.CloneFnFactory[V]()
// Clone root node corresponding to the IP version, for copy-on-write.
n := &pt.root4
if is4 {
pt.root4 = *t.root4.CloneFlat(cloneFn)
pt.root6 = t.root6
} else {
pt.root4 = t.root4
pt.root6 = *t.root6.CloneFlat(cloneFn)
n = &pt.root6
}
if !n.InsertPersist(cloneFn, pfx, val, 0) {
pt.sizeUpdate(is4, 1)
}
return pt
}
// Delete removes the exact prefix pfx from the table in-place.
//
// This is an exact-match operation (no LPM). If pfx exists, the entry is
// removed. If pfx does not exist or pfx is invalid, the table is left unchanged.
//
// The prefix is canonicalized (Masked) before lookup.
func (t *_TABLE_TYPE[V]) Delete(pfx netip.Prefix) {
if !pfx.IsValid() {
return
}
// canonicalize prefix
pfx = pfx.Masked()
is4 := pfx.Addr().Is4()
n := t.rootNodeByVersion(is4)
if exists := n.Delete(pfx); exists {
t.sizeUpdate(is4, -1)
}
}
// Get performs an exact-prefix lookup and returns whether the exact
// prefix exists. The prefix is canonicalized (Masked) before lookup.
//
// This is an exact-match operation (no LPM). The prefix must match exactly
// in both address and prefix length to be found. If pfx exists, the
// associated value (zero value for Lite) and found=true is returned.
// If pfx does not exist or pfx is invalid, the zero value for V and
// exists=false is returned.
//
// For longest-prefix-match (LPM) lookups, use Contains(ip), Lookup(ip),
// LookupPrefix(pfx) or LookupPrefixLPM(pfx) instead.
func (t *_TABLE_TYPE[V]) Get(pfx netip.Prefix) (val V, exists bool) {
if !pfx.IsValid() {
return val, exists
}
// canonicalize prefix
pfx = pfx.Masked()
is4 := pfx.Addr().Is4()
n := t.rootNodeByVersion(is4)
return n.Get(pfx)
}
// DeletePersist is similar to Delete but does not modify the receiver.
//
// It performs a copy-on-write delete operation, cloning all nodes touched during
// deletion and returning a new _TABLE_TYPE reflecting the change.
//
// If the prefix is invalid or doesn't exist, the original table is
// returned unchanged.
//
// If the payload type V contains pointers or requires deep copying,
// it must implement the Clone method for correct cloning.
//
// Due to cloning overhead this is significantly slower than Delete,
// typically taking μsec instead of nsec.
func (t *_TABLE_TYPE[V]) DeletePersist(pfx netip.Prefix) *_TABLE_TYPE[V] {
if !pfx.IsValid() {
return t
}
// canonicalize prefix
pfx = pfx.Masked()
is4 := pfx.Addr().Is4()
// Preflight check: avoid cloning if prefix doesn't exist
n := t.rootNodeByVersion(is4)
if _, found := n.Get(pfx); !found {
return t
}
// share size counters; root nodes cloned selectively.
pt := &_TABLE_TYPE[V]{
size4: t.size4,
size6: t.size6,
}
// Create a cloning function for deep copying values;
// returns nil if V does not provide a Clone() V method.
cloneFn := value.CloneFnFactory[V]()
// Clone root node corresponding to the IP version, for copy-on-write.
if is4 {
pt.root4 = *t.root4.CloneFlat(cloneFn)
pt.root6 = t.root6
n = &pt.root4
} else {
pt.root4 = t.root4
pt.root6 = *t.root6.CloneFlat(cloneFn)
n = &pt.root6
}
if exists := n.DeletePersist(cloneFn, pfx); exists {
pt.sizeUpdate(is4, -1)
}
return pt
}
// ModifyPersist is similar to Modify but the receiver isn't modified and
// a new *_TABLE_TYPE is returned.
func (t *_TABLE_TYPE[V]) ModifyPersist(pfx netip.Prefix, cb func(_ V, ok bool) (_ V, del bool)) *_TABLE_TYPE[V] {
if !pfx.IsValid() {
return t
}
// make a cheap test in front of expensive operation
oldVal, ok := t.Get(pfx)
val := oldVal
// to clone or not to clone ...
cloneFn := value.CloneFnFactory[V]()
if cloneFn != nil && ok {
val = cloneFn(oldVal)
}
newVal, del := cb(val, ok)
switch {
case !ok && del: // no-op
return t
case !ok && !del: // insert
return t.InsertPersist(pfx, newVal)
case ok && !del: // update
return t.InsertPersist(pfx, newVal)
case ok && del: // delete
return t.DeletePersist(pfx)
}
panic("unreachable")
}
// Supernets returns an iterator over all supernet routes that cover the given prefix pfx.
//
// The traversal searches both exact-length and shorter (less specific) prefixes that
// include pfx. Starting from the most specific position in the trie,
// it walks upward through parent nodes and yields any matching entries found at each level.
//
// The iteration order is reverse-CIDR: from longest prefix match (LPM) towards
// least-specific routes.
//
// This can be used to enumerate all covering supernet routes in routing-based
// policy engines, diagnostics tools, or fallback resolution logic.
//
// Example:
//
// for supernet, val := range table.Supernets(netip.MustParsePrefix("192.0.2.128/25")) {
// fmt.Println("Covered by:", supernet, "->", val)
// }
//
// The iteration can be stopped early by breaking from the range loop.
// Returns an empty iterator if the prefix is invalid.
func (t *_TABLE_TYPE[V]) Supernets(pfx netip.Prefix) iter.Seq2[netip.Prefix, V] {
return func(yield func(netip.Prefix, V) bool) {
if !pfx.IsValid() {
return
}
// canonicalize the prefix
pfx = pfx.Masked()
is4 := pfx.Addr().Is4()
n := t.rootNodeByVersion(is4)
n.Supernets(pfx, yield)
}
}
// Subnets returns an iterator over all subnets of the given prefix
// in natural CIDR sort order. This includes prefixes of the same length
// (exact match) and longer (more specific) prefixes that are contained
// within the given prefix.
//
// Example:
//
// for sub, val := range table.Subnets(netip.MustParsePrefix("10.0.0.0/8")) {
// fmt.Println("Covered:", sub, "->", val)
// }
//
// The iteration can be stopped early by breaking from the range loop.
// Returns an empty iterator if the prefix is invalid.
func (t *_TABLE_TYPE[V]) Subnets(pfx netip.Prefix) iter.Seq2[netip.Prefix, V] {
return func(yield func(netip.Prefix, V) bool) {
if !pfx.IsValid() {
return
}
pfx = pfx.Masked()
is4 := pfx.Addr().Is4()
n := t.rootNodeByVersion(is4)
n.Subnets(pfx, yield)
}
}
// OverlapsPrefix reports whether any prefix in the routing table overlaps with
// the given prefix. Two prefixes overlap if they share any IP addresses.
//
// The check is bidirectional: it returns true if the input prefix is covered by an existing
// route, or if any stored route is itself contained within the input prefix.
//
// Internally, the function normalizes the prefix and descends the relevant trie branch,
// using stride-based logic to identify overlap without performing a full lookup.
//
// This is useful for containment tests, route validation, or policy checks using prefix
// semantics without retrieving exact matches.
func (t *_TABLE_TYPE[V]) OverlapsPrefix(pfx netip.Prefix) bool {
if !pfx.IsValid() {
return false
}
// canonicalize the prefix
pfx = pfx.Masked()
is4 := pfx.Addr().Is4()
n := t.rootNodeByVersion(is4)
return n.OverlapsPrefixAtDepth(pfx, 0)
}
// Overlaps reports whether any route in the receiver table overlaps
// with a route in the other table, in either direction.
//
// The overlap check is bidirectional: it returns true if any IP prefix
// in the receiver is covered by the other table, or vice versa.
// This includes partial overlaps, exact matches, and supernet/subnet relationships.
//
// Both IPv4 and IPv6 route trees are compared independently. If either
// tree has overlapping routes, the function returns true.
//
// This is useful for conflict detection, policy enforcement,
// or validating mutually exclusive routing domains.
func (t *_TABLE_TYPE[V]) Overlaps(o *_TABLE_TYPE[V]) bool {
return t.Overlaps4(o) || t.Overlaps6(o)
}
// Overlaps4 is like [_TABLE_TYPE.Overlaps] but for the v4 routing table only.
func (t *_TABLE_TYPE[V]) Overlaps4(o *_TABLE_TYPE[V]) bool {
if t.size4 == 0 || o.size4 == 0 {
return false
}
return t.root4.Overlaps(&o.root4, 0)
}
// Overlaps6 is like [_TABLE_TYPE.Overlaps] but for the v6 routing table only.
func (t *_TABLE_TYPE[V]) Overlaps6(o *_TABLE_TYPE[V]) bool {
if t.size6 == 0 || o.size6 == 0 {
return false
}
return t.root6.Overlaps(&o.root6, 0)
}
// Union merges another routing table into the receiver table, modifying it in-place.
//
// All prefixes and values from the other table (o) are inserted into the receiver.
// If a duplicate prefix exists in both tables, the value from o replaces the existing entry.
// This duplicate is shallow-copied by default, but if the value type V implements the
// Clone method, the value is deeply cloned before insertion. See also _TABLE_TYPE.Clone.
func (t *_TABLE_TYPE[V]) Union(o *_TABLE_TYPE[V]) {
// panics on nil receiver
_ = t.size4
// panics on nil argument
if o.size4 == 0 && o.size6 == 0 {
return
}
// t is unchanged
if o == t {
return
}
// Create a cloning function for deep copying values;
// returns nil if V does not provide a Clone() V method.
cloneFn := value.CloneFnFactory[V]()
dup4 := t.root4.UnionRec(cloneFn, &o.root4, 0)
dup6 := t.root6.UnionRec(cloneFn, &o.root6, 0)
t.size4 += o.size4 - dup4
t.size6 += o.size6 - dup6
}
// UnionPersist is similar to [Union] but the receiver isn't modified.
//
// All nodes touched during union are cloned and a new *_TABLE_TYPE is returned.
// If o is empty, no nodes are touched and the receiver may be
// returned unchanged.
func (t *_TABLE_TYPE[V]) UnionPersist(o *_TABLE_TYPE[V]) *_TABLE_TYPE[V] {
// panics on nil receiver
_ = t.size4
// panics on nil argument
if o.size4 == 0 && o.size6 == 0 {
return t
}
if o == t {
return t
}
// Create a cloning function for deep copying values;
// returns nil if V does not provide a Clone() V method.
cloneFn := value.CloneFnFactory[V]()
// new _TABLE_TYPE with root nodes just copied.
pt := &_TABLE_TYPE[V]{
root4: t.root4,
root6: t.root6,
//
size4: t.size4,
size6: t.size6,
}
// only clone the root node if there is something to union
if o.size4 != 0 {
pt.root4 = *t.root4.CloneFlat(cloneFn)
}
if o.size6 != 0 {
pt.root6 = *t.root6.CloneFlat(cloneFn)
}
dup4 := pt.root4.UnionRecPersist(cloneFn, &o.root4, 0)
dup6 := pt.root6.UnionRecPersist(cloneFn, &o.root6, 0)
pt.size4 += o.size4 - dup4
pt.size6 += o.size6 - dup6
return pt
}
// Equal checks whether two tables are structurally and semantically equal.
// It ensures both trees (IPv4-based and IPv6-based) have the same sizes and
// recursively compares their root nodes.
//
// If V implements an `Equal(V) bool` method, its custom equality logic is used.
// Otherwise, values are compared directly using the == operator.
//
// Note: If V implements `Equal(V) bool` with a pointer receiver, the Equal
// method should handle nil receivers gracefully.
//
// ATTENTION: If V is not comparable at runtime (such as a slice or map without an `Equal`
// method), a runtime panic will occur.
func (t *_TABLE_TYPE[V]) Equal(o *_TABLE_TYPE[V]) bool {
if t.size4 != o.size4 || t.size6 != o.size6 {
return false
}
if o == t {
return true
}
return t.root4.EqualRec(&o.root4) && t.root6.EqualRec(&o.root6)
}
// Clone returns a copy of the routing table.
// The payload of type V is shallow copied by default. To enable deep copying,
// implement the following method on your value type:
//
// Clone() V
//
// Example:
//
// type MyValue struct { Data []byte }
// func (v MyValue) Clone() MyValue {
// return MyValue{Data: slices.Clone(v.Data)}
// }
//
// The bart package will automatically detect and use this method via Go's
// structural typing.
//
// Note: If V implements Clone() V with a pointer receiver, the Clone
// method should handle nil receivers gracefully.
func (t *_TABLE_TYPE[V]) Clone() *_TABLE_TYPE[V] {
c := new(_TABLE_TYPE[V])
cloneFn := value.CloneFnFactory[V]()
c.root4 = *t.root4.CloneRec(cloneFn)
c.root6 = *t.root6.CloneRec(cloneFn)
c.size4 = t.size4
c.size6 = t.size6
return c
}
// Size returns the prefix count.
func (t *_TABLE_TYPE[V]) Size() int {
return t.size4 + t.size6
}
// Size4 returns the IPv4 prefix count.
func (t *_TABLE_TYPE[V]) Size4() int {
return t.size4
}
// Size6 returns the IPv6 prefix count.
func (t *_TABLE_TYPE[V]) Size6() int {
return t.size6
}
// All returns an iterator over all prefix–value pairs in the table.
//
// The iteration order is unspecified and may vary between calls; for a stable order,
// use [_TABLE_TYPE.AllSorted].
//
// IMPORTANT: Modifying the table during iteration is not allowed,
// as this would interfere with the internal traversal and may corrupt or
// prematurely terminate the iteration.
func (t *_TABLE_TYPE[V]) All() iter.Seq2[netip.Prefix, V] {
return func(yield func(netip.Prefix, V) bool) {
_ = t.root4.AllRec(stridePath{}, 0, true, yield) && t.root6.AllRec(stridePath{}, 0, false, yield)
}
}
// All4 is like [_TABLE_TYPE.All] but only for the v4 routing table.
func (t *_TABLE_TYPE[V]) All4() iter.Seq2[netip.Prefix, V] {
return func(yield func(netip.Prefix, V) bool) {
_ = t.root4.AllRec(stridePath{}, 0, true, yield)
}
}
// All6 is like [_TABLE_TYPE.All] but only for the v6 routing table.
func (t *_TABLE_TYPE[V]) All6() iter.Seq2[netip.Prefix, V] {
return func(yield func(netip.Prefix, V) bool) {
_ = t.root6.AllRec(stridePath{}, 0, false, yield)
}
}
// AllSorted is like [_TABLE_TYPE.All] but the iteration is ordered in canonical
// CIDR prefix sort order.
func (t *_TABLE_TYPE[V]) AllSorted() iter.Seq2[netip.Prefix, V] {
return func(yield func(netip.Prefix, V) bool) {
_ = t.root4.AllRecSorted(stridePath{}, 0, true, yield) &&
t.root6.AllRecSorted(stridePath{}, 0, false, yield)
}
}
// AllSorted4 is like [_TABLE_TYPE.AllSorted] but only for the v4 routing table.
func (t *_TABLE_TYPE[V]) AllSorted4() iter.Seq2[netip.Prefix, V] {
return func(yield func(netip.Prefix, V) bool) {
_ = t.root4.AllRecSorted(stridePath{}, 0, true, yield)
}
}
// AllSorted6 is like [_TABLE_TYPE.AllSorted] but only for the v6 routing table.
func (t *_TABLE_TYPE[V]) AllSorted6() iter.Seq2[netip.Prefix, V] {
return func(yield func(netip.Prefix, V) bool) {
_ = t.root6.AllRecSorted(stridePath{}, 0, false, yield)
}
}
// Fprint writes a hierarchical tree diagram of the ordered CIDRs
// with default formatted payload V to w.
//
// The order from top to bottom is in ascending order of the prefix address
// and the subtree structure is determined by the CIDRs coverage.
//
// ▼
// ├─ 10.0.0.0/8 (V)
// │ ├─ 10.0.0.0/24 (V)
// │ └─ 10.0.1.0/24 (V)
// ├─ 127.0.0.0/8 (V)
// │ └─ 127.0.0.1/32 (V)
// ├─ 169.254.0.0/16 (V)
// ├─ 172.16.0.0/12 (V)
// └─ 192.168.0.0/16 (V)
// └─ 192.168.1.0/24 (V)
// ▼
// └─ ::/0 (V)
// ├─ ::1/128 (V)
// ├─ 2000::/3 (V)
// │ └─ 2001:db8::/32 (V)
// └─ fe80::/10 (V)
func (t *_TABLE_TYPE[V]) Fprint(w io.Writer) error {
if w == nil && t != nil {
return fmt.Errorf("nil writer")
}
// v4
if err := t.fprint(w, true); err != nil {
return err
}
// v6
if err := t.fprint(w, false); err != nil {
return err
}
return nil
}
// fprint is the version dependent adapter to fprintRec.
func (t *_TABLE_TYPE[V]) fprint(w io.Writer, is4 bool) error {
n := t.rootNodeByVersion(is4)
if n.IsEmpty() {
return nil
}
if _, err := fmt.Fprint(w, "▼\n"); err != nil {
return err
}
startParent := nodes.TrieItem[V]{
Node: nil,
Idx: 0,
Path: stridePath{},
Is4: is4,
}
return n.FprintRec(w, startParent, "")
}
// MarshalText implements the [encoding.TextMarshaler] interface,
// just a wrapper for [_TABLE_TYPE.Fprint].
func (t *_TABLE_TYPE[V]) MarshalText() ([]byte, error) {
w := new(bytes.Buffer)
if err := t.Fprint(w); err != nil {
return nil, err
}
return w.Bytes(), nil
}
// MarshalJSON dumps the table into two sorted lists: for ipv4 and ipv6.
// Every root and subnet is an array, not a map, because the order matters.
func (t *_TABLE_TYPE[V]) MarshalJSON() ([]byte, error) {
result := struct {
Ipv4 []DumpListNode[V] `json:"ipv4,omitempty"`
Ipv6 []DumpListNode[V] `json:"ipv6,omitempty"`
}{
Ipv4: t.DumpList4(),
Ipv6: t.DumpList6(),
}
buf, err := json.Marshal(result)
if err != nil {
return nil, err
}
return buf, nil
}
// DumpList4 dumps the ipv4 tree into a list of roots and their subnets.
// It can be used to analyze the tree or build the text or JSON serialization.
func (t *_TABLE_TYPE[V]) DumpList4() []DumpListNode[V] {
return t.dumpListRec(&t.root4, 0, stridePath{}, 0, true)
}
// DumpList6 dumps the ipv6 tree into a list of roots and their subnets.
// It can be used to analyze the tree or build custom JSON representation.
func (t *_TABLE_TYPE[V]) DumpList6() []DumpListNode[V] {
return t.dumpListRec(&t.root6, 0, stridePath{}, 0, false)
}
// dumpListRec, build the data structure rec-descent with the help of directItemsRec.
// anyNode is nodes.BartNode, nodes.FastNode or nodes.LiteNode
func (t *_TABLE_TYPE[V]) dumpListRec(anyNode any, parentIdx uint8, path stridePath, depth int, is4 bool) []DumpListNode[V] {
// recursion stop condition
if anyNode == nil {
return nil
}
// the same method is generated for all table types, therefore
// type assert to the smallest needed interface.
// The panic on wrong type assertion is by intention, MUST NOT happen
n := anyNode.(interface {
DirectItemsRec(uint8, stridePath, int, bool) []nodes.TrieItem[V]
})
directItems := n.DirectItemsRec(parentIdx, path, depth, is4)
// sort the items by prefix
slices.SortFunc(directItems, func(a, b nodes.TrieItem[V]) int {
return nodes.CmpPrefix(a.Cidr, b.Cidr)
})
dumpNodes := make([]DumpListNode[V], 0, len(directItems))
for _, item := range directItems {
dumpNodes = append(dumpNodes, DumpListNode[V]{
CIDR: item.Cidr,
Value: item.Val,
// build it rec-descent, item.Node is also from type any
Subnets: t.dumpListRec(item.Node, item.Idx, item.Path, item.Depth, is4),
})
}
return dumpNodes
}
// dumpString is just a wrapper for dump.
func (t *_TABLE_TYPE[V]) dumpString() string {
w := new(strings.Builder)
t.dump(w)
return w.String()
}
// dump the table structure and all the nodes to w.
func (t *_TABLE_TYPE[V]) dump(w io.Writer) {
if t.size4 > 0 {
stats := t.root4.StatsRec()
fmt.Fprintln(w)
fmt.Fprintf(w, "### IPv4: size(%d), subnodes(%d), prefixes(%d), leaves(%d), fringes(%d)",
t.size4, stats.SubNodes, stats.Prefixes, stats.Leaves, stats.Fringes)
t.root4.DumpRec(w, stridePath{}, 0, true)
}
if t.size6 > 0 {
stats := t.root6.StatsRec()
fmt.Fprintln(w)
fmt.Fprintf(w, "### IPv6: size(%d), subnodes(%d), prefixes(%d), leaves(%d), fringes(%d)",
t.size6, stats.SubNodes, stats.Prefixes, stats.Leaves, stats.Fringes)
t.root6.DumpRec(w, stridePath{}, 0, false)
}
}
File diff suppressed because it is too large. Load diff
-290
View File
@@ -1,290 +0,0 @@
// Copyright (c) 2024 Karl Gaissmaier
// SPDX-License-Identifier: MIT
package bart
import (
"fmt"
"io"
"strconv"
"strings"
)
type nodeType byte
const (
nullNode nodeType = iota // empty node
fullNode // prefixes and children or path-compressed prefixes
leafNode // no children, only prefixes or path-compressed prefixes
intermediateNode // only children, no prefix nor path-compressed prefixes
)
// ##################################################
// useful during development, debugging and testing
// ##################################################
// dumpString is just a wrapper for dump.
func (t *Table[V]) dumpString() string {
w := new(strings.Builder)
t.dump(w)
return w.String()
}
// dump the table structure and all the nodes to w.
func (t *Table[V]) dump(w io.Writer) {
if t == nil {
return
}
if t.size4 > 0 {
fmt.Fprintln(w)
fmt.Fprintf(w, "### IPv4: size(%d), nodes(%d)", t.size4, t.root4.nodeStatsRec().nodes)
t.root4.dumpRec(w, stridePath{}, 0, true)
}
if t.size6 > 0 {
fmt.Fprintln(w)
fmt.Fprintf(w, "### IPv6: size(%d), nodes(%d)", t.size6, t.root6.nodeStatsRec().nodes)
t.root6.dumpRec(w, stridePath{}, 0, false)
}
}
// dumpRec, rec-descent the trie.
func (n *node[V]) dumpRec(w io.Writer, path stridePath, depth int, is4 bool) {
// dump this node
n.dump(w, path, depth, is4)
// the node may have childs, rec-descent down
for i, addr := range n.children.All() {
octet := byte(addr)
path[depth] = octet
if child, ok := n.children.Items[i].(*node[V]); ok {
child.dumpRec(w, path, depth+1, is4)
}
}
}
// dump the node to w.
func (n *node[V]) dump(w io.Writer, path stridePath, depth int, is4 bool) {
bits := depth * strideLen
indent := strings.Repeat(".", depth)
// node type with depth and octet path and bits.
fmt.Fprintf(w, "\n%s[%s] depth: %d path: [%s] / %d\n",
indent, n.hasType(), depth, ipStridePath(path, depth, is4), bits)
if nPfxCount := n.prefixes.Len(); nPfxCount != 0 {
// no heap allocs
allIndices := n.prefixes.All()
// print the baseIndices for this node.
fmt.Fprintf(w, "%sindexs(#%d): %v\n", indent, nPfxCount, allIndices)
// print the prefixes for this node
fmt.Fprintf(w, "%sprefxs(#%d):", indent, nPfxCount)
for _, idx := range allIndices {
octet, pfxLen := idxToPfx(idx)
fmt.Fprintf(w, " %s/%d", octetFmt(octet, is4), pfxLen)
}
fmt.Fprintln(w)
// print the values for this node
fmt.Fprintf(w, "%svalues(#%d):", indent, nPfxCount)
for _, val := range n.prefixes.Items {
fmt.Fprintf(w, " %v", val)
}
fmt.Fprintln(w)
}
if n.children.Len() != 0 {
nodeAddrs := make([]uint, 0, maxNodeChildren)
leafAddrs := make([]uint, 0, maxNodeChildren)
// the node has recursive child nodes or path-compressed leaves
for i, addr := range n.children.All() {
switch n.children.Items[i].(type) {
case *node[V]:
nodeAddrs = append(nodeAddrs, addr)
continue
case *leaf[V]:
leafAddrs = append(leafAddrs, addr)
default:
panic("logic error, wrong node type")
}
}
if nodeCount := len(nodeAddrs); nodeCount > 0 {
// print the childs for this node
fmt.Fprintf(w, "%schilds(#%d):", indent, nodeCount)
for _, addr := range nodeAddrs {
octet := byte(addr)
fmt.Fprintf(w, " %s", octetFmt(octet, is4))
}
fmt.Fprintln(w)
}
if leafCount := len(leafAddrs); leafCount > 0 {
// print the pathcomp prefixes for this node
fmt.Fprintf(w, "%sleaves(#%d):", indent, leafCount)
for _, addr := range leafAddrs {
octet := byte(addr)
k := n.children.MustGet(addr)
pc := k.(*leaf[V])
fmt.Fprintf(w, " %s:{%s, %v}", octetFmt(octet, is4), pc.prefix, pc.value)
}
fmt.Fprintln(w)
}
}
}
// hasType returns the nodeType.
func (n *node[V]) hasType() nodeType {
s := n.nodeStats()
switch {
case s.pfxs == 0 && s.childs == 0:
return nullNode
case s.nodes == 0:
return leafNode
case (s.pfxs > 0 || s.leaves > 0) && s.nodes > 0:
return fullNode
case (s.pfxs == 0 && s.leaves == 0) && s.nodes > 0:
return intermediateNode
default:
panic(fmt.Sprintf("UNREACHABLE: pfx: %d, chld: %d, node: %d, leaf: %d",
s.pfxs, s.childs, s.nodes, s.leaves))
}
}
// octetFmt, different format strings for IPv4 and IPv6, decimal versus hex.
func octetFmt(octet byte, is4 bool) string {
if is4 {
return fmt.Sprintf("%d", octet)
}
return fmt.Sprintf("0x%02x", octet)
}
// ip stride path, different formats for IPv4 and IPv6, dotted decimal or hex.
//
// 127.0.0
// 2001:0d
func ipStridePath(path stridePath, depth int, is4 bool) string {
buf := new(strings.Builder)
if is4 {
for i, b := range path[:depth] {
if i != 0 {
buf.WriteString(".")
}
buf.WriteString(strconv.Itoa(int(b)))
}
return buf.String()
}
for i, b := range path[:depth] {
if i != 0 && i%2 == 0 {
buf.WriteString(":")
}
buf.WriteString(fmt.Sprintf("%02x", b))
}
return buf.String()
}
// String implements Stringer for nodeType.
func (nt nodeType) String() string {
switch nt {
case nullNode:
return "NULL"
case fullNode:
return "FULL"
case leafNode:
return "LEAF"
case intermediateNode:
return "IMED"
default:
return "unreachable"
}
}
// stats, only used for dump, tests and benchmarks
type stats struct {
pfxs int
childs int
nodes int
leaves int
}
// node statistics for this single node
func (n *node[V]) nodeStats() stats {
var s stats
s.pfxs = n.prefixes.Len()
s.childs = n.children.Len()
for i := range n.children.All() {
switch n.children.Items[i].(type) {
case *node[V]:
s.nodes++
case *leaf[V]:
s.leaves++
default:
panic("logic error, wrong node type")
}
}
return s
}
// nodeStatsRec, calculate the number of pfxs, nodes and leaves under n, rec-descent.
func (n *node[V]) nodeStatsRec() stats {
var s stats
if n == nil || n.isEmpty() {
return s
}
s.pfxs = n.prefixes.Len()
s.childs = n.children.Len()
s.nodes = 1 // this node
s.leaves = 0
for _, kidAny := range n.children.Items {
switch kid := kidAny.(type) {
case *node[V]:
// rec-descent
rs := kid.nodeStatsRec()
s.pfxs += rs.pfxs
s.childs += rs.childs
s.nodes += rs.nodes
s.leaves += rs.leaves
case *leaf[V]:
s.leaves++
default:
panic("logic error, wrong node type")
}
}
return s
}
+399
View File
@@ -0,0 +1,399 @@
// Copyright (c) 2026 Karl Gaissmaier
// SPDX-License-Identifier: MIT
package bart
import (
"net/netip"
"sync"
"github.com/gaissmai/bart/internal/art"
"github.com/gaissmai/bart/internal/lpm"
"github.com/gaissmai/bart/internal/nodes"
)
// Fast is similar to Table but uses additional 256 bytes in each node
// for faster level traversing in the multibit trie.
//
// As a result Fast sacrifices memory efficiency to achieve 25% better
// speed in lookup and contains.
//
// The zero value is ready to use.
//
// A Fast must not be copied by value; always pass by pointer.
// Nil pointers as receivers or arguments are forbidden and will panic.
//
// The Fast is safe for concurrent reads, but concurrent reads and writes
// must be externally synchronized. Mutation via Insert/Delete requires locks,
// or alternatively, use ...Persist methods which return a modified copy
// without altering the original table (copy-on-write).
//
// Performance note: Do not pass IPv4-in-IPv6 addresses (e.g., ::ffff:192.0.2.1)
// as input. The methods do not perform automatic unmapping to avoid unnecessary
// overhead for the common case where native addresses are used.
// Users should unmap IPv4-in-IPv6 addresses to their native IPv4 form
// (e.g., 192.0.2.1) before calling these methods.
type Fast[V any] struct {
// used by -copylocks checker from `go vet`.
_ [0]sync.Mutex
// the root nodes, implemented as popcount compressed multibit tries
root4 nodes.FastNode[V]
root6 nodes.FastNode[V]
// the number of prefixes in the routing table
size4 int
size6 int
}
// rootNodeByVersion, root node getter for ip version.
func (f *Fast[V]) rootNodeByVersion(is4 bool) *nodes.FastNode[V] {
if is4 {
return &f.root4
}
return &f.root6
}
// Insert adds or updates a prefix-value pair in the routing table.
// If the prefix already exists, its value is updated; otherwise a new entry is created.
// Invalid prefixes are silently ignored.
//
// The prefix is automatically canonicalized using pfx.Masked() to ensure
// consistent behavior regardless of host bits in the input.
func (f *Fast[V]) Insert(pfx netip.Prefix, val V) {
f.insert(pfx, val)
}
// InsertPersist is similar to Insert but the receiver isn't modified.
//
// All nodes touched during insert are cloned and a new Fast is returned.
// This is not a full [Fast.Clone], all untouched nodes are still referenced
// from both Fasts.
//
// If the payload type V contains pointers or needs deep copying,
// it must implement the Clone method to support correct cloning.
//
// Due to cloning overhead this is significantly slower than Insert,
// typically taking μsec instead of nsec.
func (f *Fast[V]) InsertPersist(pfx netip.Prefix, val V) *Fast[V] {
return f.insertPersist(pfx, val)
}
// Modify applies an insert, update, or delete operation for the value
// associated with the given prefix. The supplied callback decides the
// operation: it is called with the current value (or zero if not found)
// and a boolean indicating whether the prefix exists. The callback must
// return a new value and a delete flag: del == false inserts or updates,
// del == true deletes the entry if it exists (otherwise no-op).
//
// The callback is invoked at most once per call.
//
// The operation is determined by the callback function, which is called with:
//
// val: the current value (or zero value if not found)
// found: true if the prefix currently exists, false otherwise
//
// The callback returns:
//
// val: the new value to insert or update (ignored if del == true)
// del: true to delete the entry, false to insert or update
//
// Summary of callback semantics:
//
// | cb-input | cb-return | Ops |
// |-----------------|-----------------|--------|
// | (zero, false) | (_, true) | no-op |
// | (zero, false) | (newVal, false) | insert |
// | (oldVal, true) | (newVal, false) | update |
// | (oldVal, true) | (_, true) | delete |
// ------------------------------------- --------
func (f *Fast[V]) Modify(pfx netip.Prefix, cb func(_ V, ok bool) (_ V, del bool)) {
if !pfx.IsValid() {
return
}
// canonicalize prefix
pfx = pfx.Masked()
is4 := pfx.Addr().Is4()
n := f.rootNodeByVersion(is4)
delta := n.Modify(pfx, cb)
f.sizeUpdate(is4, delta)
}
// Contains reports whether any stored prefix covers the given IP address.
// Returns false for invalid IP addresses.
//
// This performs longest-prefix matching and returns true if any prefix
// in the routing table contains the IP address, regardless of the associated value.
//
// It does not return the value or the prefix of the matching item,
// but as a test against an allow-/deny-list it's often sufficient
// and even few nanoseconds faster than [Fast.Lookup].
func (f *Fast[V]) Contains(ip netip.Addr) bool {
// speed is top priority: no explicit test for ip.IsValid
// if ip is invalid, AsSlice() returns nil, Contains returns false.
is4 := ip.Is4()
n := f.rootNodeByVersion(is4)
for _, octet := range ip.AsSlice() {
// for contains, any lpm match is good enough, no backtracking needed
if n.PrefixCount() != 0 && n.Contains(art.OctetToIdx(octet)) {
return true
}
// stop traversing?
if !n.Children.Test(octet) {
return false
}
kid := n.MustGetChild(octet)
// kid is node or leaf or fringe at octet
switch kid := kid.(type) {
case *nodes.FastNode[V]:
n = kid // descend down to next trie level
case *nodes.FringeNode[V]:
// fringe is the default-route for all possible octets below
return true
case *nodes.LeafNode[V]:
return kid.Prefix.Contains(ip)
}
}
return false
}
// Lookup performs a longest prefix match (LPM) lookup for the given address.
// It finds the most specific (longest) prefix in the routing table that
// contains the given address and returns its associated value.
//
// This is the fundamental operation for IP routing decisions, finding the
// best matching route for a destination address.
//
// Returns the associated value and true if a matching prefix is found.
// Returns zero value and false if no prefix contains the address.
func (f *Fast[V]) Lookup(ip netip.Addr) (val V, ok bool) {
if !ip.IsValid() {
return val, ok
}
is4 := ip.Is4()
octets := ip.AsSlice()
n := f.rootNodeByVersion(is4)
// stack of the traversed nodes for fast backtracking, if needed
stack := [nodes.MaxTreeDepth]*nodes.FastNode[V]{}
// run variable, used after for loop
var depth int
var octet byte
LOOP:
// find leaf node
for depth, octet = range octets {
depth &= nodes.DepthMask // BCE, Lookup must be fast
// push current node on stack for fast backtracking
stack[depth] = n
// go down in tight loop to last octet
if !n.Children.Test(octet) {
// no more nodes below octet
break LOOP
}
kid := n.MustGetChild(octet)
// kid is node or leaf or fringe at octet
switch kid := kid.(type) {
case *nodes.FastNode[V]:
n = kid
continue LOOP // descend down to next trie level
case *nodes.FringeNode[V]:
// fringe is the default-route for all possible octets below
return kid.Value, true
case *nodes.LeafNode[V]:
if kid.Prefix.Contains(ip) {
return kid.Value, true
}
// reached a path compressed prefix, stop traversing
break LOOP
}
}
// start backtracking, unwind the stack, bounds check eliminated
for ; depth >= 0; depth-- {
depth &= nodes.DepthMask // BCE
n = stack[depth]
// longest prefix match, skip if node has no prefixes
if n.PrefixCount() != 0 {
idx := art.OctetToIdx(octets[depth])
// lookupIdx() manually inlined
if lpmIdx, ok2 := n.Prefixes.IntersectionTop(&lpm.LookupTbl[idx]); ok2 {
return n.MustGetPrefix(lpmIdx), ok2
}
}
}
return val, ok
}
// LookupPrefix performs a longest prefix match lookup for any address within
// the given prefix. It finds the most specific routing table entry that would
// match any address in the provided prefix range.
//
// This is functionally identical to LookupPrefixLPM but returns only the
// associated value, not the matching prefix itself.
//
// Returns the value and true if a matching prefix is found.
// Returns zero value and false if no match exists.
func (f *Fast[V]) LookupPrefix(pfx netip.Prefix) (val V, ok bool) {
_, val, ok = f.lookupPrefixLPM(pfx, false)
return val, ok
}
// LookupPrefixLPM performs a longest prefix match lookup for any address within
// the given prefix. It finds the most specific routing table entry that would
// match any address in the provided prefix range.
//
// This is functionally identical to LookupPrefix but additionally returns the
// matching LPM prefix itself along with the value.
//
// This method is slower than LookupPrefix and should only be used if the
// matching lpm entry is also required for other reasons.
//
// Returns the matching prefix, its associated value, and true if found.
// Returns zero values and false if no match exists.
func (f *Fast[V]) LookupPrefixLPM(pfx netip.Prefix) (lpmPfx netip.Prefix, val V, ok bool) {
return f.lookupPrefixLPM(pfx, true)
}
func (f *Fast[V]) lookupPrefixLPM(pfx netip.Prefix, withLPM bool) (lpmPfx netip.Prefix, val V, ok bool) {
if !pfx.IsValid() {
return lpmPfx, val, ok
}
// canonicalize the prefix
pfx = pfx.Masked()
ip := pfx.Addr()
pfxLen := pfx.Bits()
is4 := ip.Is4()
octets := ip.AsSlice()
strideCount, modBits := nodes.DivMod8(pfxLen)
n := f.rootNodeByVersion(is4)
// record path to leaf node
stack := [nodes.MaxTreeDepth]*nodes.FastNode[V]{}
var depth int
var octet byte
LOOP:
// find the last node on the octets path in the trie,
for depth, octet = range octets {
depth &= nodes.DepthMask // BCE
// stepped one past the last stride of interest; back up to last and break
if depth > strideCount {
depth--
break
}
// push current node on stack
stack[depth] = n
// go down in tight loop to leaf node
if !n.Children.Test(octet) {
break LOOP
}
kid := n.MustGetChild(octet)
// kid is node or leaf or fringe at octet
switch kid := kid.(type) {
case *nodes.FastNode[V]:
n = kid
continue LOOP // descend down to next trie level
case *nodes.LeafNode[V]:
// reached a path compressed prefix, stop traversing
if kid.Prefix.Bits() > pfxLen || !kid.Prefix.Contains(ip) {
break LOOP
}
return kid.Prefix, kid.Value, true
case *nodes.FringeNode[V]:
// the bits of the fringe are defined by the depth
// maybe the LPM isn't needed, saves some cycles
fringeBits := (depth + 1) << 3
if fringeBits > pfxLen {
break LOOP
}
// the LPM isn't needed, saves some cycles
if !withLPM {
return netip.Prefix{}, kid.Value, true
}
// get the LPM prefix back from ip and depth
// it's a fringe, bits are always /8, /16, /24, ...
fringePfx, _ := ip.Prefix((depth + 1) << 3)
return fringePfx, kid.Value, true
}
}
// start backtracking, unwind the stack
for ; depth >= 0; depth-- {
depth &= nodes.DepthMask // BCE
n = stack[depth]
// longest prefix match, skip if node has no prefixes
if n.Prefixes.Len() == 0 {
continue
}
var idx uint8
octet = octets[depth]
// only the final stride may have a different prefix len
// all others are just host routes
if depth == strideCount {
idx = art.PfxToIdx(octet, modBits)
} else {
idx = art.OctetToIdx(octet)
}
// manually inlined: lookupIdx(idx)
var topIdx uint8
if topIdx, ok = n.Prefixes.IntersectionTop(&lpm.LookupTbl[idx]); ok {
val = n.MustGetPrefix(topIdx)
// called from LookupPrefix
if !withLPM {
return netip.Prefix{}, val, ok
}
// called from LookupPrefixLPM
// get the bits from depth and top idx
pfxBits := int(art.PfxBits(depth, topIdx))
// calculate the lpmPfx from incoming ip and new mask
// netip.Addr.Prefix canonicalizes. Invariant: art.PfxBits(depth, topIdx)
// yields a valid mask (v4: 0..32, v6: 0..128), so error is impossible.
lpmPfx, _ = ip.Prefix(pfxBits)
return lpmPfx, val, ok
}
}
return lpmPfx, val, ok
}
+731
View File
@@ -0,0 +1,731 @@
// Code generated from file "commonmethods_tmpl.go"; DO NOT EDIT.
// Copyright (c) 2026 Karl Gaissmaier
// SPDX-License-Identifier: MIT
package bart
import (
"bytes"
"encoding/json"
"fmt"
"io"
"iter"
"net/netip"
"slices"
"strings"
"github.com/gaissmai/bart/internal/nodes"
"github.com/gaissmai/bart/internal/value"
)
func (t *Fast[V]) sizeUpdate(is4 bool, delta int) {
if is4 {
t.size4 += delta
return
}
t.size6 += delta
}
// insert adds or updates a prefix-value pair in the routing table.
// If the prefix already exists, its value is updated; otherwise a new entry is created.
// Invalid prefixes are silently ignored.
//
// The prefix is automatically canonicalized using pfx.Masked() to ensure
// consistent behavior regardless of host bits in the input.
func (t *Fast[V]) insert(pfx netip.Prefix, val V) {
if !pfx.IsValid() {
return
}
// canonicalize prefix
pfx = pfx.Masked()
is4 := pfx.Addr().Is4()
n := t.rootNodeByVersion(is4)
if exists := n.Insert(pfx, val, 0); exists {
return
}
// true insert, update size
t.sizeUpdate(is4, 1)
}
// insertPersist is similar to insert but the receiver isn't modified.
//
// All nodes touched during insert are cloned and a new Fast is returned.
// This is not a full [Fast.Clone], all untouched nodes are still referenced
// from both Tables.
//
// If the payload type V contains pointers or needs deep copying,
// implement:
//
// func (v V) Clone() V
//
// The bart package detects this via structural typing and deep‑copies
// values during persistent ops.
//
// Due to cloning overhead this is significantly slower than insert,
// typically taking μsec instead of nsec.
func (t *Fast[V]) insertPersist(pfx netip.Prefix, val V) *Fast[V] {
if !pfx.IsValid() {
return t
}
// canonicalize prefix
pfx = pfx.Masked()
is4 := pfx.Addr().Is4()
// share size counters; root nodes cloned selectively.
pt := &Fast[V]{
size4: t.size4,
size6: t.size6,
}
// Create a cloning function for deep copying values;
// returns nil if V does not provide a Clone() V method.
cloneFn := value.CloneFnFactory[V]()
// Clone root node corresponding to the IP version, for copy-on-write.
n := &pt.root4
if is4 {
pt.root4 = *t.root4.CloneFlat(cloneFn)
pt.root6 = t.root6
} else {
pt.root4 = t.root4
pt.root6 = *t.root6.CloneFlat(cloneFn)
n = &pt.root6
}
if !n.InsertPersist(cloneFn, pfx, val, 0) {
pt.sizeUpdate(is4, 1)
}
return pt
}
// Delete removes the exact prefix pfx from the table in-place.
//
// This is an exact-match operation (no LPM). If pfx exists, the entry is
// removed. If pfx does not exist or pfx is invalid, the table is left unchanged.
//
// The prefix is canonicalized (Masked) before lookup.
func (t *Fast[V]) Delete(pfx netip.Prefix) {
if !pfx.IsValid() {
return
}
// canonicalize prefix
pfx = pfx.Masked()
is4 := pfx.Addr().Is4()
n := t.rootNodeByVersion(is4)
if exists := n.Delete(pfx); exists {
t.sizeUpdate(is4, -1)
}
}
// Get performs an exact-prefix lookup and returns whether the exact
// prefix exists. The prefix is canonicalized (Masked) before lookup.
//
// This is an exact-match operation (no LPM). The prefix must match exactly
// in both address and prefix length to be found. If pfx exists, the
// associated value (zero value for Lite) and found=true is returned.
// If pfx does not exist or pfx is invalid, the zero value for V and
// exists=false is returned.
//
// For longest-prefix-match (LPM) lookups, use Contains(ip), Lookup(ip),
// LookupPrefix(pfx) or LookupPrefixLPM(pfx) instead.
func (t *Fast[V]) Get(pfx netip.Prefix) (val V, exists bool) {
if !pfx.IsValid() {
return val, exists
}
// canonicalize prefix
pfx = pfx.Masked()
is4 := pfx.Addr().Is4()
n := t.rootNodeByVersion(is4)
return n.Get(pfx)
}
// DeletePersist is similar to Delete but does not modify the receiver.
//
// It performs a copy-on-write delete operation, cloning all nodes touched during
// deletion and returning a new Fast reflecting the change.
//
// If the prefix is invalid or doesn't exist, the original table is
// returned unchanged.
//
// If the payload type V contains pointers or requires deep copying,
// it must implement the Clone method for correct cloning.
//
// Due to cloning overhead this is significantly slower than Delete,
// typically taking μsec instead of nsec.
func (t *Fast[V]) DeletePersist(pfx netip.Prefix) *Fast[V] {
if !pfx.IsValid() {
return t
}
// canonicalize prefix
pfx = pfx.Masked()
is4 := pfx.Addr().Is4()
// Preflight check: avoid cloning if prefix doesn't exist
n := t.rootNodeByVersion(is4)
if _, found := n.Get(pfx); !found {
return t
}
// share size counters; root nodes cloned selectively.
pt := &Fast[V]{
size4: t.size4,
size6: t.size6,
}
// Create a cloning function for deep copying values;
// returns nil if V does not provide a Clone() V method.
cloneFn := value.CloneFnFactory[V]()
// Clone root node corresponding to the IP version, for copy-on-write.
if is4 {
pt.root4 = *t.root4.CloneFlat(cloneFn)
pt.root6 = t.root6
n = &pt.root4
} else {
pt.root4 = t.root4
pt.root6 = *t.root6.CloneFlat(cloneFn)
n = &pt.root6
}
if exists := n.DeletePersist(cloneFn, pfx); exists {
pt.sizeUpdate(is4, -1)
}
return pt
}
// ModifyPersist is similar to Modify but the receiver isn't modified and
// a new *Fast is returned.
func (t *Fast[V]) ModifyPersist(pfx netip.Prefix, cb func(_ V, ok bool) (_ V, del bool)) *Fast[V] {
if !pfx.IsValid() {
return t
}
// make a cheap test in front of expensive operation
oldVal, ok := t.Get(pfx)
val := oldVal
// to clone or not to clone ...
cloneFn := value.CloneFnFactory[V]()
if cloneFn != nil && ok {
val = cloneFn(oldVal)
}
newVal, del := cb(val, ok)
switch {
case !ok && del: // no-op
return t
case !ok && !del: // insert
return t.InsertPersist(pfx, newVal)
case ok && !del: // update
return t.InsertPersist(pfx, newVal)
case ok && del: // delete
return t.DeletePersist(pfx)
}
panic("unreachable")
}
// Supernets returns an iterator over all supernet routes that cover the given prefix pfx.
//
// The traversal searches both exact-length and shorter (less specific) prefixes that
// include pfx. Starting from the most specific position in the trie,
// it walks upward through parent nodes and yields any matching entries found at each level.
//
// The iteration order is reverse-CIDR: from longest prefix match (LPM) towards
// least-specific routes.
//
// This can be used to enumerate all covering supernet routes in routing-based
// policy engines, diagnostics tools, or fallback resolution logic.
//
// Example:
//
// for supernet, val := range table.Supernets(netip.MustParsePrefix("192.0.2.128/25")) {
// fmt.Println("Covered by:", supernet, "->", val)
// }
//
// The iteration can be stopped early by breaking from the range loop.
// Returns an empty iterator if the prefix is invalid.
func (t *Fast[V]) Supernets(pfx netip.Prefix) iter.Seq2[netip.Prefix, V] {
return func(yield func(netip.Prefix, V) bool) {
if !pfx.IsValid() {
return
}
// canonicalize the prefix
pfx = pfx.Masked()
is4 := pfx.Addr().Is4()
n := t.rootNodeByVersion(is4)
n.Supernets(pfx, yield)
}
}
// Subnets returns an iterator over all subnets of the given prefix
// in natural CIDR sort order. This includes prefixes of the same length
// (exact match) and longer (more specific) prefixes that are contained
// within the given prefix.
//
// Example:
//
// for sub, val := range table.Subnets(netip.MustParsePrefix("10.0.0.0/8")) {
// fmt.Println("Covered:", sub, "->", val)
// }
//
// The iteration can be stopped early by breaking from the range loop.
// Returns an empty iterator if the prefix is invalid.
func (t *Fast[V]) Subnets(pfx netip.Prefix) iter.Seq2[netip.Prefix, V] {
return func(yield func(netip.Prefix, V) bool) {
if !pfx.IsValid() {
return
}
pfx = pfx.Masked()
is4 := pfx.Addr().Is4()
n := t.rootNodeByVersion(is4)
n.Subnets(pfx, yield)
}
}
// OverlapsPrefix reports whether any prefix in the routing table overlaps with
// the given prefix. Two prefixes overlap if they share any IP addresses.
//
// The check is bidirectional: it returns true if the input prefix is covered by an existing
// route, or if any stored route is itself contained within the input prefix.
//
// Internally, the function normalizes the prefix and descends the relevant trie branch,
// using stride-based logic to identify overlap without performing a full lookup.
//
// This is useful for containment tests, route validation, or policy checks using prefix
// semantics without retrieving exact matches.
func (t *Fast[V]) OverlapsPrefix(pfx netip.Prefix) bool {
if !pfx.IsValid() {
return false
}
// canonicalize the prefix
pfx = pfx.Masked()
is4 := pfx.Addr().Is4()
n := t.rootNodeByVersion(is4)
return n.OverlapsPrefixAtDepth(pfx, 0)
}
// Overlaps reports whether any route in the receiver table overlaps
// with a route in the other table, in either direction.
//
// The overlap check is bidirectional: it returns true if any IP prefix
// in the receiver is covered by the other table, or vice versa.
// This includes partial overlaps, exact matches, and supernet/subnet relationships.
//
// Both IPv4 and IPv6 route trees are compared independently. If either
// tree has overlapping routes, the function returns true.
//
// This is useful for conflict detection, policy enforcement,
// or validating mutually exclusive routing domains.
func (t *Fast[V]) Overlaps(o *Fast[V]) bool {
return t.Overlaps4(o) || t.Overlaps6(o)
}
// Overlaps4 is like [Fast.Overlaps] but for the v4 routing table only.
func (t *Fast[V]) Overlaps4(o *Fast[V]) bool {
if t.size4 == 0 || o.size4 == 0 {
return false
}
return t.root4.Overlaps(&o.root4, 0)
}
// Overlaps6 is like [Fast.Overlaps] but for the v6 routing table only.
func (t *Fast[V]) Overlaps6(o *Fast[V]) bool {
if t.size6 == 0 || o.size6 == 0 {
return false
}
return t.root6.Overlaps(&o.root6, 0)
}
// Union merges another routing table into the receiver table, modifying it in-place.
//
// All prefixes and values from the other table (o) are inserted into the receiver.
// If a duplicate prefix exists in both tables, the value from o replaces the existing entry.
// This duplicate is shallow-copied by default, but if the value type V implements the
// Clone method, the value is deeply cloned before insertion. See also Fast.Clone.
func (t *Fast[V]) Union(o *Fast[V]) {
// panics on nil receiver
_ = t.size4
// panics on nil argument
if o.size4 == 0 && o.size6 == 0 {
return
}
// t is unchanged
if o == t {
return
}
// Create a cloning function for deep copying values;
// returns nil if V does not provide a Clone() V method.
cloneFn := value.CloneFnFactory[V]()
dup4 := t.root4.UnionRec(cloneFn, &o.root4, 0)
dup6 := t.root6.UnionRec(cloneFn, &o.root6, 0)
t.size4 += o.size4 - dup4
t.size6 += o.size6 - dup6
}
// UnionPersist is similar to [Union] but the receiver isn't modified.
//
// All nodes touched during union are cloned and a new *Fast is returned.
// If o is empty, no nodes are touched and the receiver may be
// returned unchanged.
func (t *Fast[V]) UnionPersist(o *Fast[V]) *Fast[V] {
// panics on nil receiver
_ = t.size4
// panics on nil argument
if o.size4 == 0 && o.size6 == 0 {
return t
}
if o == t {
return t
}
// Create a cloning function for deep copying values;
// returns nil if V does not provide a Clone() V method.
cloneFn := value.CloneFnFactory[V]()
// new Fast with root nodes just copied.
pt := &Fast[V]{
root4: t.root4,
root6: t.root6,
//
size4: t.size4,
size6: t.size6,
}
// only clone the root node if there is something to union
if o.size4 != 0 {
pt.root4 = *t.root4.CloneFlat(cloneFn)
}
if o.size6 != 0 {
pt.root6 = *t.root6.CloneFlat(cloneFn)
}
dup4 := pt.root4.UnionRecPersist(cloneFn, &o.root4, 0)
dup6 := pt.root6.UnionRecPersist(cloneFn, &o.root6, 0)
pt.size4 += o.size4 - dup4
pt.size6 += o.size6 - dup6
return pt
}
// Equal checks whether two tables are structurally and semantically equal.
// It ensures both trees (IPv4-based and IPv6-based) have the same sizes and
// recursively compares their root nodes.
//
// If V implements an `Equal(V) bool` method, its custom equality logic is used.
// Otherwise, values are compared directly using the == operator.
//
// Note: If V implements `Equal(V) bool` with a pointer receiver, the Equal
// method should handle nil receivers gracefully.
//
// ATTENTION: If V is not comparable at runtime (such as a slice or map without an `Equal`
// method), a runtime panic will occur.
func (t *Fast[V]) Equal(o *Fast[V]) bool {
if t.size4 != o.size4 || t.size6 != o.size6 {
return false
}
if o == t {
return true
}
return t.root4.EqualRec(&o.root4) && t.root6.EqualRec(&o.root6)
}
// Clone returns a copy of the routing table.
// The payload of type V is shallow copied by default. To enable deep copying,
// implement the following method on your value type:
//
// Clone() V
//
// Example:
//
// type MyValue struct { Data []byte }
// func (v MyValue) Clone() MyValue {
// return MyValue{Data: slices.Clone(v.Data)}
// }
//
// The bart package will automatically detect and use this method via Go's
// structural typing.
//
// Note: If V implements Clone() V with a pointer receiver, the Clone
// method should handle nil receivers gracefully.
func (t *Fast[V]) Clone() *Fast[V] {
c := new(Fast[V])
cloneFn := value.CloneFnFactory[V]()
c.root4 = *t.root4.CloneRec(cloneFn)
c.root6 = *t.root6.CloneRec(cloneFn)
c.size4 = t.size4
c.size6 = t.size6
return c
}
// Size returns the prefix count.
func (t *Fast[V]) Size() int {
return t.size4 + t.size6
}
// Size4 returns the IPv4 prefix count.
func (t *Fast[V]) Size4() int {
return t.size4
}
// Size6 returns the IPv6 prefix count.
func (t *Fast[V]) Size6() int {
return t.size6
}
// All returns an iterator over all prefix–value pairs in the table.
//
// The iteration order is unspecified and may vary between calls; for a stable order,
// use [Fast.AllSorted].
//
// IMPORTANT: Modifying the table during iteration is not allowed,
// as this would interfere with the internal traversal and may corrupt or
// prematurely terminate the iteration.
func (t *Fast[V]) All() iter.Seq2[netip.Prefix, V] {
return func(yield func(netip.Prefix, V) bool) {
_ = t.root4.AllRec(stridePath{}, 0, true, yield) && t.root6.AllRec(stridePath{}, 0, false, yield)
}
}
// All4 is like [Fast.All] but only for the v4 routing table.
func (t *Fast[V]) All4() iter.Seq2[netip.Prefix, V] {
return func(yield func(netip.Prefix, V) bool) {
_ = t.root4.AllRec(stridePath{}, 0, true, yield)
}
}
// All6 is like [Fast.All] but only for the v6 routing table.
func (t *Fast[V]) All6() iter.Seq2[netip.Prefix, V] {
return func(yield func(netip.Prefix, V) bool) {
_ = t.root6.AllRec(stridePath{}, 0, false, yield)
}
}
// AllSorted is like [Fast.All] but the iteration is ordered in canonical
// CIDR prefix sort order.
func (t *Fast[V]) AllSorted() iter.Seq2[netip.Prefix, V] {
return func(yield func(netip.Prefix, V) bool) {
_ = t.root4.AllRecSorted(stridePath{}, 0, true, yield) &&
t.root6.AllRecSorted(stridePath{}, 0, false, yield)
}
}
// AllSorted4 is like [Fast.AllSorted] but only for the v4 routing table.
func (t *Fast[V]) AllSorted4() iter.Seq2[netip.Prefix, V] {
return func(yield func(netip.Prefix, V) bool) {
_ = t.root4.AllRecSorted(stridePath{}, 0, true, yield)
}
}
// AllSorted6 is like [Fast.AllSorted] but only for the v6 routing table.
func (t *Fast[V]) AllSorted6() iter.Seq2[netip.Prefix, V] {
return func(yield func(netip.Prefix, V) bool) {
_ = t.root6.AllRecSorted(stridePath{}, 0, false, yield)
}
}
// Fprint writes a hierarchical tree diagram of the ordered CIDRs
// with default formatted payload V to w.
//
// The order from top to bottom is in ascending order of the prefix address
// and the subtree structure is determined by the CIDRs coverage.
//
// ▼
// ├─ 10.0.0.0/8 (V)
// │ ├─ 10.0.0.0/24 (V)
// │ └─ 10.0.1.0/24 (V)
// ├─ 127.0.0.0/8 (V)
// │ └─ 127.0.0.1/32 (V)
// ├─ 169.254.0.0/16 (V)
// ├─ 172.16.0.0/12 (V)
// └─ 192.168.0.0/16 (V)
// └─ 192.168.1.0/24 (V)
// ▼
// └─ ::/0 (V)
// ├─ ::1/128 (V)
// ├─ 2000::/3 (V)
// │ └─ 2001:db8::/32 (V)
// └─ fe80::/10 (V)
func (t *Fast[V]) Fprint(w io.Writer) error {
if w == nil && t != nil {
return fmt.Errorf("nil writer")
}
// v4
if err := t.fprint(w, true); err != nil {
return err
}
// v6
if err := t.fprint(w, false); err != nil {
return err
}
return nil
}
// fprint is the version dependent adapter to fprintRec.
func (t *Fast[V]) fprint(w io.Writer, is4 bool) error {
n := t.rootNodeByVersion(is4)
if n.IsEmpty() {
return nil
}
if _, err := fmt.Fprint(w, "▼\n"); err != nil {
return err
}
startParent := nodes.TrieItem[V]{
Node: nil,
Idx: 0,
Path: stridePath{},
Is4: is4,
}
return n.FprintRec(w, startParent, "")
}
// MarshalText implements the [encoding.TextMarshaler] interface,
// just a wrapper for [Fast.Fprint].
func (t *Fast[V]) MarshalText() ([]byte, error) {
w := new(bytes.Buffer)
if err := t.Fprint(w); err != nil {
return nil, err
}
return w.Bytes(), nil
}
// MarshalJSON dumps the table into two sorted lists: for ipv4 and ipv6.
// Every root and subnet is an array, not a map, because the order matters.
func (t *Fast[V]) MarshalJSON() ([]byte, error) {
result := struct {
Ipv4 []DumpListNode[V] `json:"ipv4,omitempty"`
Ipv6 []DumpListNode[V] `json:"ipv6,omitempty"`
}{
Ipv4: t.DumpList4(),
Ipv6: t.DumpList6(),
}
buf, err := json.Marshal(result)
if err != nil {
return nil, err
}
return buf, nil
}
// DumpList4 dumps the ipv4 tree into a list of roots and their subnets.
// It can be used to analyze the tree or build the text or JSON serialization.
func (t *Fast[V]) DumpList4() []DumpListNode[V] {
return t.dumpListRec(&t.root4, 0, stridePath{}, 0, true)
}
// DumpList6 dumps the ipv6 tree into a list of roots and their subnets.
// It can be used to analyze the tree or build custom JSON representation.
func (t *Fast[V]) DumpList6() []DumpListNode[V] {
return t.dumpListRec(&t.root6, 0, stridePath{}, 0, false)
}
// dumpListRec, build the data structure rec-descent with the help of directItemsRec.
// anyNode is nodes.BartNode, nodes.FastNode or nodes.LiteNode
func (t *Fast[V]) dumpListRec(anyNode any, parentIdx uint8, path stridePath, depth int, is4 bool) []DumpListNode[V] {
// recursion stop condition
if anyNode == nil {
return nil
}
// the same method is generated for all table types, therefore
// type assert to the smallest needed interface.
// The panic on wrong type assertion is by intention, MUST NOT happen
n := anyNode.(interface {
DirectItemsRec(uint8, stridePath, int, bool) []nodes.TrieItem[V]
})
directItems := n.DirectItemsRec(parentIdx, path, depth, is4)
// sort the items by prefix
slices.SortFunc(directItems, func(a, b nodes.TrieItem[V]) int {
return nodes.CmpPrefix(a.Cidr, b.Cidr)
})
dumpNodes := make([]DumpListNode[V], 0, len(directItems))
for _, item := range directItems {
dumpNodes = append(dumpNodes, DumpListNode[V]{
CIDR: item.Cidr,
Value: item.Val,
// build it rec-descent, item.Node is also from type any
Subnets: t.dumpListRec(item.Node, item.Idx, item.Path, item.Depth, is4),
})
}
return dumpNodes
}
// dumpString is just a wrapper for dump.
func (t *Fast[V]) dumpString() string {
w := new(strings.Builder)
t.dump(w)
return w.String()
}
// dump the table structure and all the nodes to w.
func (t *Fast[V]) dump(w io.Writer) {
if t.size4 > 0 {
stats := t.root4.StatsRec()
fmt.Fprintln(w)
fmt.Fprintf(w, "### IPv4: size(%d), subnodes(%d), prefixes(%d), leaves(%d), fringes(%d)",
t.size4, stats.SubNodes, stats.Prefixes, stats.Leaves, stats.Fringes)
t.root4.DumpRec(w, stridePath{}, 0, true)
}
if t.size6 > 0 {
stats := t.root6.StatsRec()
fmt.Fprintln(w)
fmt.Fprintf(w, "### IPv6: size(%d), subnodes(%d), prefixes(%d), leaves(%d), fringes(%d)",
t.size6, stats.SubNodes, stats.Prefixes, stats.Leaves, stats.Fringes)
t.root6.DumpRec(w, stridePath{}, 0, false)
}
}
+547
View File
@@ -0,0 +1,547 @@
// Code generated by generate_allottbl.go; DO NOT EDIT.
// Copyright (c) 2026 Karl Gaissmaier
// SPDX-License-Identifier: MIT
package allot
import "github.com/gaissmai/bart/internal/bitset"
// PfxRoutesLookupTbl is a lookup table of BitSet256 entries.
//
// It maps a baseIndex (1..255) to a bitset that contains all more
// specific baseIndices that are covered by the prefix at that index.
//
// The idea:
//
// If prefix P has baseIndex idx, then P covers all prefixes in the subtree
// rooted at idx. This table enumerates and encodes those indices.
//
// The table is split:
// - PfxRoutesLookupTbl holds prefixes up to /7
// - FringeRoutesLookupTbl holds /8 prefixes
//
// This structure allows for very fast set inclusion checks using simple bitwise AND.
//
//nolint:gochecknoglobals // Precomputed read‑only table used in hot paths.
var PfxRoutesLookupTbl = [256]bitset.BitSet256{
/* idx: 0 */ {0x0, 0x0, 0x0, 0x0}, // invalid
/* idx: 1 */ {0xfffffffffffffffe, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff}, // [1, 2, 3, 4, 5, ... ]
/* idx: 2 */ {0xffff00ff0f34, 0xffffffff, 0xffffffffffffffff, 0x0}, // [2, 4, 5, 8, 9, ... ]
/* idx: 3 */ {0xffff0000ff00f0c8, 0xffffffff00000000, 0x0, 0xffffffffffffffff}, // [3, 6, 7, 12, 13, ... ]
/* idx: 4 */ {0xff000f0310, 0xffff, 0xffffffff, 0x0}, // [4, 8, 9, 16, 17, ... ]
/* idx: 5 */ {0xff0000f00c20, 0xffff0000, 0xffffffff00000000, 0x0}, // [5, 10, 11, 20, 21, ... ]
/* idx: 6 */ {0xff00000f003040, 0xffff00000000, 0x0, 0xffffffff}, // [6, 12, 13, 24, 25, ... ]
/* idx: 7 */ {0xff000000f000c080, 0xffff000000000000, 0x0, 0xffffffff00000000}, // [7, 14, 15, 28, 29, ... ]
/* idx: 8 */ {0xf00030100, 0xff, 0xffff, 0x0}, // [8, 16, 17, 32, 33, ... ]
/* idx: 9 */ {0xf0000c0200, 0xff00, 0xffff0000, 0x0}, // [9, 18, 19, 36, 37, ... ]
/* idx: 10 */ {0xf0000300400, 0xff0000, 0xffff00000000, 0x0}, // [10, 20, 21, 40, 41, ... ]
/* idx: 11 */ {0xf00000c00800, 0xff000000, 0xffff000000000000, 0x0}, // [11, 22, 23, 44, 45, ... ]
/* idx: 12 */ {0xf000003001000, 0xff00000000, 0x0, 0xffff}, // [12, 24, 25, 48, 49, ... ]
/* idx: 13 */ {0xf000000c002000, 0xff0000000000, 0x0, 0xffff0000}, // [13, 26, 27, 52, 53, ... ]
/* idx: 14 */ {0xf00000030004000, 0xff000000000000, 0x0, 0xffff00000000}, // [14, 28, 29, 56, 57, ... ]
/* idx: 15 */ {0xf0000000c0008000, 0xff00000000000000, 0x0, 0xffff000000000000}, // [15, 30, 31, 60, 61, ... ]
/* idx: 16 */ {0x300010000, 0xf, 0xff, 0x0}, // [16, 32, 33, 64, 65, ... ]
/* idx: 17 */ {0xc00020000, 0xf0, 0xff00, 0x0}, // [17, 34, 35, 68, 69, ... ]
/* idx: 18 */ {0x3000040000, 0xf00, 0xff0000, 0x0}, // [18, 36, 37, 72, 73, ... ]
/* idx: 19 */ {0xc000080000, 0xf000, 0xff000000, 0x0}, // [19, 38, 39, 76, 77, ... ]
/* idx: 20 */ {0x30000100000, 0xf0000, 0xff00000000, 0x0}, // [20, 40, 41, 80, 81, ... ]
/* idx: 21 */ {0xc0000200000, 0xf00000, 0xff0000000000, 0x0}, // [21, 42, 43, 84, 85, ... ]
/* idx: 22 */ {0x300000400000, 0xf000000, 0xff000000000000, 0x0}, // [22, 44, 45, 88, 89, ... ]
/* idx: 23 */ {0xc00000800000, 0xf0000000, 0xff00000000000000, 0x0}, // [23, 46, 47, 92, 93, ... ]
/* idx: 24 */ {0x3000001000000, 0xf00000000, 0x0, 0xff}, // [24, 48, 49, 96, 97, ... ]
/* idx: 25 */ {0xc000002000000, 0xf000000000, 0x0, 0xff00}, // [25, 50, 51, 100, 101, ... ]
/* idx: 26 */ {0x30000004000000, 0xf0000000000, 0x0, 0xff0000}, // [26, 52, 53, 104, 105, ... ]
/* idx: 27 */ {0xc0000008000000, 0xf00000000000, 0x0, 0xff000000}, // [27, 54, 55, 108, 109, ... ]
/* idx: 28 */ {0x300000010000000, 0xf000000000000, 0x0, 0xff00000000}, // [28, 56, 57, 112, 113, ... ]
/* idx: 29 */ {0xc00000020000000, 0xf0000000000000, 0x0, 0xff0000000000}, // [29, 58, 59, 116, 117, ... ]
/* idx: 30 */ {0x3000000040000000, 0xf00000000000000, 0x0, 0xff000000000000}, // [30, 60, 61, 120, 121, ... ]
/* idx: 31 */ {0xc000000080000000, 0xf000000000000000, 0x0, 0xff00000000000000}, // [31, 62, 63, 124, 125, ... ]
/* idx: 32 */ {0x100000000, 0x3, 0xf, 0x0}, // [32 64 65 128 129 130 131]
/* idx: 33 */ {0x200000000, 0xc, 0xf0, 0x0}, // [33 66 67 132 133 134 135]
/* idx: 34 */ {0x400000000, 0x30, 0xf00, 0x0}, // [34 68 69 136 137 138 139]
/* idx: 35 */ {0x800000000, 0xc0, 0xf000, 0x0}, // [35 70 71 140 141 142 143]
/* idx: 36 */ {0x1000000000, 0x300, 0xf0000, 0x0}, // [36 72 73 144 145 146 147]
/* idx: 37 */ {0x2000000000, 0xc00, 0xf00000, 0x0}, // [37 74 75 148 149 150 151]
/* idx: 38 */ {0x4000000000, 0x3000, 0xf000000, 0x0}, // [38 76 77 152 153 154 155]
/* idx: 39 */ {0x8000000000, 0xc000, 0xf0000000, 0x0}, // [39 78 79 156 157 158 159]
/* idx: 40 */ {0x10000000000, 0x30000, 0xf00000000, 0x0}, // [40 80 81 160 161 162 163]
/* idx: 41 */ {0x20000000000, 0xc0000, 0xf000000000, 0x0}, // [41 82 83 164 165 166 167]
/* idx: 42 */ {0x40000000000, 0x300000, 0xf0000000000, 0x0}, // [42 84 85 168 169 170 171]
/* idx: 43 */ {0x80000000000, 0xc00000, 0xf00000000000, 0x0}, // [43 86 87 172 173 174 175]
/* idx: 44 */ {0x100000000000, 0x3000000, 0xf000000000000, 0x0}, // [44 88 89 176 177 178 179]
/* idx: 45 */ {0x200000000000, 0xc000000, 0xf0000000000000, 0x0}, // [45 90 91 180 181 182 183]
/* idx: 46 */ {0x400000000000, 0x30000000, 0xf00000000000000, 0x0}, // [46 92 93 184 185 186 187]
/* idx: 47 */ {0x800000000000, 0xc0000000, 0xf000000000000000, 0x0}, // [47 94 95 188 189 190 191]
/* idx: 48 */ {0x1000000000000, 0x300000000, 0x0, 0xf}, // [48 96 97 192 193 194 195]
/* idx: 49 */ {0x2000000000000, 0xc00000000, 0x0, 0xf0}, // [49 98 99 196 197 198 199]
/* idx: 50 */ {0x4000000000000, 0x3000000000, 0x0, 0xf00}, // [50 100 101 200 201 202 203]
/* idx: 51 */ {0x8000000000000, 0xc000000000, 0x0, 0xf000}, // [51 102 103 204 205 206 207]
/* idx: 52 */ {0x10000000000000, 0x30000000000, 0x0, 0xf0000}, // [52 104 105 208 209 210 211]
/* idx: 53 */ {0x20000000000000, 0xc0000000000, 0x0, 0xf00000}, // [53 106 107 212 213 214 215]
/* idx: 54 */ {0x40000000000000, 0x300000000000, 0x0, 0xf000000}, // [54 108 109 216 217 218 219]
/* idx: 55 */ {0x80000000000000, 0xc00000000000, 0x0, 0xf0000000}, // [55 110 111 220 221 222 223]
/* idx: 56 */ {0x100000000000000, 0x3000000000000, 0x0, 0xf00000000}, // [56 112 113 224 225 226 227]
/* idx: 57 */ {0x200000000000000, 0xc000000000000, 0x0, 0xf000000000}, // [57 114 115 228 229 230 231]
/* idx: 58 */ {0x400000000000000, 0x30000000000000, 0x0, 0xf0000000000}, // [58 116 117 232 233 234 235]
/* idx: 59 */ {0x800000000000000, 0xc0000000000000, 0x0, 0xf00000000000}, // [59 118 119 236 237 238 239]
/* idx: 60 */ {0x1000000000000000, 0x300000000000000, 0x0, 0xf000000000000}, // [60 120 121 240 241 242 243]
/* idx: 61 */ {0x2000000000000000, 0xc00000000000000, 0x0, 0xf0000000000000}, // [61 122 123 244 245 246 247]
/* idx: 62 */ {0x4000000000000000, 0x3000000000000000, 0x0, 0xf00000000000000}, // [62 124 125 248 249 250 251]
/* idx: 63 */ {0x8000000000000000, 0xc000000000000000, 0x0, 0xf000000000000000}, // [63 126 127 252 253 254 255]
/* idx: 64 */ {0x0, 0x1, 0x3, 0x0}, // [64 128 129]
/* idx: 65 */ {0x0, 0x2, 0xc, 0x0}, // [65 130 131]
/* idx: 66 */ {0x0, 0x4, 0x30, 0x0}, // [66 132 133]
/* idx: 67 */ {0x0, 0x8, 0xc0, 0x0}, // [67 134 135]
/* idx: 68 */ {0x0, 0x10, 0x300, 0x0}, // [68 136 137]
/* idx: 69 */ {0x0, 0x20, 0xc00, 0x0}, // [69 138 139]
/* idx: 70 */ {0x0, 0x40, 0x3000, 0x0}, // [70 140 141]
/* idx: 71 */ {0x0, 0x80, 0xc000, 0x0}, // [71 142 143]
/* idx: 72 */ {0x0, 0x100, 0x30000, 0x0}, // [72 144 145]
/* idx: 73 */ {0x0, 0x200, 0xc0000, 0x0}, // [73 146 147]
/* idx: 74 */ {0x0, 0x400, 0x300000, 0x0}, // [74 148 149]
/* idx: 75 */ {0x0, 0x800, 0xc00000, 0x0}, // [75 150 151]
/* idx: 76 */ {0x0, 0x1000, 0x3000000, 0x0}, // [76 152 153]
/* idx: 77 */ {0x0, 0x2000, 0xc000000, 0x0}, // [77 154 155]
/* idx: 78 */ {0x0, 0x4000, 0x30000000, 0x0}, // [78 156 157]
/* idx: 79 */ {0x0, 0x8000, 0xc0000000, 0x0}, // [79 158 159]
/* idx: 80 */ {0x0, 0x10000, 0x300000000, 0x0}, // [80 160 161]
/* idx: 81 */ {0x0, 0x20000, 0xc00000000, 0x0}, // [81 162 163]
/* idx: 82 */ {0x0, 0x40000, 0x3000000000, 0x0}, // [82 164 165]
/* idx: 83 */ {0x0, 0x80000, 0xc000000000, 0x0}, // [83 166 167]
/* idx: 84 */ {0x0, 0x100000, 0x30000000000, 0x0}, // [84 168 169]
/* idx: 85 */ {0x0, 0x200000, 0xc0000000000, 0x0}, // [85 170 171]
/* idx: 86 */ {0x0, 0x400000, 0x300000000000, 0x0}, // [86 172 173]
/* idx: 87 */ {0x0, 0x800000, 0xc00000000000, 0x0}, // [87 174 175]
/* idx: 88 */ {0x0, 0x1000000, 0x3000000000000, 0x0}, // [88 176 177]
/* idx: 89 */ {0x0, 0x2000000, 0xc000000000000, 0x0}, // [89 178 179]
/* idx: 90 */ {0x0, 0x4000000, 0x30000000000000, 0x0}, // [90 180 181]
/* idx: 91 */ {0x0, 0x8000000, 0xc0000000000000, 0x0}, // [91 182 183]
/* idx: 92 */ {0x0, 0x10000000, 0x300000000000000, 0x0}, // [92 184 185]
/* idx: 93 */ {0x0, 0x20000000, 0xc00000000000000, 0x0}, // [93 186 187]
/* idx: 94 */ {0x0, 0x40000000, 0x3000000000000000, 0x0}, // [94 188 189]
/* idx: 95 */ {0x0, 0x80000000, 0xc000000000000000, 0x0}, // [95 190 191]
/* idx: 96 */ {0x0, 0x100000000, 0x0, 0x3}, // [96 192 193]
/* idx: 97 */ {0x0, 0x200000000, 0x0, 0xc}, // [97 194 195]
/* idx: 98 */ {0x0, 0x400000000, 0x0, 0x30}, // [98 196 197]
/* idx: 99 */ {0x0, 0x800000000, 0x0, 0xc0}, // [99 198 199]
/* idx: 100 */ {0x0, 0x1000000000, 0x0, 0x300}, // [100 200 201]
/* idx: 101 */ {0x0, 0x2000000000, 0x0, 0xc00}, // [101 202 203]
/* idx: 102 */ {0x0, 0x4000000000, 0x0, 0x3000}, // [102 204 205]
/* idx: 103 */ {0x0, 0x8000000000, 0x0, 0xc000}, // [103 206 207]
/* idx: 104 */ {0x0, 0x10000000000, 0x0, 0x30000}, // [104 208 209]
/* idx: 105 */ {0x0, 0x20000000000, 0x0, 0xc0000}, // [105 210 211]
/* idx: 106 */ {0x0, 0x40000000000, 0x0, 0x300000}, // [106 212 213]
/* idx: 107 */ {0x0, 0x80000000000, 0x0, 0xc00000}, // [107 214 215]
/* idx: 108 */ {0x0, 0x100000000000, 0x0, 0x3000000}, // [108 216 217]
/* idx: 109 */ {0x0, 0x200000000000, 0x0, 0xc000000}, // [109 218 219]
/* idx: 110 */ {0x0, 0x400000000000, 0x0, 0x30000000}, // [110 220 221]
/* idx: 111 */ {0x0, 0x800000000000, 0x0, 0xc0000000}, // [111 222 223]
/* idx: 112 */ {0x0, 0x1000000000000, 0x0, 0x300000000}, // [112 224 225]
/* idx: 113 */ {0x0, 0x2000000000000, 0x0, 0xc00000000}, // [113 226 227]
/* idx: 114 */ {0x0, 0x4000000000000, 0x0, 0x3000000000}, // [114 228 229]
/* idx: 115 */ {0x0, 0x8000000000000, 0x0, 0xc000000000}, // [115 230 231]
/* idx: 116 */ {0x0, 0x10000000000000, 0x0, 0x30000000000}, // [116 232 233]
/* idx: 117 */ {0x0, 0x20000000000000, 0x0, 0xc0000000000}, // [117 234 235]
/* idx: 118 */ {0x0, 0x40000000000000, 0x0, 0x300000000000}, // [118 236 237]
/* idx: 119 */ {0x0, 0x80000000000000, 0x0, 0xc00000000000}, // [119 238 239]
/* idx: 120 */ {0x0, 0x100000000000000, 0x0, 0x3000000000000}, // [120 240 241]
/* idx: 121 */ {0x0, 0x200000000000000, 0x0, 0xc000000000000}, // [121 242 243]
/* idx: 122 */ {0x0, 0x400000000000000, 0x0, 0x30000000000000}, // [122 244 245]
/* idx: 123 */ {0x0, 0x800000000000000, 0x0, 0xc0000000000000}, // [123 246 247]
/* idx: 124 */ {0x0, 0x1000000000000000, 0x0, 0x300000000000000}, // [124 248 249]
/* idx: 125 */ {0x0, 0x2000000000000000, 0x0, 0xc00000000000000}, // [125 250 251]
/* idx: 126 */ {0x0, 0x4000000000000000, 0x0, 0x3000000000000000}, // [126 252 253]
/* idx: 127 */ {0x0, 0x8000000000000000, 0x0, 0xc000000000000000}, // [127 254 255]
/* idx: 128 */ {0x0, 0x0, 0x1, 0x0}, // [128]
/* idx: 129 */ {0x0, 0x0, 0x2, 0x0}, // [129]
/* idx: 130 */ {0x0, 0x0, 0x4, 0x0}, // [130]
/* idx: 131 */ {0x0, 0x0, 0x8, 0x0}, // [131]
/* idx: 132 */ {0x0, 0x0, 0x10, 0x0}, // [132]
/* idx: 133 */ {0x0, 0x0, 0x20, 0x0}, // [133]
/* idx: 134 */ {0x0, 0x0, 0x40, 0x0}, // [134]
/* idx: 135 */ {0x0, 0x0, 0x80, 0x0}, // [135]
/* idx: 136 */ {0x0, 0x0, 0x100, 0x0}, // [136]
/* idx: 137 */ {0x0, 0x0, 0x200, 0x0}, // [137]
/* idx: 138 */ {0x0, 0x0, 0x400, 0x0}, // [138]
/* idx: 139 */ {0x0, 0x0, 0x800, 0x0}, // [139]
/* idx: 140 */ {0x0, 0x0, 0x1000, 0x0}, // [140]
/* idx: 141 */ {0x0, 0x0, 0x2000, 0x0}, // [141]
/* idx: 142 */ {0x0, 0x0, 0x4000, 0x0}, // [142]
/* idx: 143 */ {0x0, 0x0, 0x8000, 0x0}, // [143]
/* idx: 144 */ {0x0, 0x0, 0x10000, 0x0}, // [144]
/* idx: 145 */ {0x0, 0x0, 0x20000, 0x0}, // [145]
/* idx: 146 */ {0x0, 0x0, 0x40000, 0x0}, // [146]
/* idx: 147 */ {0x0, 0x0, 0x80000, 0x0}, // [147]
/* idx: 148 */ {0x0, 0x0, 0x100000, 0x0}, // [148]
/* idx: 149 */ {0x0, 0x0, 0x200000, 0x0}, // [149]
/* idx: 150 */ {0x0, 0x0, 0x400000, 0x0}, // [150]
/* idx: 151 */ {0x0, 0x0, 0x800000, 0x0}, // [151]
/* idx: 152 */ {0x0, 0x0, 0x1000000, 0x0}, // [152]
/* idx: 153 */ {0x0, 0x0, 0x2000000, 0x0}, // [153]
/* idx: 154 */ {0x0, 0x0, 0x4000000, 0x0}, // [154]
/* idx: 155 */ {0x0, 0x0, 0x8000000, 0x0}, // [155]
/* idx: 156 */ {0x0, 0x0, 0x10000000, 0x0}, // [156]
/* idx: 157 */ {0x0, 0x0, 0x20000000, 0x0}, // [157]
/* idx: 158 */ {0x0, 0x0, 0x40000000, 0x0}, // [158]
/* idx: 159 */ {0x0, 0x0, 0x80000000, 0x0}, // [159]
/* idx: 160 */ {0x0, 0x0, 0x100000000, 0x0}, // [160]
/* idx: 161 */ {0x0, 0x0, 0x200000000, 0x0}, // [161]
/* idx: 162 */ {0x0, 0x0, 0x400000000, 0x0}, // [162]
/* idx: 163 */ {0x0, 0x0, 0x800000000, 0x0}, // [163]
/* idx: 164 */ {0x0, 0x0, 0x1000000000, 0x0}, // [164]
/* idx: 165 */ {0x0, 0x0, 0x2000000000, 0x0}, // [165]
/* idx: 166 */ {0x0, 0x0, 0x4000000000, 0x0}, // [166]
/* idx: 167 */ {0x0, 0x0, 0x8000000000, 0x0}, // [167]
/* idx: 168 */ {0x0, 0x0, 0x10000000000, 0x0}, // [168]
/* idx: 169 */ {0x0, 0x0, 0x20000000000, 0x0}, // [169]
/* idx: 170 */ {0x0, 0x0, 0x40000000000, 0x0}, // [170]
/* idx: 171 */ {0x0, 0x0, 0x80000000000, 0x0}, // [171]
/* idx: 172 */ {0x0, 0x0, 0x100000000000, 0x0}, // [172]
/* idx: 173 */ {0x0, 0x0, 0x200000000000, 0x0}, // [173]
/* idx: 174 */ {0x0, 0x0, 0x400000000000, 0x0}, // [174]
/* idx: 175 */ {0x0, 0x0, 0x800000000000, 0x0}, // [175]
/* idx: 176 */ {0x0, 0x0, 0x1000000000000, 0x0}, // [176]
/* idx: 177 */ {0x0, 0x0, 0x2000000000000, 0x0}, // [177]
/* idx: 178 */ {0x0, 0x0, 0x4000000000000, 0x0}, // [178]
/* idx: 179 */ {0x0, 0x0, 0x8000000000000, 0x0}, // [179]
/* idx: 180 */ {0x0, 0x0, 0x10000000000000, 0x0}, // [180]
/* idx: 181 */ {0x0, 0x0, 0x20000000000000, 0x0}, // [181]
/* idx: 182 */ {0x0, 0x0, 0x40000000000000, 0x0}, // [182]
/* idx: 183 */ {0x0, 0x0, 0x80000000000000, 0x0}, // [183]
/* idx: 184 */ {0x0, 0x0, 0x100000000000000, 0x0}, // [184]
/* idx: 185 */ {0x0, 0x0, 0x200000000000000, 0x0}, // [185]
/* idx: 186 */ {0x0, 0x0, 0x400000000000000, 0x0}, // [186]
/* idx: 187 */ {0x0, 0x0, 0x800000000000000, 0x0}, // [187]
/* idx: 188 */ {0x0, 0x0, 0x1000000000000000, 0x0}, // [188]
/* idx: 189 */ {0x0, 0x0, 0x2000000000000000, 0x0}, // [189]
/* idx: 190 */ {0x0, 0x0, 0x4000000000000000, 0x0}, // [190]
/* idx: 191 */ {0x0, 0x0, 0x8000000000000000, 0x0}, // [191]
/* idx: 192 */ {0x0, 0x0, 0x0, 0x1}, // [192]
/* idx: 193 */ {0x0, 0x0, 0x0, 0x2}, // [193]
/* idx: 194 */ {0x0, 0x0, 0x0, 0x4}, // [194]
/* idx: 195 */ {0x0, 0x0, 0x0, 0x8}, // [195]
/* idx: 196 */ {0x0, 0x0, 0x0, 0x10}, // [196]
/* idx: 197 */ {0x0, 0x0, 0x0, 0x20}, // [197]
/* idx: 198 */ {0x0, 0x0, 0x0, 0x40}, // [198]
/* idx: 199 */ {0x0, 0x0, 0x0, 0x80}, // [199]
/* idx: 200 */ {0x0, 0x0, 0x0, 0x100}, // [200]
/* idx: 201 */ {0x0, 0x0, 0x0, 0x200}, // [201]
/* idx: 202 */ {0x0, 0x0, 0x0, 0x400}, // [202]
/* idx: 203 */ {0x0, 0x0, 0x0, 0x800}, // [203]
/* idx: 204 */ {0x0, 0x0, 0x0, 0x1000}, // [204]
/* idx: 205 */ {0x0, 0x0, 0x0, 0x2000}, // [205]
/* idx: 206 */ {0x0, 0x0, 0x0, 0x4000}, // [206]
/* idx: 207 */ {0x0, 0x0, 0x0, 0x8000}, // [207]
/* idx: 208 */ {0x0, 0x0, 0x0, 0x10000}, // [208]
/* idx: 209 */ {0x0, 0x0, 0x0, 0x20000}, // [209]
/* idx: 210 */ {0x0, 0x0, 0x0, 0x40000}, // [210]
/* idx: 211 */ {0x0, 0x0, 0x0, 0x80000}, // [211]
/* idx: 212 */ {0x0, 0x0, 0x0, 0x100000}, // [212]
/* idx: 213 */ {0x0, 0x0, 0x0, 0x200000}, // [213]
/* idx: 214 */ {0x0, 0x0, 0x0, 0x400000}, // [214]
/* idx: 215 */ {0x0, 0x0, 0x0, 0x800000}, // [215]
/* idx: 216 */ {0x0, 0x0, 0x0, 0x1000000}, // [216]
/* idx: 217 */ {0x0, 0x0, 0x0, 0x2000000}, // [217]
/* idx: 218 */ {0x0, 0x0, 0x0, 0x4000000}, // [218]
/* idx: 219 */ {0x0, 0x0, 0x0, 0x8000000}, // [219]
/* idx: 220 */ {0x0, 0x0, 0x0, 0x10000000}, // [220]
/* idx: 221 */ {0x0, 0x0, 0x0, 0x20000000}, // [221]
/* idx: 222 */ {0x0, 0x0, 0x0, 0x40000000}, // [222]
/* idx: 223 */ {0x0, 0x0, 0x0, 0x80000000}, // [223]
/* idx: 224 */ {0x0, 0x0, 0x0, 0x100000000}, // [224]
/* idx: 225 */ {0x0, 0x0, 0x0, 0x200000000}, // [225]
/* idx: 226 */ {0x0, 0x0, 0x0, 0x400000000}, // [226]
/* idx: 227 */ {0x0, 0x0, 0x0, 0x800000000}, // [227]
/* idx: 228 */ {0x0, 0x0, 0x0, 0x1000000000}, // [228]
/* idx: 229 */ {0x0, 0x0, 0x0, 0x2000000000}, // [229]
/* idx: 230 */ {0x0, 0x0, 0x0, 0x4000000000}, // [230]
/* idx: 231 */ {0x0, 0x0, 0x0, 0x8000000000}, // [231]
/* idx: 232 */ {0x0, 0x0, 0x0, 0x10000000000}, // [232]
/* idx: 233 */ {0x0, 0x0, 0x0, 0x20000000000}, // [233]
/* idx: 234 */ {0x0, 0x0, 0x0, 0x40000000000}, // [234]
/* idx: 235 */ {0x0, 0x0, 0x0, 0x80000000000}, // [235]
/* idx: 236 */ {0x0, 0x0, 0x0, 0x100000000000}, // [236]
/* idx: 237 */ {0x0, 0x0, 0x0, 0x200000000000}, // [237]
/* idx: 238 */ {0x0, 0x0, 0x0, 0x400000000000}, // [238]
/* idx: 239 */ {0x0, 0x0, 0x0, 0x800000000000}, // [239]
/* idx: 240 */ {0x0, 0x0, 0x0, 0x1000000000000}, // [240]
/* idx: 241 */ {0x0, 0x0, 0x0, 0x2000000000000}, // [241]
/* idx: 242 */ {0x0, 0x0, 0x0, 0x4000000000000}, // [242]
/* idx: 243 */ {0x0, 0x0, 0x0, 0x8000000000000}, // [243]
/* idx: 244 */ {0x0, 0x0, 0x0, 0x10000000000000}, // [244]
/* idx: 245 */ {0x0, 0x0, 0x0, 0x20000000000000}, // [245]
/* idx: 246 */ {0x0, 0x0, 0x0, 0x40000000000000}, // [246]
/* idx: 247 */ {0x0, 0x0, 0x0, 0x80000000000000}, // [247]
/* idx: 248 */ {0x0, 0x0, 0x0, 0x100000000000000}, // [248]
/* idx: 249 */ {0x0, 0x0, 0x0, 0x200000000000000}, // [249]
/* idx: 250 */ {0x0, 0x0, 0x0, 0x400000000000000}, // [250]
/* idx: 251 */ {0x0, 0x0, 0x0, 0x800000000000000}, // [251]
/* idx: 252 */ {0x0, 0x0, 0x0, 0x1000000000000000}, // [252]
/* idx: 253 */ {0x0, 0x0, 0x0, 0x2000000000000000}, // [253]
/* idx: 254 */ {0x0, 0x0, 0x0, 0x4000000000000000}, // [254]
/* idx: 255 */ {0x0, 0x0, 0x0, 0x8000000000000000}, // [255]
}
// FringeRoutesLookupTbl, the second 256 Bits, see also the PfxRoutesLookupTbl for the first 256 Bits
// we split 512 bits into 2×256 to leverage BitSet256 optimizations.
//
//nolint:gochecknoglobals // Precomputed read‑only table used in hot paths.
var FringeRoutesLookupTbl = [256]bitset.BitSet256{
/* idx: 0 */ {0x0, 0x0, 0x0, 0x0}, // invalid
/* idx: 1 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff}, // [0, 1, 2, 3, 4, ... ]
/* idx: 2 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x0, 0x0}, // [0, 1, 2, 3, 4, ... ]
/* idx: 3 */ {0x0, 0x0, 0xffffffffffffffff, 0xffffffffffffffff}, // [128, 129, 130, 131, 132, ... ]
/* idx: 4 */ {0xffffffffffffffff, 0x0, 0x0, 0x0}, // [0, 1, 2, 3, 4, ... ]
/* idx: 5 */ {0x0, 0xffffffffffffffff, 0x0, 0x0}, // [64, 65, 66, 67, 68, ... ]
/* idx: 6 */ {0x0, 0x0, 0xffffffffffffffff, 0x0}, // [128, 129, 130, 131, 132, ... ]
/* idx: 7 */ {0x0, 0x0, 0x0, 0xffffffffffffffff}, // [192, 193, 194, 195, 196, ... ]
/* idx: 8 */ {0xffffffff, 0x0, 0x0, 0x0}, // [0, 1, 2, 3, 4, ... ]
/* idx: 9 */ {0xffffffff00000000, 0x0, 0x0, 0x0}, // [32, 33, 34, 35, 36, ... ]
/* idx: 10 */ {0x0, 0xffffffff, 0x0, 0x0}, // [64, 65, 66, 67, 68, ... ]
/* idx: 11 */ {0x0, 0xffffffff00000000, 0x0, 0x0}, // [96, 97, 98, 99, 100, ... ]
/* idx: 12 */ {0x0, 0x0, 0xffffffff, 0x0}, // [128, 129, 130, 131, 132, ... ]
/* idx: 13 */ {0x0, 0x0, 0xffffffff00000000, 0x0}, // [160, 161, 162, 163, 164, ... ]
/* idx: 14 */ {0x0, 0x0, 0x0, 0xffffffff}, // [192, 193, 194, 195, 196, ... ]
/* idx: 15 */ {0x0, 0x0, 0x0, 0xffffffff00000000}, // [224, 225, 226, 227, 228, ... ]
/* idx: 16 */ {0xffff, 0x0, 0x0, 0x0}, // [0, 1, 2, 3, 4, ... ]
/* idx: 17 */ {0xffff0000, 0x0, 0x0, 0x0}, // [16, 17, 18, 19, 20, ... ]
/* idx: 18 */ {0xffff00000000, 0x0, 0x0, 0x0}, // [32, 33, 34, 35, 36, ... ]
/* idx: 19 */ {0xffff000000000000, 0x0, 0x0, 0x0}, // [48, 49, 50, 51, 52, ... ]
/* idx: 20 */ {0x0, 0xffff, 0x0, 0x0}, // [64, 65, 66, 67, 68, ... ]
/* idx: 21 */ {0x0, 0xffff0000, 0x0, 0x0}, // [80, 81, 82, 83, 84, ... ]
/* idx: 22 */ {0x0, 0xffff00000000, 0x0, 0x0}, // [96, 97, 98, 99, 100, ... ]
/* idx: 23 */ {0x0, 0xffff000000000000, 0x0, 0x0}, // [112, 113, 114, 115, 116, ... ]
/* idx: 24 */ {0x0, 0x0, 0xffff, 0x0}, // [128, 129, 130, 131, 132, ... ]
/* idx: 25 */ {0x0, 0x0, 0xffff0000, 0x0}, // [144, 145, 146, 147, 148, ... ]
/* idx: 26 */ {0x0, 0x0, 0xffff00000000, 0x0}, // [160, 161, 162, 163, 164, ... ]
/* idx: 27 */ {0x0, 0x0, 0xffff000000000000, 0x0}, // [176, 177, 178, 179, 180, ... ]
/* idx: 28 */ {0x0, 0x0, 0x0, 0xffff}, // [192, 193, 194, 195, 196, ... ]
/* idx: 29 */ {0x0, 0x0, 0x0, 0xffff0000}, // [208, 209, 210, 211, 212, ... ]
/* idx: 30 */ {0x0, 0x0, 0x0, 0xffff00000000}, // [224, 225, 226, 227, 228, ... ]
/* idx: 31 */ {0x0, 0x0, 0x0, 0xffff000000000000}, // [240, 241, 242, 243, 244, ... ]
/* idx: 32 */ {0xff, 0x0, 0x0, 0x0}, // [0 1 2 3 4 5 6 7]
/* idx: 33 */ {0xff00, 0x0, 0x0, 0x0}, // [8 9 10 11 12 13 14 15]
/* idx: 34 */ {0xff0000, 0x0, 0x0, 0x0}, // [16 17 18 19 20 21 22 23]
/* idx: 35 */ {0xff000000, 0x0, 0x0, 0x0}, // [24 25 26 27 28 29 30 31]
/* idx: 36 */ {0xff00000000, 0x0, 0x0, 0x0}, // [32 33 34 35 36 37 38 39]
/* idx: 37 */ {0xff0000000000, 0x0, 0x0, 0x0}, // [40 41 42 43 44 45 46 47]
/* idx: 38 */ {0xff000000000000, 0x0, 0x0, 0x0}, // [48 49 50 51 52 53 54 55]
/* idx: 39 */ {0xff00000000000000, 0x0, 0x0, 0x0}, // [56 57 58 59 60 61 62 63]
/* idx: 40 */ {0x0, 0xff, 0x0, 0x0}, // [64 65 66 67 68 69 70 71]
/* idx: 41 */ {0x0, 0xff00, 0x0, 0x0}, // [72 73 74 75 76 77 78 79]
/* idx: 42 */ {0x0, 0xff0000, 0x0, 0x0}, // [80 81 82 83 84 85 86 87]
/* idx: 43 */ {0x0, 0xff000000, 0x0, 0x0}, // [88 89 90 91 92 93 94 95]
/* idx: 44 */ {0x0, 0xff00000000, 0x0, 0x0}, // [96 97 98 99 100 101 102 103]
/* idx: 45 */ {0x0, 0xff0000000000, 0x0, 0x0}, // [104 105 106 107 108 109 110 111]
/* idx: 46 */ {0x0, 0xff000000000000, 0x0, 0x0}, // [112 113 114 115 116 117 118 119]
/* idx: 47 */ {0x0, 0xff00000000000000, 0x0, 0x0}, // [120 121 122 123 124 125 126 127]
/* idx: 48 */ {0x0, 0x0, 0xff, 0x0}, // [128 129 130 131 132 133 134 135]
/* idx: 49 */ {0x0, 0x0, 0xff00, 0x0}, // [136 137 138 139 140 141 142 143]
/* idx: 50 */ {0x0, 0x0, 0xff0000, 0x0}, // [144 145 146 147 148 149 150 151]
/* idx: 51 */ {0x0, 0x0, 0xff000000, 0x0}, // [152 153 154 155 156 157 158 159]
/* idx: 52 */ {0x0, 0x0, 0xff00000000, 0x0}, // [160 161 162 163 164 165 166 167]
/* idx: 53 */ {0x0, 0x0, 0xff0000000000, 0x0}, // [168 169 170 171 172 173 174 175]
/* idx: 54 */ {0x0, 0x0, 0xff000000000000, 0x0}, // [176 177 178 179 180 181 182 183]
/* idx: 55 */ {0x0, 0x0, 0xff00000000000000, 0x0}, // [184 185 186 187 188 189 190 191]
/* idx: 56 */ {0x0, 0x0, 0x0, 0xff}, // [192 193 194 195 196 197 198 199]
/* idx: 57 */ {0x0, 0x0, 0x0, 0xff00}, // [200 201 202 203 204 205 206 207]
/* idx: 58 */ {0x0, 0x0, 0x0, 0xff0000}, // [208 209 210 211 212 213 214 215]
/* idx: 59 */ {0x0, 0x0, 0x0, 0xff000000}, // [216 217 218 219 220 221 222 223]
/* idx: 60 */ {0x0, 0x0, 0x0, 0xff00000000}, // [224 225 226 227 228 229 230 231]
/* idx: 61 */ {0x0, 0x0, 0x0, 0xff0000000000}, // [232 233 234 235 236 237 238 239]
/* idx: 62 */ {0x0, 0x0, 0x0, 0xff000000000000}, // [240 241 242 243 244 245 246 247]
/* idx: 63 */ {0x0, 0x0, 0x0, 0xff00000000000000}, // [248 249 250 251 252 253 254 255]
/* idx: 64 */ {0xf, 0x0, 0x0, 0x0}, // [0 1 2 3]
/* idx: 65 */ {0xf0, 0x0, 0x0, 0x0}, // [4 5 6 7]
/* idx: 66 */ {0xf00, 0x0, 0x0, 0x0}, // [8 9 10 11]
/* idx: 67 */ {0xf000, 0x0, 0x0, 0x0}, // [12 13 14 15]
/* idx: 68 */ {0xf0000, 0x0, 0x0, 0x0}, // [16 17 18 19]
/* idx: 69 */ {0xf00000, 0x0, 0x0, 0x0}, // [20 21 22 23]
/* idx: 70 */ {0xf000000, 0x0, 0x0, 0x0}, // [24 25 26 27]
/* idx: 71 */ {0xf0000000, 0x0, 0x0, 0x0}, // [28 29 30 31]
/* idx: 72 */ {0xf00000000, 0x0, 0x0, 0x0}, // [32 33 34 35]
/* idx: 73 */ {0xf000000000, 0x0, 0x0, 0x0}, // [36 37 38 39]
/* idx: 74 */ {0xf0000000000, 0x0, 0x0, 0x0}, // [40 41 42 43]
/* idx: 75 */ {0xf00000000000, 0x0, 0x0, 0x0}, // [44 45 46 47]
/* idx: 76 */ {0xf000000000000, 0x0, 0x0, 0x0}, // [48 49 50 51]
/* idx: 77 */ {0xf0000000000000, 0x0, 0x0, 0x0}, // [52 53 54 55]
/* idx: 78 */ {0xf00000000000000, 0x0, 0x0, 0x0}, // [56 57 58 59]
/* idx: 79 */ {0xf000000000000000, 0x0, 0x0, 0x0}, // [60 61 62 63]
/* idx: 80 */ {0x0, 0xf, 0x0, 0x0}, // [64 65 66 67]
/* idx: 81 */ {0x0, 0xf0, 0x0, 0x0}, // [68 69 70 71]
/* idx: 82 */ {0x0, 0xf00, 0x0, 0x0}, // [72 73 74 75]
/* idx: 83 */ {0x0, 0xf000, 0x0, 0x0}, // [76 77 78 79]
/* idx: 84 */ {0x0, 0xf0000, 0x0, 0x0}, // [80 81 82 83]
/* idx: 85 */ {0x0, 0xf00000, 0x0, 0x0}, // [84 85 86 87]
/* idx: 86 */ {0x0, 0xf000000, 0x0, 0x0}, // [88 89 90 91]
/* idx: 87 */ {0x0, 0xf0000000, 0x0, 0x0}, // [92 93 94 95]
/* idx: 88 */ {0x0, 0xf00000000, 0x0, 0x0}, // [96 97 98 99]
/* idx: 89 */ {0x0, 0xf000000000, 0x0, 0x0}, // [100 101 102 103]
/* idx: 90 */ {0x0, 0xf0000000000, 0x0, 0x0}, // [104 105 106 107]
/* idx: 91 */ {0x0, 0xf00000000000, 0x0, 0x0}, // [108 109 110 111]
/* idx: 92 */ {0x0, 0xf000000000000, 0x0, 0x0}, // [112 113 114 115]
/* idx: 93 */ {0x0, 0xf0000000000000, 0x0, 0x0}, // [116 117 118 119]
/* idx: 94 */ {0x0, 0xf00000000000000, 0x0, 0x0}, // [120 121 122 123]
/* idx: 95 */ {0x0, 0xf000000000000000, 0x0, 0x0}, // [124 125 126 127]
/* idx: 96 */ {0x0, 0x0, 0xf, 0x0}, // [128 129 130 131]
/* idx: 97 */ {0x0, 0x0, 0xf0, 0x0}, // [132 133 134 135]
/* idx: 98 */ {0x0, 0x0, 0xf00, 0x0}, // [136 137 138 139]
/* idx: 99 */ {0x0, 0x0, 0xf000, 0x0}, // [140 141 142 143]
/* idx: 100 */ {0x0, 0x0, 0xf0000, 0x0}, // [144 145 146 147]
/* idx: 101 */ {0x0, 0x0, 0xf00000, 0x0}, // [148 149 150 151]
/* idx: 102 */ {0x0, 0x0, 0xf000000, 0x0}, // [152 153 154 155]
/* idx: 103 */ {0x0, 0x0, 0xf0000000, 0x0}, // [156 157 158 159]
/* idx: 104 */ {0x0, 0x0, 0xf00000000, 0x0}, // [160 161 162 163]
/* idx: 105 */ {0x0, 0x0, 0xf000000000, 0x0}, // [164 165 166 167]
/* idx: 106 */ {0x0, 0x0, 0xf0000000000, 0x0}, // [168 169 170 171]
/* idx: 107 */ {0x0, 0x0, 0xf00000000000, 0x0}, // [172 173 174 175]
/* idx: 108 */ {0x0, 0x0, 0xf000000000000, 0x0}, // [176 177 178 179]
/* idx: 109 */ {0x0, 0x0, 0xf0000000000000, 0x0}, // [180 181 182 183]
/* idx: 110 */ {0x0, 0x0, 0xf00000000000000, 0x0}, // [184 185 186 187]
/* idx: 111 */ {0x0, 0x0, 0xf000000000000000, 0x0}, // [188 189 190 191]
/* idx: 112 */ {0x0, 0x0, 0x0, 0xf}, // [192 193 194 195]
/* idx: 113 */ {0x0, 0x0, 0x0, 0xf0}, // [196 197 198 199]
/* idx: 114 */ {0x0, 0x0, 0x0, 0xf00}, // [200 201 202 203]
/* idx: 115 */ {0x0, 0x0, 0x0, 0xf000}, // [204 205 206 207]
/* idx: 116 */ {0x0, 0x0, 0x0, 0xf0000}, // [208 209 210 211]
/* idx: 117 */ {0x0, 0x0, 0x0, 0xf00000}, // [212 213 214 215]
/* idx: 118 */ {0x0, 0x0, 0x0, 0xf000000}, // [216 217 218 219]
/* idx: 119 */ {0x0, 0x0, 0x0, 0xf0000000}, // [220 221 222 223]
/* idx: 120 */ {0x0, 0x0, 0x0, 0xf00000000}, // [224 225 226 227]
/* idx: 121 */ {0x0, 0x0, 0x0, 0xf000000000}, // [228 229 230 231]
/* idx: 122 */ {0x0, 0x0, 0x0, 0xf0000000000}, // [232 233 234 235]
/* idx: 123 */ {0x0, 0x0, 0x0, 0xf00000000000}, // [236 237 238 239]
/* idx: 124 */ {0x0, 0x0, 0x0, 0xf000000000000}, // [240 241 242 243]
/* idx: 125 */ {0x0, 0x0, 0x0, 0xf0000000000000}, // [244 245 246 247]
/* idx: 126 */ {0x0, 0x0, 0x0, 0xf00000000000000}, // [248 249 250 251]
/* idx: 127 */ {0x0, 0x0, 0x0, 0xf000000000000000}, // [252 253 254 255]
/* idx: 128 */ {0x3, 0x0, 0x0, 0x0}, // [0 1]
/* idx: 129 */ {0xc, 0x0, 0x0, 0x0}, // [2 3]
/* idx: 130 */ {0x30, 0x0, 0x0, 0x0}, // [4 5]
/* idx: 131 */ {0xc0, 0x0, 0x0, 0x0}, // [6 7]
/* idx: 132 */ {0x300, 0x0, 0x0, 0x0}, // [8 9]
/* idx: 133 */ {0xc00, 0x0, 0x0, 0x0}, // [10 11]
/* idx: 134 */ {0x3000, 0x0, 0x0, 0x0}, // [12 13]
/* idx: 135 */ {0xc000, 0x0, 0x0, 0x0}, // [14 15]
/* idx: 136 */ {0x30000, 0x0, 0x0, 0x0}, // [16 17]
/* idx: 137 */ {0xc0000, 0x0, 0x0, 0x0}, // [18 19]
/* idx: 138 */ {0x300000, 0x0, 0x0, 0x0}, // [20 21]
/* idx: 139 */ {0xc00000, 0x0, 0x0, 0x0}, // [22 23]
/* idx: 140 */ {0x3000000, 0x0, 0x0, 0x0}, // [24 25]
/* idx: 141 */ {0xc000000, 0x0, 0x0, 0x0}, // [26 27]
/* idx: 142 */ {0x30000000, 0x0, 0x0, 0x0}, // [28 29]
/* idx: 143 */ {0xc0000000, 0x0, 0x0, 0x0}, // [30 31]
/* idx: 144 */ {0x300000000, 0x0, 0x0, 0x0}, // [32 33]
/* idx: 145 */ {0xc00000000, 0x0, 0x0, 0x0}, // [34 35]
/* idx: 146 */ {0x3000000000, 0x0, 0x0, 0x0}, // [36 37]
/* idx: 147 */ {0xc000000000, 0x0, 0x0, 0x0}, // [38 39]
/* idx: 148 */ {0x30000000000, 0x0, 0x0, 0x0}, // [40 41]
/* idx: 149 */ {0xc0000000000, 0x0, 0x0, 0x0}, // [42 43]
/* idx: 150 */ {0x300000000000, 0x0, 0x0, 0x0}, // [44 45]
/* idx: 151 */ {0xc00000000000, 0x0, 0x0, 0x0}, // [46 47]
/* idx: 152 */ {0x3000000000000, 0x0, 0x0, 0x0}, // [48 49]
/* idx: 153 */ {0xc000000000000, 0x0, 0x0, 0x0}, // [50 51]
/* idx: 154 */ {0x30000000000000, 0x0, 0x0, 0x0}, // [52 53]
/* idx: 155 */ {0xc0000000000000, 0x0, 0x0, 0x0}, // [54 55]
/* idx: 156 */ {0x300000000000000, 0x0, 0x0, 0x0}, // [56 57]
/* idx: 157 */ {0xc00000000000000, 0x0, 0x0, 0x0}, // [58 59]
/* idx: 158 */ {0x3000000000000000, 0x0, 0x0, 0x0}, // [60 61]
/* idx: 159 */ {0xc000000000000000, 0x0, 0x0, 0x0}, // [62 63]
/* idx: 160 */ {0x0, 0x3, 0x0, 0x0}, // [64 65]
/* idx: 161 */ {0x0, 0xc, 0x0, 0x0}, // [66 67]
/* idx: 162 */ {0x0, 0x30, 0x0, 0x0}, // [68 69]
/* idx: 163 */ {0x0, 0xc0, 0x0, 0x0}, // [70 71]
/* idx: 164 */ {0x0, 0x300, 0x0, 0x0}, // [72 73]
/* idx: 165 */ {0x0, 0xc00, 0x0, 0x0}, // [74 75]
/* idx: 166 */ {0x0, 0x3000, 0x0, 0x0}, // [76 77]
/* idx: 167 */ {0x0, 0xc000, 0x0, 0x0}, // [78 79]
/* idx: 168 */ {0x0, 0x30000, 0x0, 0x0}, // [80 81]
/* idx: 169 */ {0x0, 0xc0000, 0x0, 0x0}, // [82 83]
/* idx: 170 */ {0x0, 0x300000, 0x0, 0x0}, // [84 85]
/* idx: 171 */ {0x0, 0xc00000, 0x0, 0x0}, // [86 87]
/* idx: 172 */ {0x0, 0x3000000, 0x0, 0x0}, // [88 89]
/* idx: 173 */ {0x0, 0xc000000, 0x0, 0x0}, // [90 91]
/* idx: 174 */ {0x0, 0x30000000, 0x0, 0x0}, // [92 93]
/* idx: 175 */ {0x0, 0xc0000000, 0x0, 0x0}, // [94 95]
/* idx: 176 */ {0x0, 0x300000000, 0x0, 0x0}, // [96 97]
/* idx: 177 */ {0x0, 0xc00000000, 0x0, 0x0}, // [98 99]
/* idx: 178 */ {0x0, 0x3000000000, 0x0, 0x0}, // [100 101]
/* idx: 179 */ {0x0, 0xc000000000, 0x0, 0x0}, // [102 103]
/* idx: 180 */ {0x0, 0x30000000000, 0x0, 0x0}, // [104 105]
/* idx: 181 */ {0x0, 0xc0000000000, 0x0, 0x0}, // [106 107]
/* idx: 182 */ {0x0, 0x300000000000, 0x0, 0x0}, // [108 109]
/* idx: 183 */ {0x0, 0xc00000000000, 0x0, 0x0}, // [110 111]
/* idx: 184 */ {0x0, 0x3000000000000, 0x0, 0x0}, // [112 113]
/* idx: 185 */ {0x0, 0xc000000000000, 0x0, 0x0}, // [114 115]
/* idx: 186 */ {0x0, 0x30000000000000, 0x0, 0x0}, // [116 117]
/* idx: 187 */ {0x0, 0xc0000000000000, 0x0, 0x0}, // [118 119]
/* idx: 188 */ {0x0, 0x300000000000000, 0x0, 0x0}, // [120 121]
/* idx: 189 */ {0x0, 0xc00000000000000, 0x0, 0x0}, // [122 123]
/* idx: 190 */ {0x0, 0x3000000000000000, 0x0, 0x0}, // [124 125]
/* idx: 191 */ {0x0, 0xc000000000000000, 0x0, 0x0}, // [126 127]
/* idx: 192 */ {0x0, 0x0, 0x3, 0x0}, // [128 129]
/* idx: 193 */ {0x0, 0x0, 0xc, 0x0}, // [130 131]
/* idx: 194 */ {0x0, 0x0, 0x30, 0x0}, // [132 133]
/* idx: 195 */ {0x0, 0x0, 0xc0, 0x0}, // [134 135]
/* idx: 196 */ {0x0, 0x0, 0x300, 0x0}, // [136 137]
/* idx: 197 */ {0x0, 0x0, 0xc00, 0x0}, // [138 139]
/* idx: 198 */ {0x0, 0x0, 0x3000, 0x0}, // [140 141]
/* idx: 199 */ {0x0, 0x0, 0xc000, 0x0}, // [142 143]
/* idx: 200 */ {0x0, 0x0, 0x30000, 0x0}, // [144 145]
/* idx: 201 */ {0x0, 0x0, 0xc0000, 0x0}, // [146 147]
/* idx: 202 */ {0x0, 0x0, 0x300000, 0x0}, // [148 149]
/* idx: 203 */ {0x0, 0x0, 0xc00000, 0x0}, // [150 151]
/* idx: 204 */ {0x0, 0x0, 0x3000000, 0x0}, // [152 153]
/* idx: 205 */ {0x0, 0x0, 0xc000000, 0x0}, // [154 155]
/* idx: 206 */ {0x0, 0x0, 0x30000000, 0x0}, // [156 157]
/* idx: 207 */ {0x0, 0x0, 0xc0000000, 0x0}, // [158 159]
/* idx: 208 */ {0x0, 0x0, 0x300000000, 0x0}, // [160 161]
/* idx: 209 */ {0x0, 0x0, 0xc00000000, 0x0}, // [162 163]
/* idx: 210 */ {0x0, 0x0, 0x3000000000, 0x0}, // [164 165]
/* idx: 211 */ {0x0, 0x0, 0xc000000000, 0x0}, // [166 167]
/* idx: 212 */ {0x0, 0x0, 0x30000000000, 0x0}, // [168 169]
/* idx: 213 */ {0x0, 0x0, 0xc0000000000, 0x0}, // [170 171]
/* idx: 214 */ {0x0, 0x0, 0x300000000000, 0x0}, // [172 173]
/* idx: 215 */ {0x0, 0x0, 0xc00000000000, 0x0}, // [174 175]
/* idx: 216 */ {0x0, 0x0, 0x3000000000000, 0x0}, // [176 177]
/* idx: 217 */ {0x0, 0x0, 0xc000000000000, 0x0}, // [178 179]
/* idx: 218 */ {0x0, 0x0, 0x30000000000000, 0x0}, // [180 181]
/* idx: 219 */ {0x0, 0x0, 0xc0000000000000, 0x0}, // [182 183]
/* idx: 220 */ {0x0, 0x0, 0x300000000000000, 0x0}, // [184 185]
/* idx: 221 */ {0x0, 0x0, 0xc00000000000000, 0x0}, // [186 187]
/* idx: 222 */ {0x0, 0x0, 0x3000000000000000, 0x0}, // [188 189]
/* idx: 223 */ {0x0, 0x0, 0xc000000000000000, 0x0}, // [190 191]
/* idx: 224 */ {0x0, 0x0, 0x0, 0x3}, // [192 193]
/* idx: 225 */ {0x0, 0x0, 0x0, 0xc}, // [194 195]
/* idx: 226 */ {0x0, 0x0, 0x0, 0x30}, // [196 197]
/* idx: 227 */ {0x0, 0x0, 0x0, 0xc0}, // [198 199]
/* idx: 228 */ {0x0, 0x0, 0x0, 0x300}, // [200 201]
/* idx: 229 */ {0x0, 0x0, 0x0, 0xc00}, // [202 203]
/* idx: 230 */ {0x0, 0x0, 0x0, 0x3000}, // [204 205]
/* idx: 231 */ {0x0, 0x0, 0x0, 0xc000}, // [206 207]
/* idx: 232 */ {0x0, 0x0, 0x0, 0x30000}, // [208 209]
/* idx: 233 */ {0x0, 0x0, 0x0, 0xc0000}, // [210 211]
/* idx: 234 */ {0x0, 0x0, 0x0, 0x300000}, // [212 213]
/* idx: 235 */ {0x0, 0x0, 0x0, 0xc00000}, // [214 215]
/* idx: 236 */ {0x0, 0x0, 0x0, 0x3000000}, // [216 217]
/* idx: 237 */ {0x0, 0x0, 0x0, 0xc000000}, // [218 219]
/* idx: 238 */ {0x0, 0x0, 0x0, 0x30000000}, // [220 221]
/* idx: 239 */ {0x0, 0x0, 0x0, 0xc0000000}, // [222 223]
/* idx: 240 */ {0x0, 0x0, 0x0, 0x300000000}, // [224 225]
/* idx: 241 */ {0x0, 0x0, 0x0, 0xc00000000}, // [226 227]
/* idx: 242 */ {0x0, 0x0, 0x0, 0x3000000000}, // [228 229]
/* idx: 243 */ {0x0, 0x0, 0x0, 0xc000000000}, // [230 231]
/* idx: 244 */ {0x0, 0x0, 0x0, 0x30000000000}, // [232 233]
/* idx: 245 */ {0x0, 0x0, 0x0, 0xc0000000000}, // [234 235]
/* idx: 246 */ {0x0, 0x0, 0x0, 0x300000000000}, // [236 237]
/* idx: 247 */ {0x0, 0x0, 0x0, 0xc00000000000}, // [238 239]
/* idx: 248 */ {0x0, 0x0, 0x0, 0x3000000000000}, // [240 241]
/* idx: 249 */ {0x0, 0x0, 0x0, 0xc000000000000}, // [242 243]
/* idx: 250 */ {0x0, 0x0, 0x0, 0x30000000000000}, // [244 245]
/* idx: 251 */ {0x0, 0x0, 0x0, 0xc0000000000000}, // [246 247]
/* idx: 252 */ {0x0, 0x0, 0x0, 0x300000000000000}, // [248 249]
/* idx: 253 */ {0x0, 0x0, 0x0, 0xc00000000000000}, // [250 251]
/* idx: 254 */ {0x0, 0x0, 0x0, 0x3000000000000000}, // [252 253]
/* idx: 255 */ {0x0, 0x0, 0x0, 0xc000000000000000}, // [254 255]
}
+236
View File
@@ -0,0 +1,236 @@
//go:build generate
//go:generate go run $GOFILE
// Copyright (c) 2026 Karl Gaissmaier
// SPDX-License-Identifier: MIT
// Package main generates lookup tables for route allocation in the BART routing table.
//
// This generator creates two precomputed lookup tables (PfxRoutesLookupTbl and
// FringeRoutesLookupTbl) that enable fast prefix coverage queries. Each table maps
// a base index (1..255) to a bitset containing all more specific indices covered
// by that prefix.
//
// The tables model a complete binary tree where:
// - Indices 1..255 represent prefixes up to /7
// - Indices 256..511 represent /8 prefixes (stored separately as "fringe")
package main
import (
"fmt"
"os"
"os/exec"
"strings"
"text/template"
"github.com/gaissmai/bart/internal/bitset"
)
var (
thisFname = os.Getenv("GOFILE")
outFname = "allotroutesgenerated.go"
)
var (
// prefixAllotTbl holds the bitsets for indices 1..255 (prefixes up to /7).
// For each index idx, the bitset contains all descendant indices in the
// range 1..255 that are covered by the prefix at idx.
prefixAllotTbl [256]bitset.BitSet256
// fringeAllotTbl holds the bitsets for indices 1..255, but tracks descendants
// in the range 256..511 (/8 prefixes). The bitset at index idx contains all
// /8 fringe indices (minus 256 offset) covered by the prefix at idx.
fringeAllotTbl [256]bitset.BitSet256
)
// data holds the template variables for code generation
var data struct {
File string
PrefixAllotTable string
FringeAllotTable string
}
// colored prefix for informational and error messages
const (
INFO = "\x1b[34mINFO:\x1b[0m"
DIE = "\x1b[31mERROR:\x1b[0m"
)
func main() {
outFile, err := os.Create(outFname)
if err != nil {
fmt.Fprintf(os.Stderr, "%s %v\n", DIE, err)
os.Exit(1)
}
// Generate both lookup tables using the stack-based algorithm
prefixAllotTbl, fringeAllotTbl = genAllotTables()
// Populate template data
data.File = thisFname
data.PrefixAllotTable = asString(prefixAllotTbl)
data.FringeAllotTable = asString(fringeAllotTbl)
// Parse the template ...
t := template.Must(template.New("foo").Parse(codeTemplate))
// ... and execute it with our data
err = t.Execute(outFile, data)
if err != nil {
fmt.Fprintf(os.Stderr, "%s %v\n", DIE, err)
_ = outFile.Close()
os.Exit(1)
}
// Ensure the file is properly closed before formatting
if err := outFile.Close(); err != nil {
fmt.Fprintf(os.Stderr, "%s %v\n", DIE, err)
os.Exit(1)
}
fmt.Fprintf(os.Stdout, "%s ✓ Generated %s\n", INFO, outFname)
// Run goimports to organize imports and format the code
goimports := exec.Command("goimports", "-w", outFname)
fmt.Fprintf(os.Stdout, "%s Running goimports on %s\n", INFO, outFname)
if out, err := goimports.CombinedOutput(); err != nil {
fmt.Fprintf(os.Stderr, "%s %s\n", DIE, string(out))
os.Exit(1)
}
fmt.Fprintf(os.Stdout, "%s ✓ goimports completed\n", INFO)
// Run gofumpt for additional formatting consistency
gofumpt := exec.Command("gofumpt", "-w", outFname)
fmt.Fprintf(os.Stdout, "%s Running gofumpt on %s\n", INFO, outFname)
if out, err := gofumpt.CombinedOutput(); err != nil {
fmt.Fprintf(os.Stderr, "%s %s\n", DIE, string(out))
os.Exit(1)
}
fmt.Fprintf(os.Stdout, "%s ✓ gofumpt completed\n", INFO)
fmt.Println()
}
// genAllotTables generates both prefix and fringe allocation lookup tables.
//
// For each base index idx (1..255), it performs a breadth-first traversal of the
// complete binary tree rooted at idx, collecting all descendant indices. The
// traversal uses a stack-based approach instead of recursion.
//
// The algorithm:
// 1. Start with the root index idx in the stack
// 2. For each index j in the stack:
// - If j < 256: add j to prefixAllotTbl[idx]
// - If j >= 256: add (j-256) to fringeAllotTbl[idx]
// - If j < 256: push children (2*j and 2*j+1) onto the stack
// 3. Continue until the stack is exhausted
//
// This builds a complete binary tree where each node knows all its descendants,
// split into two ranges: [1..255] and [256..511].
//
// Returns the prefix table (indices 1..255) and fringe table (indices 256..511).
func genAllotTables() (prefix, fringe [256]bitset.BitSet256) {
prefixAllotTbl := [256]bitset.BitSet256{}
fringeAllotTbl := [256]bitset.BitSet256{}
for idx := 1; idx < 256; idx++ {
// allot algorithm, stack based instead of recursion
stack := make([]int, 0, 512)
stack = append(stack, idx)
for i := 0; i < len(stack); i++ {
j := stack[i]
if j < 256 {
prefixAllotTbl[idx].Set(uint8(j))
} else {
fringeAllotTbl[idx].Set(uint8(j - 256))
}
// max j is 511, so stop the duplication at 256 and above
if j >= 256 {
continue
}
// build a complete binary tree
// left: j*2
// right: (j*2)+1
stack = append(stack, j<<1, (j<<1)+1)
}
}
return prefixAllotTbl, fringeAllotTbl
}
// asString converts a lookup table to a formatted Go source string.
//
// Each entry in the table is formatted as:
//
// /* idx: N */ BitSet256{...}, // [list of set bits]
//
// For bitsets with 10 or more set bits, the list is truncated with "..." to
// keep the generated code readable. Index 0 is marked as "invalid" since no
// valid prefix maps to base index 0 in the BART structure.
//
// The returned string is ready to be embedded into the generated Go file.
func asString(tbl [256]bitset.BitSet256) string {
builder := strings.Builder{}
// Index 0 is invalid (no valid prefix maps to 0)
fmt.Fprintf(&builder, " /* idx: %3d */ %#v, // %s\n", 0, tbl[0], "invalid")
// Format each valid index with its bitset and the list of set bits, e.g.
// /* idx: 13 */ {0x204a, 0x0, 0x0, 0x0}, // [1 3 6 13]
for idx := 1; idx < 256; idx++ {
fmt.Fprintf(&builder, " /* idx: %3d */ %#v, // ", idx, tbl[idx])
bits := tbl[idx].Bits()
if len(bits) < 10 {
fmt.Fprintf(&builder, "%v\n", bits)
} else {
fmt.Fprint(&builder, "[")
for _, bit := range bits[:5] {
fmt.Fprintf(&builder, "%d, ", bit)
}
fmt.Fprintln(&builder, "... ]")
}
}
return builder.String()
}
// codeTemplate is the Go source template for the generated allotRoutes_gen.go file.
// It embeds the precomputed lookup tables and includes documentation about
// their structure and usage.
const codeTemplate = `// Code generated by {{.File}}; DO NOT EDIT.
// Copyright (c) 2026 Karl Gaissmaier
// SPDX-License-Identifier: MIT
package allot
// PfxRoutesLookupTbl is a lookup table of BitSet256 entries.
//
// It maps a baseIndex (1..255) to a bitset that contains all more
// specific baseIndices that are covered by the prefix at that index.
//
// The idea:
//
// If prefix P has baseIndex idx, then P covers all prefixes in the subtree
// rooted at idx. This table enumerates and encodes those indices.
//
// The table is split:
// - PfxRoutesLookupTbl holds prefixes up to /7
// - FringeRoutesLookupTbl holds /8 prefixes
//
// This structure allows for very fast set inclusion checks using simple bitwise AND.
//
//nolint:gochecknoglobals // Precomputed read‑only table used in hot paths.
var PfxRoutesLookupTbl = [256]bitset.BitSet256{
{{.PrefixAllotTable}} }
// FringeRoutesLookupTbl, the second 256 Bits, see also the PfxRoutesLookupTbl for the first 256 Bits
// we split 512 bits into 2×256 to leverage BitSet256 optimizations.
//
//nolint:gochecknoglobals // Precomputed read‑only table used in hot paths.
var FringeRoutesLookupTbl = [256]bitset.BitSet256{
{{.FringeAllotTable}} }
`
+153
View File
@@ -0,0 +1,153 @@
// Copyright (c) 2026 Karl Gaissmaier
// SPDX-License-Identifier: MIT
// Package art summarizes the functions and inverse functions
// for mapping between a prefix and a baseIndex.
//
// can inline OctetToIdx with cost 6
// can inline PfxToIdx with cost 10
// can inline NetMask with cost 13
// can inline PfxBits with cost 21
// can inline IdxToPfx with cost 39
// can inline IdxToRange with cost 71
package art
import "math/bits"
// PfxToIdx maps 8bit prefixes to numbers. The prefixes range from 0/0 to 255/7
// The return values range from 1 to 255.
//
// [0x0000_00001 .. 0x1111_1111] = [1 .. 255]
//
// example: octet/pfxLen: 160/3 = 0b1010_0000/3 => IdxToPfx(160/3) => 13
//
// 0b1010_0000 => 0b0000_0101
// ^^^ >> (8-3) ^^^
//
// 0b0000_0001 => 0b0000_1000
// ^ << 3 ^
// + -----------------------
// 0b0000_1101 = 13
//
// ATTENTION: For pfxLen values > 7 the result is undefined!
func PfxToIdx(octet, pfxLen uint8) uint8 {
// use '|' instead of '+', maybe a little bit faster
return octet>>(8-pfxLen) | 1<<pfxLen
}
// OctetToIdx maps octet/8 prefixes from [256..511] => [128..255] to start
// with the parent (>>1) in the complete binary tree.
//
// Same formula as PfxToIdx, but for octet/8 and a shift (>>1).
//
// octet>>(8-pfxLen) + 1<<pfxLen
// octet>>(8-8) + 1<<8
// octet + 256
//
// got to parent idx:
// (octet+256)>>1 == octet>>1 + 128
func OctetToIdx(octet uint8) uint8 {
// use '|' instead of '+', maybe a little bit faster
return octet>>1 | 128
}
// IdxToPfx returns the octet and prefix len of baseIdx.
// It's the inverse to PfxToIdx.
func IdxToPfx(idx uint8) (octet, pfxLen uint8) {
if idx == 0 {
panic("IdxToPfx: invalid idx 0")
}
// The prefix length corresponds to the number of leading bits in idx.
// bits.Len8 returns the number of bits needed to represent idx as binary,
// so we subtract 1 to recover the prefix length (which is always >= 0).
// Invariant: idx==0 is invalid
//nolint:gosec //G115: integer overflow conversion int -> uint8 (gosec)
pfxLen = uint8(bits.Len8(idx)) - 1
// Compute the number of bits to shift back to obtain the original octet.
shiftBits := 8 - pfxLen
// Extract the original prefix bits from idx by masking out the low bits.
mask := uint8(0xff) >> shiftBits
// Shift the masked prefix bits back to their original position.
octet = (idx & mask) << shiftBits
return octet, pfxLen
}
// PfxBits returns the bit position of a prefix represented by a base index at a given trie depth.
//
// Each trie level represents an 8-bit stride (one octet).
// The base index contains enough information to recover the prefix length.
// This function returns the full bit offset of the prefix in the address space.
//
// For example:
//
// depth = 2 (i.e. third trie level)
// idx = 13 (which encodes a prefix of length 3 bits within that stride)
//
// => PfxBits = 2*8 + 3 = 19
func PfxBits(depth int, idx uint8) uint8 {
// bits.Len8(idx) gives the number of significant bits (i.e. prefix length + 1)
// subtract 1 to get the actual prefix length used in this stride
pfxLenInStride := bits.Len8(idx) - 1
// Each trie level represents 8 bits (an octet), max depth is 16
baseBits := depth << 3 // same as depth * 8
// Total prefix length in bits, [0..128]
//nolint:gosec // G115: integer overflow conversion int -> uint8
return uint8(baseBits + pfxLenInStride)
}
// IdxToRange returns the first and last octets covered by a base index.
//
// The base index encodes a prefix of up to 8 bits inside a single stride (octet).
// This function computes the numerical start and end of the value range for that prefix.
//
// For example:
//
// - A 0-bit prefix (idx == 1) covers the full range: 0..255
//
// - A 3-bit prefix like 0b101xxxx (idx == 13) covers: 160..191
//
// idx := PfxToIdx(0b10100000, 3) // 13
// first, last := IdxToRange(13) // 160, 191
//
// Internally, this function decodes (octet, prefixLength) via IdxToPfx,
// then computes the broadcast address (last octet) by masking all bits below the prefix length.
func IdxToRange(idx uint8) (first, last uint8) {
// Decode the prefix base (octet) and length (number of fixed bits)
first, pfxLen := IdxToPfx(idx)
// Compute the "broadcast" value by filling trailing (host) bits with 1s.
// This gives the maximum octet value that still matches the prefix.
last = first | ^NetMask(pfxLen)
return first, last
}
// NetMask returns an 8-bit left-aligned network mask for the given number of prefix bits.
//
// For example:
//
// bits = 0 -> 0b00000000
// bits = 1 -> 0b10000000
// bits = 2 -> 0b11000000
// bits = 3 -> 0b11100000
// ...
// bits = 8 -> 0b11111111
//
// This mask is used to extract or identify the fixed (prefix) portion of an octet.
// The rightmost (8 - bits) bits are cleared (set to zero), and the upper 'bits' are set to 1.
func NetMask(bits uint8) uint8 {
if bits > 8 {
panic("NetMask: invalid bits > 8")
}
// Use a full 8-bit mask and shift left to clear trailing (host) bits.
// Shifts 0..8 on a uint8 are well-defined; safe for bits in [0..8].
return ^uint8(0) << (8 - bits)
}
-326
View File
@@ -1,326 +0,0 @@
// Copyright (c) 2024 Karl Gaissmaier
// SPDX-License-Identifier: MIT
// Package bitset implements bitsets, a mapping
// between non-negative integers and boolean values.
//
// Studied [github.com/bits-and-blooms/bitset] inside out
// and rewrote needed parts from scratch for this project.
//
// This implementation is smaller and faster as the more
// general [github.com/bits-and-blooms/bitset].
//
// All functions can be inlined!
//
// can inline BitSet.Set with cost 63
// can inline BitSet.Clear with cost 24
// can inline BitSet.Test with cost 26
// can inline BitSet.Rank0 with cost 66
// can inline BitSet.Clone with cost 7
// can inline BitSet.Compact with cost 35
// can inline BitSet.FirstSet with cost 25
// can inline BitSet.NextSet with cost 71
// can inline BitSet.AsSlice with cost 50
// can inline BitSet.All with cost 62
// can inline BitSet.IntersectsAny with cost 42
// can inline BitSet.IntersectionTop with cost 56
// can inline BitSet.IntersectionCardinality with cost 35
// can inline (*BitSet).InPlaceIntersection with cost 71
// can inline (*BitSet).InPlaceUnion with cost 77
// can inline BitSet.Size with cost 16
// can inline popcount with cost 12
// can inline popcountAnd with cost 30
package bitset
import (
"math/bits"
)
// A BitSet is a slice of words. This is an internal package
// with a wide open public API.
type BitSet []uint64
// xIdx calculates the index of i in a []uint64
// func wIdx(i uint) int {
// return int(i >> 6) // like (i / 64) but faster
// }
// bIdx calculates the index of i in a `uint64`
// func bIdx(i uint) uint {
// return i & 63 // like (i % 64) but faster
// }
//
// just as an explanation of the expressions,
//
// i>>6 or i<<6 and i&63
//
// not factored out as functions to make most of the methods
// inlineable with minimal costs.
// Set bit i to 1, the capacity of the bitset is increased accordingly.
func (b BitSet) Set(i uint) BitSet {
// grow?
if i >= uint(len(b)<<6) {
words := int((i + 64) >> 6)
switch {
case b == nil:
b = make([]uint64, words)
case cap(b) >= words:
b = b[:words]
default:
// be exact, don't use append!
// max 512 prefixes/node (8*uint64), and a cache line has 64 Bytes
newset := make([]uint64, words)
copy(newset, b)
b = newset
}
}
b[i>>6] |= 1 << (i & 63)
return b
}
// Clear bit i to 0.
func (b BitSet) Clear(i uint) BitSet {
if x := int(i >> 6); x < len(b) {
b[x] &^= 1 << (i & 63)
}
return b
}
// Test if bit i is set.
func (b BitSet) Test(i uint) (ok bool) {
if x := int(i >> 6); x < len(b) {
return b[x]&(1<<(i&63)) != 0
}
return
}
// Clone this BitSet, returning a new BitSet that has the same bits set.
func (b BitSet) Clone() BitSet {
return append(b[:0:0], b...)
}
// Compact, preserve all set bits, while minimizing memory usage.
func (b BitSet) Compact() BitSet {
last := len(b) - 1
// find last word with at least one bit set.
for ; last >= 0; last-- {
if b[last] != 0 {
b = b[: last+1 : last+1]
return b
}
}
// BitSet was empty, shrink to nil
return nil
}
// FirstSet returns the first bit set along with an ok code.
func (b BitSet) FirstSet() (uint, bool) {
for x, word := range b {
if word != 0 {
return uint(x<<6 + bits.TrailingZeros64(word)), true
}
}
return 0, false
}
// NextSet returns the next bit set from the specified index,
// including possibly the current index along with an ok code.
func (b BitSet) NextSet(i uint) (uint, bool) {
x := int(i >> 6)
if x >= len(b) {
return 0, false
}
// process the first (maybe partial) word
first := b[x] >> (i & 63) // i % 64
if first != 0 {
return i + uint(bits.TrailingZeros64(first)), true
}
// process the following words until next bit is set
// x < len(b), no out-of-bounds panic in following slice expression
x++
for j, word := range b[x:] {
if word != 0 {
return uint((x+j)<<6 + bits.TrailingZeros64(word)), true
}
}
return 0, false
}
// AsSlice returns all set bits as slice of uint without
// heap allocations.
//
// This is faster than All, but also more dangerous,
// it panics if the capacity of buf is < b.Size()
func (b BitSet) AsSlice(buf []uint) []uint {
buf = buf[:cap(buf)] // len = cap
size := 0
for idx, word := range b {
for ; word != 0; size++ {
// panics if capacity of buf is exceeded.
buf[size] = uint(idx<<6 + bits.TrailingZeros64(word))
// clear the rightmost set bit
word &= word - 1
}
}
buf = buf[:size]
return buf
}
// All returns all set bits. This is simpler but slower than AsSlice.
func (b BitSet) All() []uint {
buf := make([]uint, b.Size())
slot := 0
for idx, word := range b {
for word != 0 {
buf[slot] = uint(idx<<6 + bits.TrailingZeros64(word))
slot++
// clear the rightmost set bit
word &= word - 1
}
}
return buf
}
// IntersectsAny returns true if the intersection of base set with the compare set
// is not the empty set.
func (b BitSet) IntersectsAny(c BitSet) bool {
i := min(len(b), len(c)) - 1
// bounds check eliminated (BCE)
for ; i >= 0 && i < len(b) && i < len(c); i-- {
if b[i]&c[i] != 0 {
return true
}
}
return false
}
// IntersectionTop computes the intersection of base set with the compare set.
// If the result set isn't empty, it returns the top most set bit and true.
func (b BitSet) IntersectionTop(c BitSet) (top uint, ok bool) {
i := min(len(b), len(c)) - 1
// bounds check eliminated (BCE)
for ; i >= 0 && i < len(b) && i < len(c); i-- {
if word := b[i] & c[i]; word != 0 {
return uint(i<<6+bits.Len64(word)) - 1, true
}
}
return
}
// IntersectionCardinality computes the popcount of the intersection.
func (b BitSet) IntersectionCardinality(c BitSet) int {
return popcntAnd(b, c)
}
// InPlaceIntersection overwrites and computes the intersection of
// base set with the compare set. This is the BitSet equivalent of & (and).
// If len(c) > len(b), new memory is allocated.
func (b *BitSet) InPlaceIntersection(c BitSet) {
// bounds check eliminated, range until minLen(b,c)
for i := 0; i < len(*b) && i < len(c); i++ {
(*b)[i] &= c[i]
}
// b >= c
if len(*b) >= len(c) {
// bounds check eliminated
for i := len(c); i < len(*b); i++ {
(*b)[i] = 0
}
return
}
// b < c
newset := make([]uint64, len(c))
copy(newset, *b)
*b = newset
}
// InPlaceUnion creates the destructive union of base set with compare set.
// This is the BitSet equivalent of | (or).
// If len(c) > len(b), new memory is allocated.
func (b *BitSet) InPlaceUnion(c BitSet) {
// b >= c
if len(*b) >= len(c) {
// bounds check eliminated
for i := 0; i < len(*b) && i < len(c); i++ {
(*b)[i] |= c[i]
}
return
}
// b < c
newset := make([]uint64, len(c))
copy(newset, *b)
*b = newset
// bounds check eliminated
for i := 0; i < len(*b) && i < len(c); i++ {
(*b)[i] |= c[i]
}
}
// Size (number of set bits).
func (b BitSet) Size() int {
return popcntSlice(b)
}
// Rank0 is equal to Rank(i) - 1
//
// With inlined popcount to make Rank0 itself inlineable.
func (b BitSet) Rank0(i uint) (rnk int) {
// Rank count is inclusive
i++
if wordIdx := int(i >> 6); wordIdx >= len(b) {
// inlined popcount, whole slice
for _, x := range b {
rnk += bits.OnesCount64(x)
}
} else {
// inlined popcount, partial slice ...
for _, x := range b[:wordIdx] {
rnk += bits.OnesCount64(x)
}
// ... plus partial word?
if bitsIdx := i & 63; bitsIdx != 0 {
rnk += bits.OnesCount64(b[wordIdx] << (64 - bitsIdx))
}
}
// correct for offset by one
return rnk - 1
}
// popcntSlice
func popcntSlice(s []uint64) (cnt int) {
for _, x := range s {
// count all the bits set in slice.
cnt += bits.OnesCount64(x)
}
return
}
// popcntAnd
func popcntAnd(s, m []uint64) (cnt int) {
for j := 0; j < len(s) && j < len(m); j++ {
// words are bitwise & followed by popcount.
cnt += bits.OnesCount64(s[j] & m[j])
}
return
}
+595
View File
@@ -0,0 +1,595 @@
// Copyright (c) 2026 Karl Gaissmaier
// SPDX-License-Identifier: MIT
// Package bitset provides a compact and efficient implementation of a fixed-length
// bitset for the range [0..255].
//
// This implementation is optimized for internal use in a compressed routing trie
// and prioritizes minimal allocation, performance, and inlining. It supports
// constant-time set/test operations, iteration over set bits, ranking, and masked
// intersections.
//
// Internally, the bitset is represented by four uint64 words, providing
// fast bit-level access through direct indexing and hardware-accelerated primitives.
//
// If Go eventually supports SIMD intrinsics, this can be further optimized.
//
// For external consumers, the API intentionally avoids dynamic allocation except
// when explicitly requested (via Bits()).
package bitset
// can inline (*BitSet256).AsSlice with cost 42
// can inline (*BitSet256).Bits with cost 47
// can inline (*BitSet256).Clear with cost 12
// can inline (*BitSet256).FirstSet with cost 79
// can inline (*BitSet256).Intersects with cost 48
// can inline (*BitSet256).Intersection with cost 53
// can inline (*BitSet256).IntersectionTop with cost 67
// can inline (*BitSet256).IsEmpty with cost 22
// can inline (*BitSet256).LastSet with cost 75
// can inline (*BitSet256).NextSet with cost 65
// can inline (*BitSet256).Rank with cost 57
// can inline (*BitSet256).Set with cost 12
// can inline (*BitSet256).Size with cost 33
// can inline (*BitSet256).Test with cost 15
// can inline (*BitSet256).Union with cost 36
import (
"math/bits"
)
// wordIdx calculates the wordIndex of bit i in a []uint64
// func wordIdx(i uint) int {
// return int(i >> 6) // like (i / 64) but faster
// }
// bitIdx calculates the bitIndex of i in an `uint64`
// func bitIdx(i uint) uint {
// return i & 63 // like (i % 64) but mostly faster
// }
//
// just as an explanation of the expressions,
//
// i>>6 or i<<6 and i&63
//
// not factored out as functions to make most of the methods
// inlineable with minimal costs.
// BitSet256 represents a fixed-size bitset for the range [0..255],
// stored as four uint64 words (256 bits total).
type BitSet256 [4]uint64
// Set sets the bit at position bit (0..255).
func (b *BitSet256) Set(bit uint8) {
b[bit>>6] |= 1 << (bit & 63)
}
// Clear clears the bit at position bit (0..255).
func (b *BitSet256) Clear(bit uint8) {
b[bit>>6] &^= 1 << (bit & 63)
}
// Test reports whether the bit at position bit (0..255) is set.
func (b *BitSet256) Test(bit uint8) (ok bool) {
return b[bit>>6]&(1<<(bit&63)) != 0
}
// FirstSet returns the index of the lowest (first) bit that is set in the BitSet256.
//
// It searches the 256-bit set in ascending order and returns the position of the
// first bit with value 1. If at least one bit is set, ok is true.
// If no bits are set, ok is false and first is undefined.
//
// Example:
//
// var bs BitSet256
// bs.Set(17)
// bs.Set(42)
// bs.Set(130)
// bs.Set(255)
// index, ok := bs.FirstSet() // index == 17, ok == true
//
// Note: This implementation avoids a for loop for optimal speed.
// On modern CPUs, computing all four trailing-zero counts up front allows
// the CPU to parallelize these operations internally (pipelining), avoiding
// branch misprediction and maximizing instruction throughput. This technique
// is especially effective for bitsets with known, fixed word count.
func (b *BitSet256) FirstSet() (first uint8, ok bool) {
// optimized for pipelining, can still inline with cost 79
x0 := bits.TrailingZeros64(b[0])
x1 := bits.TrailingZeros64(b[1])
x2 := bits.TrailingZeros64(b[2])
x3 := bits.TrailingZeros64(b[3])
if x0 != 64 {
//nolint:gosec // G115: integer overflow conversion int -> uint
return uint8(x0), true
}
if x1 != 64 {
//nolint:gosec // G115: integer overflow conversion int -> uint
return uint8(x1 + 64), true
}
if x2 != 64 {
//nolint:gosec // G115: integer overflow conversion int -> uint
return uint8(x2 + 128), true
}
if x3 != 64 {
//nolint:gosec // G115: integer overflow conversion int -> uint
return uint8(x3 + 192), true
}
return
}
// NextSet returns the index of the next set bit that is greater than or equal to bit.
//
// If such a bit exists, it returns its index as next and ok=true.
// Otherwise, ok is false and next is undefined.
//
// The search starts at the given bit index and proceeds toward higher indices, scanning
// across all four 64-bit segments of the internal bitset representation.
//
// Example:
//
// b.Set(5)
// b.Set(130)
// b.NextSet(0) -> 5, true
// b.NextSet(5) -> 5, true
// b.NextSet(6) -> 130, true
// b.NextSet(200) -> 0, false
func (b *BitSet256) NextSet(bit uint8) (next uint8, ok bool) {
wIdx := int(bit >> 6)
// process the first (maybe partial) word
first := b[wIdx] >> (bit & 63)
if first != 0 {
//nolint:gosec // G115: integer overflow conversion int -> uint
return bit + uint8(bits.TrailingZeros64(first)), true
}
// process the following words until next bit is set
for wIdx++; wIdx < 4; wIdx++ {
if next := b[wIdx]; next != 0 {
//nolint:gosec // G115: integer overflow conversion int -> uint
return uint8(wIdx<<6 + bits.TrailingZeros64(next)), true
}
}
return
}
// LastSet returns the index of the highest (last) bit that is set in the BitSet256.
//
// It searches the bitset in descending order and returns the position of the
// first bit (top bit) with value 1. If at least one bit is set, ok is true.
// If no bits are set, ok is false and last is 0.
//
// Example:
//
// var bs BitSet256
// bs.Set(2)
// bs.Set(130)
// bs.Set(214)
// index, ok := bs.LastSet() // index == 214, ok == true
func (b *BitSet256) LastSet() (last uint8, ok bool) {
// optimized by unrolling the loop.
// This enables compiler inlining and is ~20% faster.
if b[3] != 0 {
//nolint:gosec // G115: integer overflow conversion int -> uint
return uint8(bits.Len64(b[3]) + 191), true
}
if b[2] != 0 {
//nolint:gosec // G115: integer overflow conversion int -> uint
return uint8(bits.Len64(b[2]) + 127), true
}
if b[1] != 0 {
//nolint:gosec // G115: integer overflow conversion int -> uint
return uint8(bits.Len64(b[1]) + 63), true
}
if b[0] != 0 {
//nolint:gosec // G115: integer overflow conversion int -> uint
return uint8(bits.Len64(b[0]) - 1), true
}
return 0, false
}
// AsSlice extracts the indices of all set bits in the BitSet256, returning them
// as uint8 values in strictly ascending order.
//
// Performance Considerations:
// To guarantee zero heap allocations and enable compiler inlining, the caller must
// provide a pointer to a 256-byte array (`buf`) as backing storage. The method
// populates this array in-place and returns a sliced view (`[]uint8`) tailored to
// the actual number of set bits.
//
// Safety and Lifecycle:
// The returned slice directly shares the underlying storage of `buf` and is only
// valid until `buf` is modified or reused. This pattern is highly recommended for
// hot paths and performance-critical loops where heap churn must be avoided.
func (b *BitSet256) AsSlice(buf *[256]uint8) []uint8 {
size := 0
for wIdx, word := range b {
for ; word != 0; size++ {
//nolint:gosec // G115: integer overflow conversion int -> uint
buf[size] = uint8(wIdx<<6 + bits.TrailingZeros64(word))
word &= word - 1 // clear the rightmost set bit
}
}
// tailor to the actual number of set bits
return buf[:size]
}
// Bits returns a slice containing the indices of all set bits in strictly
// ascending order as uint8 values.
//
// Performance Considerations:
// Unlike [AsSlice], this method dynamically allocates a new slice on the
// heap to store the result. It is designed for convenience and APIs where the lifecycle
// of the returned slice needs to outlive the immediate caller's stack frame.
//
// Usage Guidance:
// Use Bits when convenience is preferred over raw performance, or when the result
// must be returned across boundaries where stack-allocated buffers cannot safely escape.
// For high-throughput or allocation-free processing, prefer [AsSlice].
func (b *BitSet256) Bits() []uint8 {
return b.AsSlice(&[256]uint8{})
}
// IntersectionTop computes the intersection of the receiver with c
// and returns the highest (top-most) set bit of the result.
// If the intersection is non-empty, it returns the top bit index and true.
// If the intersection is empty, ok is false and top is 0.
func (b *BitSet256) IntersectionTop(c *BitSet256) (top uint8, ok bool) {
// optimized by unrolling the first word check.
// This enables compiler inlining and is ~15% faster.
if w := b[3] & c[3]; w != 0 {
//nolint:gosec // G115: integer overflow conversion int -> uint8
return uint8(191 + bits.Len64(w)), true
}
for wIdx := 2; wIdx >= 0; wIdx-- {
if w := b[wIdx] & c[wIdx]; w != 0 {
//nolint:gosec // G115: integer overflow conversion int -> uint8
return uint8(wIdx<<6 + bits.Len64(w) - 1), true
}
}
return
}
// Rank returns the number of bits set (i.e., value 1) in the BitSet256
// up to and including the provided index.
//
// The rank is computed efficiently using precomputed bitmasks (rankMask),
// which mask out all bits above the index. For example:
//
// b.Set(3)
// b.Set(5)
// b.Set(120)
// b.Rank(5) -> 2 // only bits 3 and 5 are ≤ 5
// b.Rank(119) -> 2 // only bits 3 and 5 are ≤ 119
// b.Rank(120) -> 3 // bit 120 is included here
//
// Rank is particularly useful in prefix trees, indexing schemes,
// and data compression techniques where ordinal positions matter.
//
// Internally, the function performs four bitwise-and operations
// between the bitset words and a precomputed mask covering bits 0..idx,
// followed by popcount operations (via bits.OnesCount64).
//
// This avoids dynamic mask construction and enables branch-free, highly
// predictable performance.
func (b *BitSet256) Rank(idx uint8) (rnk int) {
rnk += bits.OnesCount64(b[0] & rankMask[idx][0])
rnk += bits.OnesCount64(b[1] & rankMask[idx][1])
rnk += bits.OnesCount64(b[2] & rankMask[idx][2])
rnk += bits.OnesCount64(b[3] & rankMask[idx][3])
return
}
// IsEmpty reports whether all 256 bits are zero.
func (b *BitSet256) IsEmpty() bool {
return b[0]|b[1]|b[2]|b[3] == 0
}
// Intersects reports whether the receiver and c have at least one bit in common.
func (b *BitSet256) Intersects(c *BitSet256) bool {
return b[0]&c[0] != 0 ||
b[1]&c[1] != 0 ||
b[2]&c[2] != 0 ||
b[3]&c[3] != 0
}
// Intersection returns a new BitSet256 containing only the bits
// that are set in both the receiver and c (bitwise AND).
func (b *BitSet256) Intersection(c *BitSet256) (bs BitSet256) {
bs[0] = b[0] & c[0]
bs[1] = b[1] & c[1]
bs[2] = b[2] & c[2]
bs[3] = b[3] & c[3]
return
}
// Union sets all bits in the receiver that are set in c (in-place bitwise OR).
func (b *BitSet256) Union(c *BitSet256) {
b[0] |= c[0]
b[1] |= c[1]
b[2] |= c[2]
b[3] |= c[3]
}
// Size returns the population count, i.e. the number of set bits.
func (b *BitSet256) Size() (cnt int) {
cnt += bits.OnesCount64(b[0])
cnt += bits.OnesCount64(b[1])
cnt += bits.OnesCount64(b[2])
cnt += bits.OnesCount64(b[3])
return
}
// rankMask is a table of bitmasks with all bits set to 1 up to and including a given bit position.
// It is used by BitSet256.Rank() to perform efficient popcount operations.
//
// This approach trades ~8KB of static memory for zero-allocation,
// fully branch-free, and cache-friendly Rank() calls with constant runtime.
//
// Used internally by the trie for position counting, CIDR ordering,
// and fast range-limited bit population counts.
//
//nolint:gochecknoglobals // Precomputed read‑only table used in hot paths.
var rankMask = [256]BitSet256{
/* 0 */ {0x1, 0x0, 0x0, 0x0},
/* 1 */ {0x3, 0x0, 0x0, 0x0},
/* 2 */ {0x7, 0x0, 0x0, 0x0},
/* 3 */ {0xf, 0x0, 0x0, 0x0},
/* 4 */ {0x1f, 0x0, 0x0, 0x0},
/* 5 */ {0x3f, 0x0, 0x0, 0x0},
/* 6 */ {0x7f, 0x0, 0x0, 0x0},
/* 7 */ {0xff, 0x0, 0x0, 0x0},
/* 8 */ {0x1ff, 0x0, 0x0, 0x0},
/* 9 */ {0x3ff, 0x0, 0x0, 0x0},
/* 10 */ {0x7ff, 0x0, 0x0, 0x0},
/* 11 */ {0xfff, 0x0, 0x0, 0x0},
/* 12 */ {0x1fff, 0x0, 0x0, 0x0},
/* 13 */ {0x3fff, 0x0, 0x0, 0x0},
/* 14 */ {0x7fff, 0x0, 0x0, 0x0},
/* 15 */ {0xffff, 0x0, 0x0, 0x0},
/* 16 */ {0x1ffff, 0x0, 0x0, 0x0},
/* 17 */ {0x3ffff, 0x0, 0x0, 0x0},
/* 18 */ {0x7ffff, 0x0, 0x0, 0x0},
/* 19 */ {0xfffff, 0x0, 0x0, 0x0},
/* 20 */ {0x1fffff, 0x0, 0x0, 0x0},
/* 21 */ {0x3fffff, 0x0, 0x0, 0x0},
/* 22 */ {0x7fffff, 0x0, 0x0, 0x0},
/* 23 */ {0xffffff, 0x0, 0x0, 0x0},
/* 24 */ {0x1ffffff, 0x0, 0x0, 0x0},
/* 25 */ {0x3ffffff, 0x0, 0x0, 0x0},
/* 26 */ {0x7ffffff, 0x0, 0x0, 0x0},
/* 27 */ {0xfffffff, 0x0, 0x0, 0x0},
/* 28 */ {0x1fffffff, 0x0, 0x0, 0x0},
/* 29 */ {0x3fffffff, 0x0, 0x0, 0x0},
/* 30 */ {0x7fffffff, 0x0, 0x0, 0x0},
/* 31 */ {0xffffffff, 0x0, 0x0, 0x0},
/* 32 */ {0x1ffffffff, 0x0, 0x0, 0x0},
/* 33 */ {0x3ffffffff, 0x0, 0x0, 0x0},
/* 34 */ {0x7ffffffff, 0x0, 0x0, 0x0},
/* 35 */ {0xfffffffff, 0x0, 0x0, 0x0},
/* 36 */ {0x1fffffffff, 0x0, 0x0, 0x0},
/* 37 */ {0x3fffffffff, 0x0, 0x0, 0x0},
/* 38 */ {0x7fffffffff, 0x0, 0x0, 0x0},
/* 39 */ {0xffffffffff, 0x0, 0x0, 0x0},
/* 40 */ {0x1ffffffffff, 0x0, 0x0, 0x0},
/* 41 */ {0x3ffffffffff, 0x0, 0x0, 0x0},
/* 42 */ {0x7ffffffffff, 0x0, 0x0, 0x0},
/* 43 */ {0xfffffffffff, 0x0, 0x0, 0x0},
/* 44 */ {0x1fffffffffff, 0x0, 0x0, 0x0},
/* 45 */ {0x3fffffffffff, 0x0, 0x0, 0x0},
/* 46 */ {0x7fffffffffff, 0x0, 0x0, 0x0},
/* 47 */ {0xffffffffffff, 0x0, 0x0, 0x0},
/* 48 */ {0x1ffffffffffff, 0x0, 0x0, 0x0},
/* 49 */ {0x3ffffffffffff, 0x0, 0x0, 0x0},
/* 50 */ {0x7ffffffffffff, 0x0, 0x0, 0x0},
/* 51 */ {0xfffffffffffff, 0x0, 0x0, 0x0},
/* 52 */ {0x1fffffffffffff, 0x0, 0x0, 0x0},
/* 53 */ {0x3fffffffffffff, 0x0, 0x0, 0x0},
/* 54 */ {0x7fffffffffffff, 0x0, 0x0, 0x0},
/* 55 */ {0xffffffffffffff, 0x0, 0x0, 0x0},
/* 56 */ {0x1ffffffffffffff, 0x0, 0x0, 0x0},
/* 57 */ {0x3ffffffffffffff, 0x0, 0x0, 0x0},
/* 58 */ {0x7ffffffffffffff, 0x0, 0x0, 0x0},
/* 59 */ {0xfffffffffffffff, 0x0, 0x0, 0x0},
/* 60 */ {0x1fffffffffffffff, 0x0, 0x0, 0x0},
/* 61 */ {0x3fffffffffffffff, 0x0, 0x0, 0x0},
/* 62 */ {0x7fffffffffffffff, 0x0, 0x0, 0x0},
/* 63 */ {0xffffffffffffffff, 0x0, 0x0, 0x0},
/* 64 */ {0xffffffffffffffff, 0x1, 0x0, 0x0},
/* 65 */ {0xffffffffffffffff, 0x3, 0x0, 0x0},
/* 66 */ {0xffffffffffffffff, 0x7, 0x0, 0x0},
/* 67 */ {0xffffffffffffffff, 0xf, 0x0, 0x0},
/* 68 */ {0xffffffffffffffff, 0x1f, 0x0, 0x0},
/* 69 */ {0xffffffffffffffff, 0x3f, 0x0, 0x0},
/* 70 */ {0xffffffffffffffff, 0x7f, 0x0, 0x0},
/* 71 */ {0xffffffffffffffff, 0xff, 0x0, 0x0},
/* 72 */ {0xffffffffffffffff, 0x1ff, 0x0, 0x0},
/* 73 */ {0xffffffffffffffff, 0x3ff, 0x0, 0x0},
/* 74 */ {0xffffffffffffffff, 0x7ff, 0x0, 0x0},
/* 75 */ {0xffffffffffffffff, 0xfff, 0x0, 0x0},
/* 76 */ {0xffffffffffffffff, 0x1fff, 0x0, 0x0},
/* 77 */ {0xffffffffffffffff, 0x3fff, 0x0, 0x0},
/* 78 */ {0xffffffffffffffff, 0x7fff, 0x0, 0x0},
/* 79 */ {0xffffffffffffffff, 0xffff, 0x0, 0x0},
/* 80 */ {0xffffffffffffffff, 0x1ffff, 0x0, 0x0},
/* 81 */ {0xffffffffffffffff, 0x3ffff, 0x0, 0x0},
/* 82 */ {0xffffffffffffffff, 0x7ffff, 0x0, 0x0},
/* 83 */ {0xffffffffffffffff, 0xfffff, 0x0, 0x0},
/* 84 */ {0xffffffffffffffff, 0x1fffff, 0x0, 0x0},
/* 85 */ {0xffffffffffffffff, 0x3fffff, 0x0, 0x0},
/* 86 */ {0xffffffffffffffff, 0x7fffff, 0x0, 0x0},
/* 87 */ {0xffffffffffffffff, 0xffffff, 0x0, 0x0},
/* 88 */ {0xffffffffffffffff, 0x1ffffff, 0x0, 0x0},
/* 89 */ {0xffffffffffffffff, 0x3ffffff, 0x0, 0x0},
/* 90 */ {0xffffffffffffffff, 0x7ffffff, 0x0, 0x0},
/* 91 */ {0xffffffffffffffff, 0xfffffff, 0x0, 0x0},
/* 92 */ {0xffffffffffffffff, 0x1fffffff, 0x0, 0x0},
/* 93 */ {0xffffffffffffffff, 0x3fffffff, 0x0, 0x0},
/* 94 */ {0xffffffffffffffff, 0x7fffffff, 0x0, 0x0},
/* 95 */ {0xffffffffffffffff, 0xffffffff, 0x0, 0x0},
/* 96 */ {0xffffffffffffffff, 0x1ffffffff, 0x0, 0x0},
/* 97 */ {0xffffffffffffffff, 0x3ffffffff, 0x0, 0x0},
/* 98 */ {0xffffffffffffffff, 0x7ffffffff, 0x0, 0x0},
/* 99 */ {0xffffffffffffffff, 0xfffffffff, 0x0, 0x0},
/* 100 */ {0xffffffffffffffff, 0x1fffffffff, 0x0, 0x0},
/* 101 */ {0xffffffffffffffff, 0x3fffffffff, 0x0, 0x0},
/* 102 */ {0xffffffffffffffff, 0x7fffffffff, 0x0, 0x0},
/* 103 */ {0xffffffffffffffff, 0xffffffffff, 0x0, 0x0},
/* 104 */ {0xffffffffffffffff, 0x1ffffffffff, 0x0, 0x0},
/* 105 */ {0xffffffffffffffff, 0x3ffffffffff, 0x0, 0x0},
/* 106 */ {0xffffffffffffffff, 0x7ffffffffff, 0x0, 0x0},
/* 107 */ {0xffffffffffffffff, 0xfffffffffff, 0x0, 0x0},
/* 108 */ {0xffffffffffffffff, 0x1fffffffffff, 0x0, 0x0},
/* 109 */ {0xffffffffffffffff, 0x3fffffffffff, 0x0, 0x0},
/* 110 */ {0xffffffffffffffff, 0x7fffffffffff, 0x0, 0x0},
/* 111 */ {0xffffffffffffffff, 0xffffffffffff, 0x0, 0x0},
/* 112 */ {0xffffffffffffffff, 0x1ffffffffffff, 0x0, 0x0},
/* 113 */ {0xffffffffffffffff, 0x3ffffffffffff, 0x0, 0x0},
/* 114 */ {0xffffffffffffffff, 0x7ffffffffffff, 0x0, 0x0},
/* 115 */ {0xffffffffffffffff, 0xfffffffffffff, 0x0, 0x0},
/* 116 */ {0xffffffffffffffff, 0x1fffffffffffff, 0x0, 0x0},
/* 117 */ {0xffffffffffffffff, 0x3fffffffffffff, 0x0, 0x0},
/* 118 */ {0xffffffffffffffff, 0x7fffffffffffff, 0x0, 0x0},
/* 119 */ {0xffffffffffffffff, 0xffffffffffffff, 0x0, 0x0},
/* 120 */ {0xffffffffffffffff, 0x1ffffffffffffff, 0x0, 0x0},
/* 121 */ {0xffffffffffffffff, 0x3ffffffffffffff, 0x0, 0x0},
/* 122 */ {0xffffffffffffffff, 0x7ffffffffffffff, 0x0, 0x0},
/* 123 */ {0xffffffffffffffff, 0xfffffffffffffff, 0x0, 0x0},
/* 124 */ {0xffffffffffffffff, 0x1fffffffffffffff, 0x0, 0x0},
/* 125 */ {0xffffffffffffffff, 0x3fffffffffffffff, 0x0, 0x0},
/* 126 */ {0xffffffffffffffff, 0x7fffffffffffffff, 0x0, 0x0},
/* 127 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x0, 0x0},
/* 128 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x1, 0x0},
/* 129 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x3, 0x0},
/* 130 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x7, 0x0},
/* 131 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xf, 0x0},
/* 132 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x1f, 0x0},
/* 133 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x3f, 0x0},
/* 134 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x7f, 0x0},
/* 135 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xff, 0x0},
/* 136 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x1ff, 0x0},
/* 137 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x3ff, 0x0},
/* 138 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x7ff, 0x0},
/* 139 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xfff, 0x0},
/* 140 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x1fff, 0x0},
/* 141 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x3fff, 0x0},
/* 142 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x7fff, 0x0},
/* 143 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffff, 0x0},
/* 144 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x1ffff, 0x0},
/* 145 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x3ffff, 0x0},
/* 146 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x7ffff, 0x0},
/* 147 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xfffff, 0x0},
/* 148 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x1fffff, 0x0},
/* 149 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x3fffff, 0x0},
/* 150 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x7fffff, 0x0},
/* 151 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffff, 0x0},
/* 152 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x1ffffff, 0x0},
/* 153 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x3ffffff, 0x0},
/* 154 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x7ffffff, 0x0},
/* 155 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xfffffff, 0x0},
/* 156 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x1fffffff, 0x0},
/* 157 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x3fffffff, 0x0},
/* 158 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x7fffffff, 0x0},
/* 159 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffff, 0x0},
/* 160 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x1ffffffff, 0x0},
/* 161 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x3ffffffff, 0x0},
/* 162 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x7ffffffff, 0x0},
/* 163 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xfffffffff, 0x0},
/* 164 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x1fffffffff, 0x0},
/* 165 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x3fffffffff, 0x0},
/* 166 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x7fffffffff, 0x0},
/* 167 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffff, 0x0},
/* 168 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x1ffffffffff, 0x0},
/* 169 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x3ffffffffff, 0x0},
/* 170 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x7ffffffffff, 0x0},
/* 171 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xfffffffffff, 0x0},
/* 172 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x1fffffffffff, 0x0},
/* 173 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x3fffffffffff, 0x0},
/* 174 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x7fffffffffff, 0x0},
/* 175 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffff, 0x0},
/* 176 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x1ffffffffffff, 0x0},
/* 177 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x3ffffffffffff, 0x0},
/* 178 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x7ffffffffffff, 0x0},
/* 179 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xfffffffffffff, 0x0},
/* 180 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x1fffffffffffff, 0x0},
/* 181 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x3fffffffffffff, 0x0},
/* 182 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x7fffffffffffff, 0x0},
/* 183 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffff, 0x0},
/* 184 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x1ffffffffffffff, 0x0},
/* 185 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x3ffffffffffffff, 0x0},
/* 186 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x7ffffffffffffff, 0x0},
/* 187 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xfffffffffffffff, 0x0},
/* 188 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x1fffffffffffffff, 0x0},
/* 189 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x3fffffffffffffff, 0x0},
/* 190 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x7fffffffffffffff, 0x0},
/* 191 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x0},
/* 192 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x1},
/* 193 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x3},
/* 194 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x7},
/* 195 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xf},
/* 196 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x1f},
/* 197 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x3f},
/* 198 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x7f},
/* 199 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xff},
/* 200 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x1ff},
/* 201 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x3ff},
/* 202 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x7ff},
/* 203 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xfff},
/* 204 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x1fff},
/* 205 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x3fff},
/* 206 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x7fff},
/* 207 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffff},
/* 208 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x1ffff},
/* 209 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x3ffff},
/* 210 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x7ffff},
/* 211 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xfffff},
/* 212 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x1fffff},
/* 213 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x3fffff},
/* 214 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x7fffff},
/* 215 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffff},
/* 216 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x1ffffff},
/* 217 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x3ffffff},
/* 218 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x7ffffff},
/* 219 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xfffffff},
/* 220 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x1fffffff},
/* 221 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x3fffffff},
/* 222 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x7fffffff},
/* 223 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffff},
/* 224 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x1ffffffff},
/* 225 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x3ffffffff},
/* 226 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x7ffffffff},
/* 227 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xfffffffff},
/* 228 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x1fffffffff},
/* 229 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x3fffffffff},
/* 230 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x7fffffffff},
/* 231 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffff},
/* 232 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x1ffffffffff},
/* 233 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x3ffffffffff},
/* 234 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x7ffffffffff},
/* 235 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xfffffffffff},
/* 236 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x1fffffffffff},
/* 237 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x3fffffffffff},
/* 238 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x7fffffffffff},
/* 239 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffff},
/* 240 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x1ffffffffffff},
/* 241 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x3ffffffffffff},
/* 242 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x7ffffffffffff},
/* 243 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xfffffffffffff},
/* 244 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x1fffffffffffff},
/* 245 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x3fffffffffffff},
/* 246 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x7fffffffffffff},
/* 247 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffff},
/* 248 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x1ffffffffffffff},
/* 249 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x3ffffffffffffff},
/* 250 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x7ffffffffffffff},
/* 251 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xfffffffffffffff},
/* 252 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x1fffffffffffffff},
/* 253 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x3fffffffffffffff},
/* 254 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x7fffffffffffffff},
/* 255 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff},
}
+242
View File
@@ -0,0 +1,242 @@
//go:build generate
//go:generate go run $GOFILE
// Copyright (c) 2026 Karl Gaissmaier
// SPDX-License-Identifier: MIT
// Package main generates a lookup table for longest-prefix-match (LPM) operations
// in the BART routing table.
//
// This generator creates a precomputed lookup table (LookupTbl) that enables fast
// prefix matching by replacing iterative backtracking with a single bitset intersection.
//
// The core idea:
//
// In a complete binary tree representation of prefixes, finding the longest matching
// prefix requires checking a node and all its ancestors (parent, grandparent, etc.).
// Instead of iterating through ancestors at runtime (idx >>= 1 in a loop), this table
// precomputes all ancestors for each index.
//
// Binary tree structure:
// - Each index i has a parent at i>>1 (integer division by 2)
// - Index 1 is the root (represents the default route)
// - Indices 2-255 form a 7-level binary tree
//
// Example: Index 13 (binary: 0b1101) has ancestors:
// - 13 itself (0b1101)
// - 6 = 13>>1 (0b0110) - parent
// - 3 = 6>>1 (0b0011) - grandparent
// - 1 = 3>>1 (0b0001) - root
//
// With this table, checking if any ancestor exists in a node's prefix set becomes:
//
// node.prefixes.Intersects(&LookupTbl[idx])
//
// instead of a loop with multiple Test() calls.
package main
import (
"fmt"
"os"
"os/exec"
"strings"
"text/template"
"github.com/gaissmai/bart/internal/bitset"
)
var (
thisFname = os.Getenv("GOFILE")
outFname = "lookuptblgenerated.go"
)
// data holds the template variables for code generation.
// The LookupTbl field is populated immediately with the generated table string
var data = struct {
File string
LookupTbl string
}{
File: thisFname,
LookupTbl: genLookupTbl(),
}
// colored prefix for informational and error messages
const (
INFO = "\x1b[34mINFO:\x1b[0m"
DIE = "\x1b[31mERROR:\x1b[0m"
)
func main() {
outFile, err := os.Create(outFname)
if err != nil {
fmt.Fprintf(os.Stderr, "%s %v\n", DIE, err)
os.Exit(1)
}
// Parse the template ...
t := template.Must(template.New("foo").Parse(codeTemplate))
// ... and execute it with our data
err = t.Execute(outFile, data)
if err != nil {
fmt.Fprintf(os.Stderr, "%s %v\n", DIE, err)
_ = outFile.Close()
os.Exit(1)
}
// Ensure the file is properly closed before formatting
if err := outFile.Close(); err != nil {
fmt.Fprintf(os.Stderr, "%s %v\n", DIE, err)
os.Exit(1)
}
fmt.Fprintf(os.Stdout, "%s ✓ Generated %s\n", INFO, outFname)
// Run goimports to organize imports and format the code
goimports := exec.Command("goimports", "-w", outFname)
fmt.Fprintf(os.Stdout, "%s Running goimports on %s\n", INFO, outFname)
if out, err := goimports.CombinedOutput(); err != nil {
fmt.Fprintf(os.Stderr, "%s %s\n", DIE, string(out))
os.Exit(1)
}
fmt.Fprintf(os.Stdout, "%s ✓ goimports completed\n", INFO)
// Run gofumpt for additional formatting consistency
gofumpt := exec.Command("gofumpt", "-w", outFname)
fmt.Fprintf(os.Stdout, "%s Running gofumpt on %s\n", INFO, outFname)
if out, err := gofumpt.CombinedOutput(); err != nil {
fmt.Fprintf(os.Stderr, "%s %s\n", DIE, string(out))
os.Exit(1)
}
fmt.Fprintf(os.Stdout, "%s ✓ gofumpt completed\n", INFO)
fmt.Println()
}
// genLookupTbl generates the lookup table as a formatted string.
//
// For each index i (1-255), the table stores a bitset containing:
// - i itself
// - all binary ancestors of i (i>>1, i>>2, i>>3, ...)
//
// Example: For index 13 (binary: 1101):
// - Set bit 13 (1101)
// - Set bit 6 (0110) = 13>>1
// - Set bit 3 (0011) = 6>>1
// - Set bit 1 (0001) = 3>>1
//
// This precomputation allows replacing a loop of bitset tests
// with a single bitset intersection operation during longest-prefix-match.
// genLookupTbl generates the lookup table as a formatted string.
//
// For each index i (1-255), the table stores a BitSet256 containing:
// - i itself (the current node)
// - all binary ancestors of i (obtained by repeatedly right-shifting: i>>1, i>>2, ...)
//
// The algorithm walks up the binary tree from each leaf to the root (index 1).
// Each right-shift operation (i >>= 1) moves from a child to its parent node:
// - Even indices (LSB=0) are left children: parent = i/2
// - Odd indices (LSB=1) are right children: parent = (i-1)/2
// - Both cases are handled by integer division via right-shift
//
// Example walkthrough for index 13 (binary: 0b00001101):
//
// Iteration 1: i=13 (0b00001101) => Set bit 13
// Iteration 2: i=6 (0b00000110) => Set bit 6 (parent of 13)
// Iteration 3: i=3 (0b00000011) => Set bit 3 (parent of 6)
// Iteration 4: i=1 (0b00000001) => Set bit 1 (root)
// Iteration 5: i=0 → Loop terminates
//
// Result: BitSet256 with bits {1, 3, 6, 13} set
//
// This allows longest-prefix-match to be computed with a single bitset intersection:
//
// // Fast O(1) check using precomputed ancestors
// return node.prefixes.Intersects(&LookupTbl[idx])
//
// instead of iterative backtracking:
//
// // Slow O(log n) loop checking each ancestor individually
// for ; idx > 0; idx >>= 1 {
// if node.prefixes.Test(idx) {
// return true
// }
// }
//
// The generated string is formatted for embedding into Go source code.
func genLookupTbl() string {
// Initialize the lookup table with 256 entries (one per uint8 value)
lookupTbl := [256]bitset.BitSet256{}
for idx := 1; idx <= 255; idx++ {
// Walk up the binary tree by repeatedly right-shifting
// Each iteration moves to the parent node (idx >> 1)
for i := idx; i > 0; i >>= 1 {
lookupTbl[idx].Set(uint8(i))
}
}
// Convert the lookup table to a formatted string for the generated code
builder := strings.Builder{}
// Index 0 is invalid (no valid prefix maps to 0)
fmt.Fprintf(&builder, " /* idx: %3d */ %#v, // %s\n", 0, lookupTbl[0], "invalid")
// Format each valid index with its bitset and the list of set bits, e.g.
// /* idx: 13 */ {0x204a, 0x0, 0x0, 0x0}, // [1 3 6 13]
for idx := 1; idx < 256; idx++ {
fmt.Fprintf(&builder, " /* idx: %3d */ %#v, // %v\n", idx, lookupTbl[idx], lookupTbl[idx].Bits())
}
return builder.String()
}
// codeTemplate is the Go source template for the generated lookuptbl_gen.go file.
// It embeds the precomputed lookup table and includes comprehensive documentation
// about its structure, usage, and performance benefits.
//
// The template defines:
// - Package-level documentation explaining the LPM optimization
// - The LookupTbl variable with all 256 precomputed bitsets
// - Usage examples comparing the fast (Intersects) vs slow (loop) approach
// - Warnings about not mutating the read-only table
const codeTemplate = `// Code generated by {{.File}}; DO NOT EDIT.
// Copyright (c) 2026 Karl Gaissmaier
// SPDX-License-Identifier: MIT
// Package lpm (longest-prefix-match) contains the lookup table with which
// the backtracking for the lpm in the complete binary tree of the prefixes
// can be replaced by a fast bitset operation.
package lpm
// LookupTbl is a precomputed read‑only table used in hot paths.
//
// It allows a one-shot bitset intersection algorithm:
// Each entry i encodes i and all its binary ancestors (i>>1, i>>2, ...).
//
// idx must be the uint8 produced by art.OctetToIdx or art.PfxToIdx (0 is invalid).
//
// Usage:
//
// func (n *bartNode[V]) contains(idx uint8) bool {
// return n.prefixes.Intersects(&lpm.LookupTbl[idx])
// }
//
// instead of a sequence of single bitset tests:
//
// func (n *bartNode[V]) contains(idx uint8) bool {
// for ; idx > 0; idx >>= 1 {
// if n.prefixes.Test(idx) {
// return true
// }
// }
// return false
// }
//
// DO NOT MUTATE: Precomputed read‑only table used in hot paths.
//
//nolint:gochecknoglobals
var LookupTbl = [256]bitset.BitSet256{
{{.LookupTbl}} }`
+297
View File
@@ -0,0 +1,297 @@
// Code generated by generate_lookuptbl.go; DO NOT EDIT.
// Copyright (c) 2026 Karl Gaissmaier
// SPDX-License-Identifier: MIT
// Package lpm (longest-prefix-match) contains the lookup table with which
// the backtracking for the lpm in the complete binary tree of the prefixes
// can be replaced by a fast bitset operation.
package lpm
import "github.com/gaissmai/bart/internal/bitset"
// LookupTbl is a precomputed read‑only table used in hot paths.
//
// It allows a one-shot bitset intersection algorithm:
// Each entry i encodes i and all its binary ancestors (i>>1, i>>2, ...).
//
// idx must be the uint8 produced by art.OctetToIdx or art.PfxToIdx (0 is invalid).
//
// Usage:
//
// func (n *bartNode[V]) contains(idx uint8) bool {
// return n.prefixes.Intersects(&lpm.LookupTbl[idx])
// }
//
// instead of a sequence of single bitset tests:
//
// func (n *bartNode[V]) contains(idx uint8) bool {
// for ; idx > 0; idx >>= 1 {
// if n.prefixes.Test(idx) {
// return true
// }
// }
// return false
// }
//
// DO NOT MUTATE: Precomputed read‑only table used in hot paths.
//
//nolint:gochecknoglobals
var LookupTbl = [256]bitset.BitSet256{
/* idx: 0 */ {0x0, 0x0, 0x0, 0x0}, // invalid
/* idx: 1 */ {0x2, 0x0, 0x0, 0x0}, // [1]
/* idx: 2 */ {0x6, 0x0, 0x0, 0x0}, // [1 2]
/* idx: 3 */ {0xa, 0x0, 0x0, 0x0}, // [1 3]
/* idx: 4 */ {0x16, 0x0, 0x0, 0x0}, // [1 2 4]
/* idx: 5 */ {0x26, 0x0, 0x0, 0x0}, // [1 2 5]
/* idx: 6 */ {0x4a, 0x0, 0x0, 0x0}, // [1 3 6]
/* idx: 7 */ {0x8a, 0x0, 0x0, 0x0}, // [1 3 7]
/* idx: 8 */ {0x116, 0x0, 0x0, 0x0}, // [1 2 4 8]
/* idx: 9 */ {0x216, 0x0, 0x0, 0x0}, // [1 2 4 9]
/* idx: 10 */ {0x426, 0x0, 0x0, 0x0}, // [1 2 5 10]
/* idx: 11 */ {0x826, 0x0, 0x0, 0x0}, // [1 2 5 11]
/* idx: 12 */ {0x104a, 0x0, 0x0, 0x0}, // [1 3 6 12]
/* idx: 13 */ {0x204a, 0x0, 0x0, 0x0}, // [1 3 6 13]
/* idx: 14 */ {0x408a, 0x0, 0x0, 0x0}, // [1 3 7 14]
/* idx: 15 */ {0x808a, 0x0, 0x0, 0x0}, // [1 3 7 15]
/* idx: 16 */ {0x10116, 0x0, 0x0, 0x0}, // [1 2 4 8 16]
/* idx: 17 */ {0x20116, 0x0, 0x0, 0x0}, // [1 2 4 8 17]
/* idx: 18 */ {0x40216, 0x0, 0x0, 0x0}, // [1 2 4 9 18]
/* idx: 19 */ {0x80216, 0x0, 0x0, 0x0}, // [1 2 4 9 19]
/* idx: 20 */ {0x100426, 0x0, 0x0, 0x0}, // [1 2 5 10 20]
/* idx: 21 */ {0x200426, 0x0, 0x0, 0x0}, // [1 2 5 10 21]
/* idx: 22 */ {0x400826, 0x0, 0x0, 0x0}, // [1 2 5 11 22]
/* idx: 23 */ {0x800826, 0x0, 0x0, 0x0}, // [1 2 5 11 23]
/* idx: 24 */ {0x100104a, 0x0, 0x0, 0x0}, // [1 3 6 12 24]
/* idx: 25 */ {0x200104a, 0x0, 0x0, 0x0}, // [1 3 6 12 25]
/* idx: 26 */ {0x400204a, 0x0, 0x0, 0x0}, // [1 3 6 13 26]
/* idx: 27 */ {0x800204a, 0x0, 0x0, 0x0}, // [1 3 6 13 27]
/* idx: 28 */ {0x1000408a, 0x0, 0x0, 0x0}, // [1 3 7 14 28]
/* idx: 29 */ {0x2000408a, 0x0, 0x0, 0x0}, // [1 3 7 14 29]
/* idx: 30 */ {0x4000808a, 0x0, 0x0, 0x0}, // [1 3 7 15 30]
/* idx: 31 */ {0x8000808a, 0x0, 0x0, 0x0}, // [1 3 7 15 31]
/* idx: 32 */ {0x100010116, 0x0, 0x0, 0x0}, // [1 2 4 8 16 32]
/* idx: 33 */ {0x200010116, 0x0, 0x0, 0x0}, // [1 2 4 8 16 33]
/* idx: 34 */ {0x400020116, 0x0, 0x0, 0x0}, // [1 2 4 8 17 34]
/* idx: 35 */ {0x800020116, 0x0, 0x0, 0x0}, // [1 2 4 8 17 35]
/* idx: 36 */ {0x1000040216, 0x0, 0x0, 0x0}, // [1 2 4 9 18 36]
/* idx: 37 */ {0x2000040216, 0x0, 0x0, 0x0}, // [1 2 4 9 18 37]
/* idx: 38 */ {0x4000080216, 0x0, 0x0, 0x0}, // [1 2 4 9 19 38]
/* idx: 39 */ {0x8000080216, 0x0, 0x0, 0x0}, // [1 2 4 9 19 39]
/* idx: 40 */ {0x10000100426, 0x0, 0x0, 0x0}, // [1 2 5 10 20 40]
/* idx: 41 */ {0x20000100426, 0x0, 0x0, 0x0}, // [1 2 5 10 20 41]
/* idx: 42 */ {0x40000200426, 0x0, 0x0, 0x0}, // [1 2 5 10 21 42]
/* idx: 43 */ {0x80000200426, 0x0, 0x0, 0x0}, // [1 2 5 10 21 43]
/* idx: 44 */ {0x100000400826, 0x0, 0x0, 0x0}, // [1 2 5 11 22 44]
/* idx: 45 */ {0x200000400826, 0x0, 0x0, 0x0}, // [1 2 5 11 22 45]
/* idx: 46 */ {0x400000800826, 0x0, 0x0, 0x0}, // [1 2 5 11 23 46]
/* idx: 47 */ {0x800000800826, 0x0, 0x0, 0x0}, // [1 2 5 11 23 47]
/* idx: 48 */ {0x100000100104a, 0x0, 0x0, 0x0}, // [1 3 6 12 24 48]
/* idx: 49 */ {0x200000100104a, 0x0, 0x0, 0x0}, // [1 3 6 12 24 49]
/* idx: 50 */ {0x400000200104a, 0x0, 0x0, 0x0}, // [1 3 6 12 25 50]
/* idx: 51 */ {0x800000200104a, 0x0, 0x0, 0x0}, // [1 3 6 12 25 51]
/* idx: 52 */ {0x1000000400204a, 0x0, 0x0, 0x0}, // [1 3 6 13 26 52]
/* idx: 53 */ {0x2000000400204a, 0x0, 0x0, 0x0}, // [1 3 6 13 26 53]
/* idx: 54 */ {0x4000000800204a, 0x0, 0x0, 0x0}, // [1 3 6 13 27 54]
/* idx: 55 */ {0x8000000800204a, 0x0, 0x0, 0x0}, // [1 3 6 13 27 55]
/* idx: 56 */ {0x10000001000408a, 0x0, 0x0, 0x0}, // [1 3 7 14 28 56]
/* idx: 57 */ {0x20000001000408a, 0x0, 0x0, 0x0}, // [1 3 7 14 28 57]
/* idx: 58 */ {0x40000002000408a, 0x0, 0x0, 0x0}, // [1 3 7 14 29 58]
/* idx: 59 */ {0x80000002000408a, 0x0, 0x0, 0x0}, // [1 3 7 14 29 59]
/* idx: 60 */ {0x100000004000808a, 0x0, 0x0, 0x0}, // [1 3 7 15 30 60]
/* idx: 61 */ {0x200000004000808a, 0x0, 0x0, 0x0}, // [1 3 7 15 30 61]
/* idx: 62 */ {0x400000008000808a, 0x0, 0x0, 0x0}, // [1 3 7 15 31 62]
/* idx: 63 */ {0x800000008000808a, 0x0, 0x0, 0x0}, // [1 3 7 15 31 63]
/* idx: 64 */ {0x100010116, 0x1, 0x0, 0x0}, // [1 2 4 8 16 32 64]
/* idx: 65 */ {0x100010116, 0x2, 0x0, 0x0}, // [1 2 4 8 16 32 65]
/* idx: 66 */ {0x200010116, 0x4, 0x0, 0x0}, // [1 2 4 8 16 33 66]
/* idx: 67 */ {0x200010116, 0x8, 0x0, 0x0}, // [1 2 4 8 16 33 67]
/* idx: 68 */ {0x400020116, 0x10, 0x0, 0x0}, // [1 2 4 8 17 34 68]
/* idx: 69 */ {0x400020116, 0x20, 0x0, 0x0}, // [1 2 4 8 17 34 69]
/* idx: 70 */ {0x800020116, 0x40, 0x0, 0x0}, // [1 2 4 8 17 35 70]
/* idx: 71 */ {0x800020116, 0x80, 0x0, 0x0}, // [1 2 4 8 17 35 71]
/* idx: 72 */ {0x1000040216, 0x100, 0x0, 0x0}, // [1 2 4 9 18 36 72]
/* idx: 73 */ {0x1000040216, 0x200, 0x0, 0x0}, // [1 2 4 9 18 36 73]
/* idx: 74 */ {0x2000040216, 0x400, 0x0, 0x0}, // [1 2 4 9 18 37 74]
/* idx: 75 */ {0x2000040216, 0x800, 0x0, 0x0}, // [1 2 4 9 18 37 75]
/* idx: 76 */ {0x4000080216, 0x1000, 0x0, 0x0}, // [1 2 4 9 19 38 76]
/* idx: 77 */ {0x4000080216, 0x2000, 0x0, 0x0}, // [1 2 4 9 19 38 77]
/* idx: 78 */ {0x8000080216, 0x4000, 0x0, 0x0}, // [1 2 4 9 19 39 78]
/* idx: 79 */ {0x8000080216, 0x8000, 0x0, 0x0}, // [1 2 4 9 19 39 79]
/* idx: 80 */ {0x10000100426, 0x10000, 0x0, 0x0}, // [1 2 5 10 20 40 80]
/* idx: 81 */ {0x10000100426, 0x20000, 0x0, 0x0}, // [1 2 5 10 20 40 81]
/* idx: 82 */ {0x20000100426, 0x40000, 0x0, 0x0}, // [1 2 5 10 20 41 82]
/* idx: 83 */ {0x20000100426, 0x80000, 0x0, 0x0}, // [1 2 5 10 20 41 83]
/* idx: 84 */ {0x40000200426, 0x100000, 0x0, 0x0}, // [1 2 5 10 21 42 84]
/* idx: 85 */ {0x40000200426, 0x200000, 0x0, 0x0}, // [1 2 5 10 21 42 85]
/* idx: 86 */ {0x80000200426, 0x400000, 0x0, 0x0}, // [1 2 5 10 21 43 86]
/* idx: 87 */ {0x80000200426, 0x800000, 0x0, 0x0}, // [1 2 5 10 21 43 87]
/* idx: 88 */ {0x100000400826, 0x1000000, 0x0, 0x0}, // [1 2 5 11 22 44 88]
/* idx: 89 */ {0x100000400826, 0x2000000, 0x0, 0x0}, // [1 2 5 11 22 44 89]
/* idx: 90 */ {0x200000400826, 0x4000000, 0x0, 0x0}, // [1 2 5 11 22 45 90]
/* idx: 91 */ {0x200000400826, 0x8000000, 0x0, 0x0}, // [1 2 5 11 22 45 91]
/* idx: 92 */ {0x400000800826, 0x10000000, 0x0, 0x0}, // [1 2 5 11 23 46 92]
/* idx: 93 */ {0x400000800826, 0x20000000, 0x0, 0x0}, // [1 2 5 11 23 46 93]
/* idx: 94 */ {0x800000800826, 0x40000000, 0x0, 0x0}, // [1 2 5 11 23 47 94]
/* idx: 95 */ {0x800000800826, 0x80000000, 0x0, 0x0}, // [1 2 5 11 23 47 95]
/* idx: 96 */ {0x100000100104a, 0x100000000, 0x0, 0x0}, // [1 3 6 12 24 48 96]
/* idx: 97 */ {0x100000100104a, 0x200000000, 0x0, 0x0}, // [1 3 6 12 24 48 97]
/* idx: 98 */ {0x200000100104a, 0x400000000, 0x0, 0x0}, // [1 3 6 12 24 49 98]
/* idx: 99 */ {0x200000100104a, 0x800000000, 0x0, 0x0}, // [1 3 6 12 24 49 99]
/* idx: 100 */ {0x400000200104a, 0x1000000000, 0x0, 0x0}, // [1 3 6 12 25 50 100]
/* idx: 101 */ {0x400000200104a, 0x2000000000, 0x0, 0x0}, // [1 3 6 12 25 50 101]
/* idx: 102 */ {0x800000200104a, 0x4000000000, 0x0, 0x0}, // [1 3 6 12 25 51 102]
/* idx: 103 */ {0x800000200104a, 0x8000000000, 0x0, 0x0}, // [1 3 6 12 25 51 103]
/* idx: 104 */ {0x1000000400204a, 0x10000000000, 0x0, 0x0}, // [1 3 6 13 26 52 104]
/* idx: 105 */ {0x1000000400204a, 0x20000000000, 0x0, 0x0}, // [1 3 6 13 26 52 105]
/* idx: 106 */ {0x2000000400204a, 0x40000000000, 0x0, 0x0}, // [1 3 6 13 26 53 106]
/* idx: 107 */ {0x2000000400204a, 0x80000000000, 0x0, 0x0}, // [1 3 6 13 26 53 107]
/* idx: 108 */ {0x4000000800204a, 0x100000000000, 0x0, 0x0}, // [1 3 6 13 27 54 108]
/* idx: 109 */ {0x4000000800204a, 0x200000000000, 0x0, 0x0}, // [1 3 6 13 27 54 109]
/* idx: 110 */ {0x8000000800204a, 0x400000000000, 0x0, 0x0}, // [1 3 6 13 27 55 110]
/* idx: 111 */ {0x8000000800204a, 0x800000000000, 0x0, 0x0}, // [1 3 6 13 27 55 111]
/* idx: 112 */ {0x10000001000408a, 0x1000000000000, 0x0, 0x0}, // [1 3 7 14 28 56 112]
/* idx: 113 */ {0x10000001000408a, 0x2000000000000, 0x0, 0x0}, // [1 3 7 14 28 56 113]
/* idx: 114 */ {0x20000001000408a, 0x4000000000000, 0x0, 0x0}, // [1 3 7 14 28 57 114]
/* idx: 115 */ {0x20000001000408a, 0x8000000000000, 0x0, 0x0}, // [1 3 7 14 28 57 115]
/* idx: 116 */ {0x40000002000408a, 0x10000000000000, 0x0, 0x0}, // [1 3 7 14 29 58 116]
/* idx: 117 */ {0x40000002000408a, 0x20000000000000, 0x0, 0x0}, // [1 3 7 14 29 58 117]
/* idx: 118 */ {0x80000002000408a, 0x40000000000000, 0x0, 0x0}, // [1 3 7 14 29 59 118]
/* idx: 119 */ {0x80000002000408a, 0x80000000000000, 0x0, 0x0}, // [1 3 7 14 29 59 119]
/* idx: 120 */ {0x100000004000808a, 0x100000000000000, 0x0, 0x0}, // [1 3 7 15 30 60 120]
/* idx: 121 */ {0x100000004000808a, 0x200000000000000, 0x0, 0x0}, // [1 3 7 15 30 60 121]
/* idx: 122 */ {0x200000004000808a, 0x400000000000000, 0x0, 0x0}, // [1 3 7 15 30 61 122]
/* idx: 123 */ {0x200000004000808a, 0x800000000000000, 0x0, 0x0}, // [1 3 7 15 30 61 123]
/* idx: 124 */ {0x400000008000808a, 0x1000000000000000, 0x0, 0x0}, // [1 3 7 15 31 62 124]
/* idx: 125 */ {0x400000008000808a, 0x2000000000000000, 0x0, 0x0}, // [1 3 7 15 31 62 125]
/* idx: 126 */ {0x800000008000808a, 0x4000000000000000, 0x0, 0x0}, // [1 3 7 15 31 63 126]
/* idx: 127 */ {0x800000008000808a, 0x8000000000000000, 0x0, 0x0}, // [1 3 7 15 31 63 127]
/* idx: 128 */ {0x100010116, 0x1, 0x1, 0x0}, // [1 2 4 8 16 32 64 128]
/* idx: 129 */ {0x100010116, 0x1, 0x2, 0x0}, // [1 2 4 8 16 32 64 129]
/* idx: 130 */ {0x100010116, 0x2, 0x4, 0x0}, // [1 2 4 8 16 32 65 130]
/* idx: 131 */ {0x100010116, 0x2, 0x8, 0x0}, // [1 2 4 8 16 32 65 131]
/* idx: 132 */ {0x200010116, 0x4, 0x10, 0x0}, // [1 2 4 8 16 33 66 132]
/* idx: 133 */ {0x200010116, 0x4, 0x20, 0x0}, // [1 2 4 8 16 33 66 133]
/* idx: 134 */ {0x200010116, 0x8, 0x40, 0x0}, // [1 2 4 8 16 33 67 134]
/* idx: 135 */ {0x200010116, 0x8, 0x80, 0x0}, // [1 2 4 8 16 33 67 135]
/* idx: 136 */ {0x400020116, 0x10, 0x100, 0x0}, // [1 2 4 8 17 34 68 136]
/* idx: 137 */ {0x400020116, 0x10, 0x200, 0x0}, // [1 2 4 8 17 34 68 137]
/* idx: 138 */ {0x400020116, 0x20, 0x400, 0x0}, // [1 2 4 8 17 34 69 138]
/* idx: 139 */ {0x400020116, 0x20, 0x800, 0x0}, // [1 2 4 8 17 34 69 139]
/* idx: 140 */ {0x800020116, 0x40, 0x1000, 0x0}, // [1 2 4 8 17 35 70 140]
/* idx: 141 */ {0x800020116, 0x40, 0x2000, 0x0}, // [1 2 4 8 17 35 70 141]
/* idx: 142 */ {0x800020116, 0x80, 0x4000, 0x0}, // [1 2 4 8 17 35 71 142]
/* idx: 143 */ {0x800020116, 0x80, 0x8000, 0x0}, // [1 2 4 8 17 35 71 143]
/* idx: 144 */ {0x1000040216, 0x100, 0x10000, 0x0}, // [1 2 4 9 18 36 72 144]
/* idx: 145 */ {0x1000040216, 0x100, 0x20000, 0x0}, // [1 2 4 9 18 36 72 145]
/* idx: 146 */ {0x1000040216, 0x200, 0x40000, 0x0}, // [1 2 4 9 18 36 73 146]
/* idx: 147 */ {0x1000040216, 0x200, 0x80000, 0x0}, // [1 2 4 9 18 36 73 147]
/* idx: 148 */ {0x2000040216, 0x400, 0x100000, 0x0}, // [1 2 4 9 18 37 74 148]
/* idx: 149 */ {0x2000040216, 0x400, 0x200000, 0x0}, // [1 2 4 9 18 37 74 149]
/* idx: 150 */ {0x2000040216, 0x800, 0x400000, 0x0}, // [1 2 4 9 18 37 75 150]
/* idx: 151 */ {0x2000040216, 0x800, 0x800000, 0x0}, // [1 2 4 9 18 37 75 151]
/* idx: 152 */ {0x4000080216, 0x1000, 0x1000000, 0x0}, // [1 2 4 9 19 38 76 152]
/* idx: 153 */ {0x4000080216, 0x1000, 0x2000000, 0x0}, // [1 2 4 9 19 38 76 153]
/* idx: 154 */ {0x4000080216, 0x2000, 0x4000000, 0x0}, // [1 2 4 9 19 38 77 154]
/* idx: 155 */ {0x4000080216, 0x2000, 0x8000000, 0x0}, // [1 2 4 9 19 38 77 155]
/* idx: 156 */ {0x8000080216, 0x4000, 0x10000000, 0x0}, // [1 2 4 9 19 39 78 156]
/* idx: 157 */ {0x8000080216, 0x4000, 0x20000000, 0x0}, // [1 2 4 9 19 39 78 157]
/* idx: 158 */ {0x8000080216, 0x8000, 0x40000000, 0x0}, // [1 2 4 9 19 39 79 158]
/* idx: 159 */ {0x8000080216, 0x8000, 0x80000000, 0x0}, // [1 2 4 9 19 39 79 159]
/* idx: 160 */ {0x10000100426, 0x10000, 0x100000000, 0x0}, // [1 2 5 10 20 40 80 160]
/* idx: 161 */ {0x10000100426, 0x10000, 0x200000000, 0x0}, // [1 2 5 10 20 40 80 161]
/* idx: 162 */ {0x10000100426, 0x20000, 0x400000000, 0x0}, // [1 2 5 10 20 40 81 162]
/* idx: 163 */ {0x10000100426, 0x20000, 0x800000000, 0x0}, // [1 2 5 10 20 40 81 163]
/* idx: 164 */ {0x20000100426, 0x40000, 0x1000000000, 0x0}, // [1 2 5 10 20 41 82 164]
/* idx: 165 */ {0x20000100426, 0x40000, 0x2000000000, 0x0}, // [1 2 5 10 20 41 82 165]
/* idx: 166 */ {0x20000100426, 0x80000, 0x4000000000, 0x0}, // [1 2 5 10 20 41 83 166]
/* idx: 167 */ {0x20000100426, 0x80000, 0x8000000000, 0x0}, // [1 2 5 10 20 41 83 167]
/* idx: 168 */ {0x40000200426, 0x100000, 0x10000000000, 0x0}, // [1 2 5 10 21 42 84 168]
/* idx: 169 */ {0x40000200426, 0x100000, 0x20000000000, 0x0}, // [1 2 5 10 21 42 84 169]
/* idx: 170 */ {0x40000200426, 0x200000, 0x40000000000, 0x0}, // [1 2 5 10 21 42 85 170]
/* idx: 171 */ {0x40000200426, 0x200000, 0x80000000000, 0x0}, // [1 2 5 10 21 42 85 171]
/* idx: 172 */ {0x80000200426, 0x400000, 0x100000000000, 0x0}, // [1 2 5 10 21 43 86 172]
/* idx: 173 */ {0x80000200426, 0x400000, 0x200000000000, 0x0}, // [1 2 5 10 21 43 86 173]
/* idx: 174 */ {0x80000200426, 0x800000, 0x400000000000, 0x0}, // [1 2 5 10 21 43 87 174]
/* idx: 175 */ {0x80000200426, 0x800000, 0x800000000000, 0x0}, // [1 2 5 10 21 43 87 175]
/* idx: 176 */ {0x100000400826, 0x1000000, 0x1000000000000, 0x0}, // [1 2 5 11 22 44 88 176]
/* idx: 177 */ {0x100000400826, 0x1000000, 0x2000000000000, 0x0}, // [1 2 5 11 22 44 88 177]
/* idx: 178 */ {0x100000400826, 0x2000000, 0x4000000000000, 0x0}, // [1 2 5 11 22 44 89 178]
/* idx: 179 */ {0x100000400826, 0x2000000, 0x8000000000000, 0x0}, // [1 2 5 11 22 44 89 179]
/* idx: 180 */ {0x200000400826, 0x4000000, 0x10000000000000, 0x0}, // [1 2 5 11 22 45 90 180]
/* idx: 181 */ {0x200000400826, 0x4000000, 0x20000000000000, 0x0}, // [1 2 5 11 22 45 90 181]
/* idx: 182 */ {0x200000400826, 0x8000000, 0x40000000000000, 0x0}, // [1 2 5 11 22 45 91 182]
/* idx: 183 */ {0x200000400826, 0x8000000, 0x80000000000000, 0x0}, // [1 2 5 11 22 45 91 183]
/* idx: 184 */ {0x400000800826, 0x10000000, 0x100000000000000, 0x0}, // [1 2 5 11 23 46 92 184]
/* idx: 185 */ {0x400000800826, 0x10000000, 0x200000000000000, 0x0}, // [1 2 5 11 23 46 92 185]
/* idx: 186 */ {0x400000800826, 0x20000000, 0x400000000000000, 0x0}, // [1 2 5 11 23 46 93 186]
/* idx: 187 */ {0x400000800826, 0x20000000, 0x800000000000000, 0x0}, // [1 2 5 11 23 46 93 187]
/* idx: 188 */ {0x800000800826, 0x40000000, 0x1000000000000000, 0x0}, // [1 2 5 11 23 47 94 188]
/* idx: 189 */ {0x800000800826, 0x40000000, 0x2000000000000000, 0x0}, // [1 2 5 11 23 47 94 189]
/* idx: 190 */ {0x800000800826, 0x80000000, 0x4000000000000000, 0x0}, // [1 2 5 11 23 47 95 190]
/* idx: 191 */ {0x800000800826, 0x80000000, 0x8000000000000000, 0x0}, // [1 2 5 11 23 47 95 191]
/* idx: 192 */ {0x100000100104a, 0x100000000, 0x0, 0x1}, // [1 3 6 12 24 48 96 192]
/* idx: 193 */ {0x100000100104a, 0x100000000, 0x0, 0x2}, // [1 3 6 12 24 48 96 193]
/* idx: 194 */ {0x100000100104a, 0x200000000, 0x0, 0x4}, // [1 3 6 12 24 48 97 194]
/* idx: 195 */ {0x100000100104a, 0x200000000, 0x0, 0x8}, // [1 3 6 12 24 48 97 195]
/* idx: 196 */ {0x200000100104a, 0x400000000, 0x0, 0x10}, // [1 3 6 12 24 49 98 196]
/* idx: 197 */ {0x200000100104a, 0x400000000, 0x0, 0x20}, // [1 3 6 12 24 49 98 197]
/* idx: 198 */ {0x200000100104a, 0x800000000, 0x0, 0x40}, // [1 3 6 12 24 49 99 198]
/* idx: 199 */ {0x200000100104a, 0x800000000, 0x0, 0x80}, // [1 3 6 12 24 49 99 199]
/* idx: 200 */ {0x400000200104a, 0x1000000000, 0x0, 0x100}, // [1 3 6 12 25 50 100 200]
/* idx: 201 */ {0x400000200104a, 0x1000000000, 0x0, 0x200}, // [1 3 6 12 25 50 100 201]
/* idx: 202 */ {0x400000200104a, 0x2000000000, 0x0, 0x400}, // [1 3 6 12 25 50 101 202]
/* idx: 203 */ {0x400000200104a, 0x2000000000, 0x0, 0x800}, // [1 3 6 12 25 50 101 203]
/* idx: 204 */ {0x800000200104a, 0x4000000000, 0x0, 0x1000}, // [1 3 6 12 25 51 102 204]
/* idx: 205 */ {0x800000200104a, 0x4000000000, 0x0, 0x2000}, // [1 3 6 12 25 51 102 205]
/* idx: 206 */ {0x800000200104a, 0x8000000000, 0x0, 0x4000}, // [1 3 6 12 25 51 103 206]
/* idx: 207 */ {0x800000200104a, 0x8000000000, 0x0, 0x8000}, // [1 3 6 12 25 51 103 207]
/* idx: 208 */ {0x1000000400204a, 0x10000000000, 0x0, 0x10000}, // [1 3 6 13 26 52 104 208]
/* idx: 209 */ {0x1000000400204a, 0x10000000000, 0x0, 0x20000}, // [1 3 6 13 26 52 104 209]
/* idx: 210 */ {0x1000000400204a, 0x20000000000, 0x0, 0x40000}, // [1 3 6 13 26 52 105 210]
/* idx: 211 */ {0x1000000400204a, 0x20000000000, 0x0, 0x80000}, // [1 3 6 13 26 52 105 211]
/* idx: 212 */ {0x2000000400204a, 0x40000000000, 0x0, 0x100000}, // [1 3 6 13 26 53 106 212]
/* idx: 213 */ {0x2000000400204a, 0x40000000000, 0x0, 0x200000}, // [1 3 6 13 26 53 106 213]
/* idx: 214 */ {0x2000000400204a, 0x80000000000, 0x0, 0x400000}, // [1 3 6 13 26 53 107 214]
/* idx: 215 */ {0x2000000400204a, 0x80000000000, 0x0, 0x800000}, // [1 3 6 13 26 53 107 215]
/* idx: 216 */ {0x4000000800204a, 0x100000000000, 0x0, 0x1000000}, // [1 3 6 13 27 54 108 216]
/* idx: 217 */ {0x4000000800204a, 0x100000000000, 0x0, 0x2000000}, // [1 3 6 13 27 54 108 217]
/* idx: 218 */ {0x4000000800204a, 0x200000000000, 0x0, 0x4000000}, // [1 3 6 13 27 54 109 218]
/* idx: 219 */ {0x4000000800204a, 0x200000000000, 0x0, 0x8000000}, // [1 3 6 13 27 54 109 219]
/* idx: 220 */ {0x8000000800204a, 0x400000000000, 0x0, 0x10000000}, // [1 3 6 13 27 55 110 220]
/* idx: 221 */ {0x8000000800204a, 0x400000000000, 0x0, 0x20000000}, // [1 3 6 13 27 55 110 221]
/* idx: 222 */ {0x8000000800204a, 0x800000000000, 0x0, 0x40000000}, // [1 3 6 13 27 55 111 222]
/* idx: 223 */ {0x8000000800204a, 0x800000000000, 0x0, 0x80000000}, // [1 3 6 13 27 55 111 223]
/* idx: 224 */ {0x10000001000408a, 0x1000000000000, 0x0, 0x100000000}, // [1 3 7 14 28 56 112 224]
/* idx: 225 */ {0x10000001000408a, 0x1000000000000, 0x0, 0x200000000}, // [1 3 7 14 28 56 112 225]
/* idx: 226 */ {0x10000001000408a, 0x2000000000000, 0x0, 0x400000000}, // [1 3 7 14 28 56 113 226]
/* idx: 227 */ {0x10000001000408a, 0x2000000000000, 0x0, 0x800000000}, // [1 3 7 14 28 56 113 227]
/* idx: 228 */ {0x20000001000408a, 0x4000000000000, 0x0, 0x1000000000}, // [1 3 7 14 28 57 114 228]
/* idx: 229 */ {0x20000001000408a, 0x4000000000000, 0x0, 0x2000000000}, // [1 3 7 14 28 57 114 229]
/* idx: 230 */ {0x20000001000408a, 0x8000000000000, 0x0, 0x4000000000}, // [1 3 7 14 28 57 115 230]
/* idx: 231 */ {0x20000001000408a, 0x8000000000000, 0x0, 0x8000000000}, // [1 3 7 14 28 57 115 231]
/* idx: 232 */ {0x40000002000408a, 0x10000000000000, 0x0, 0x10000000000}, // [1 3 7 14 29 58 116 232]
/* idx: 233 */ {0x40000002000408a, 0x10000000000000, 0x0, 0x20000000000}, // [1 3 7 14 29 58 116 233]
/* idx: 234 */ {0x40000002000408a, 0x20000000000000, 0x0, 0x40000000000}, // [1 3 7 14 29 58 117 234]
/* idx: 235 */ {0x40000002000408a, 0x20000000000000, 0x0, 0x80000000000}, // [1 3 7 14 29 58 117 235]
/* idx: 236 */ {0x80000002000408a, 0x40000000000000, 0x0, 0x100000000000}, // [1 3 7 14 29 59 118 236]
/* idx: 237 */ {0x80000002000408a, 0x40000000000000, 0x0, 0x200000000000}, // [1 3 7 14 29 59 118 237]
/* idx: 238 */ {0x80000002000408a, 0x80000000000000, 0x0, 0x400000000000}, // [1 3 7 14 29 59 119 238]
/* idx: 239 */ {0x80000002000408a, 0x80000000000000, 0x0, 0x800000000000}, // [1 3 7 14 29 59 119 239]
/* idx: 240 */ {0x100000004000808a, 0x100000000000000, 0x0, 0x1000000000000}, // [1 3 7 15 30 60 120 240]
/* idx: 241 */ {0x100000004000808a, 0x100000000000000, 0x0, 0x2000000000000}, // [1 3 7 15 30 60 120 241]
/* idx: 242 */ {0x100000004000808a, 0x200000000000000, 0x0, 0x4000000000000}, // [1 3 7 15 30 60 121 242]
/* idx: 243 */ {0x100000004000808a, 0x200000000000000, 0x0, 0x8000000000000}, // [1 3 7 15 30 60 121 243]
/* idx: 244 */ {0x200000004000808a, 0x400000000000000, 0x0, 0x10000000000000}, // [1 3 7 15 30 61 122 244]
/* idx: 245 */ {0x200000004000808a, 0x400000000000000, 0x0, 0x20000000000000}, // [1 3 7 15 30 61 122 245]
/* idx: 246 */ {0x200000004000808a, 0x800000000000000, 0x0, 0x40000000000000}, // [1 3 7 15 30 61 123 246]
/* idx: 247 */ {0x200000004000808a, 0x800000000000000, 0x0, 0x80000000000000}, // [1 3 7 15 30 61 123 247]
/* idx: 248 */ {0x400000008000808a, 0x1000000000000000, 0x0, 0x100000000000000}, // [1 3 7 15 31 62 124 248]
/* idx: 249 */ {0x400000008000808a, 0x1000000000000000, 0x0, 0x200000000000000}, // [1 3 7 15 31 62 124 249]
/* idx: 250 */ {0x400000008000808a, 0x2000000000000000, 0x0, 0x400000000000000}, // [1 3 7 15 31 62 125 250]
/* idx: 251 */ {0x400000008000808a, 0x2000000000000000, 0x0, 0x800000000000000}, // [1 3 7 15 31 62 125 251]
/* idx: 252 */ {0x800000008000808a, 0x4000000000000000, 0x0, 0x1000000000000000}, // [1 3 7 15 31 63 126 252]
/* idx: 253 */ {0x800000008000808a, 0x4000000000000000, 0x0, 0x2000000000000000}, // [1 3 7 15 31 63 126 253]
/* idx: 254 */ {0x800000008000808a, 0x8000000000000000, 0x0, 0x4000000000000000}, // [1 3 7 15 31 63 127 254]
/* idx: 255 */ {0x800000008000808a, 0x8000000000000000, 0x0, 0x8000000000000000}, // [1 3 7 15 31 63 127 255]
}
+265
View File
@@ -0,0 +1,265 @@
// Copyright (c) 2026 Karl Gaissmaier
// SPDX-License-Identifier: MIT
package nodes
import (
"iter"
"github.com/gaissmai/bart/internal/lpm"
"github.com/gaissmai/bart/internal/sparse"
)
// BartNode represents a single trie level in the multibit routing table.
//
// Unlike the original ART algorithm, this implementation uses popcount-compressed
// sparse arrays instead of fixed-size allocations. Insertions and lookups rely on
// fast bitset operations and precomputed lookup tables to maximize CPU pipelining
// and cache efficiency.
//
// Each BartNode maintains two distinct sparse arrays:
//
// 1. Prefixes: Stores routing entries (prefix -> value) for the current stride.
// These are laid out as a complete binary tree using the baseIndex()
// function mapping from the ART algorithm. Prefixes that match exactly at
// the maximum trie depth are always stored here.
//
// 2. Children: Holds pointers to the next logical levels with a branching
// factor of 256 (8 bits per stride).
//
// A slot in the Children array may contain one of three types:
// - *BartNode[V]: An internal intermediate node for further trie traversal.
// - *FringeNode[V]: A path-compressed node if it qualifies as fringe [IsFringe]
// - *LeafNode[V]: A path-compressed node otherwise.
//
// Note: LeafNode and FringeNode are created through path compression and
// are automatically split into regular BartNodes when a more specific prefix
// is inserted that requires further branching.
type BartNode[V any] struct {
Prefixes sparse.Array256[V]
Children sparse.Array256[any]
}
// IsEmpty returns true if the node contains no routing entries (prefixes)
// and no child nodes. Empty nodes are candidates for compression or removal
// during trie optimization.
func (n *BartNode[V]) IsEmpty() bool {
if n == nil {
return true
}
return n.Prefixes.Len() == 0 && n.Children.Len() == 0
}
// PrefixCount returns the number of prefixes stored in this node.
func (n *BartNode[V]) PrefixCount() int {
return n.Prefixes.Len()
}
// ChildCount returns the number of slots used in this node.
func (n *BartNode[V]) ChildCount() int {
return n.Children.Len()
}
// InsertPrefix adds or updates a routing entry at the specified index with the given value.
// It returns true if a prefix already existed at that index (indicating an update),
// false if this is a new insertion.
func (n *BartNode[V]) InsertPrefix(idx uint8, val V) (exists bool) {
_, exists = n.Prefixes.InsertAt(idx, val)
return
}
// GetPrefix retrieves the value associated with the prefix at the given index.
// Returns the value and true if found, or zero value and false if not present.
func (n *BartNode[V]) GetPrefix(idx uint8) (val V, exists bool) {
return n.Prefixes.Get(idx)
}
// MustGetPrefix retrieves the value at the specified index, panicking if not found.
// This method should only be used when the caller is certain the index exists.
func (n *BartNode[V]) MustGetPrefix(idx uint8) (val V) {
return n.Prefixes.MustGet(idx)
}
// AllIndices returns an iterator over all prefix entries.
// Each iteration yields the prefix index (uint8) and its associated value (V).
func (n *BartNode[V]) AllIndices() iter.Seq2[uint8, V] {
return func(yield func(uint8, V) bool) {
var buf [256]uint8
for i, idx := range n.Prefixes.AsSlice(&buf) {
if !yield(idx, n.Prefixes.Items[i]) {
return
}
}
}
}
// DeletePrefix removes the prefix at the specified index.
// Returns true if the prefix existed, otherwise false.
func (n *BartNode[V]) DeletePrefix(idx uint8) (exists bool) {
_, exists = n.Prefixes.DeleteAt(idx)
return exists
}
// InsertChild adds a child node at the specified address (0-255).
// The child can be a *BartNode[V], *LeafNode[V], or *FringeNode[V].
// Returns true if a child already existed at that address.
func (n *BartNode[V]) InsertChild(addr uint8, child any) (exists bool) {
_, exists = n.Children.InsertAt(addr, child)
return
}
// GetChild retrieves the child node at the specified address.
// Returns the child and true if found, or nil and false if not present.
func (n *BartNode[V]) GetChild(addr uint8) (any, bool) {
return n.Children.Get(addr)
}
// MustGetChild retrieves the child at the specified address, panicking if not found.
// This method should only be used when the caller is certain the child exists.
func (n *BartNode[V]) MustGetChild(addr uint8) any {
return n.Children.MustGet(addr)
}
// AllChildren returns an iterator over all child nodes.
// Each iteration yields the child's address (uint8) and the child node (any).
func (n *BartNode[V]) AllChildren() iter.Seq2[uint8, any] {
return func(yield func(addr uint8, child any) bool) {
var buf [256]uint8
addrs := n.Children.AsSlice(&buf)
for i, addr := range addrs {
if !yield(addr, n.Children.Items[i]) {
return
}
}
}
}
// DeleteChild removes the child node at the specified address.
// This operation is idempotent - removing a non-existent child is safe.
func (n *BartNode[V]) DeleteChild(addr uint8) (exists bool) {
_, exists = n.Children.DeleteAt(addr)
return exists
}
// Contains returns true if an index (idx) has any matching longest-prefix
// in the current node’s prefix table.
//
// This function performs a presence check without retrieving the associated value.
// It is faster than a full lookup, as it only tests for intersection with the
// backtracking bitset for the given index.
//
// The prefix table is structured as a complete binary tree (CBT), and LPM testing
// is done via a bitset operation that maps the traversal path from the given index
// toward its possible ancestors.
func (n *BartNode[V]) Contains(idx uint8) bool {
return n.Prefixes.Intersects(&lpm.LookupTbl[idx])
}
// LookupIdx performs a longest-prefix match (LPM) lookup for the given index (idx)
// within the 8-bit stride-based prefix table at this trie depth.
//
// The function returns the matched base index, associated value, and true if a
// matching prefix exists at this level; otherwise, ok is false.
//
// Internally, the prefix table is organized as a complete binary tree (CBT) indexed
// via the baseIndex function. Unlike the original ART algorithm, this implementation
// does not use an allotment-based approach. Instead, it performs CBT backtracking
// using a bitset-based operation with a precomputed backtracking pattern specific to idx.
func (n *BartNode[V]) LookupIdx(idx uint8) (top uint8, val V, ok bool) {
// top is the idx of the longest-prefix-match
if top, ok = n.Prefixes.IntersectionTop(&lpm.LookupTbl[idx]); ok {
return top, n.MustGetPrefix(top), true
}
return top, val, ok
}
// Lookup is just a simple wrapper for LookupIdx.
func (n *BartNode[V]) Lookup(idx uint8) (val V, ok bool) {
_, val, ok = n.LookupIdx(idx)
return val, ok
}
// CloneFlat returns a shallow copy of the current node, optionally performing deep copies of values.
//
// If cloneFn is nil, the stored values in prefixes are copied directly without modification.
// Otherwise, cloneFn is applied to each stored value for deep cloning.
// Child nodes are cloned shallowly: LeafNode and FringeNode children are cloned via their clone methods,
// but child nodes of type *BartNode[V] (subnodes) are assigned as-is without recursive cloning.
// This method does not recursively clone descendants beyond the immediate children.
//
// Note: The returned node is a new instance with copied slices but only shallow copies of nested nodes,
// except for LeafNode and FringeNode children which are cloned according to cloneFn.
func (n *BartNode[V]) CloneFlat(cloneFn func(V) V) *BartNode[V] {
if n == nil {
return nil
}
c := new(BartNode[V])
if n.IsEmpty() {
return c
}
// copy ...
c.Prefixes = *(n.Prefixes.Copy())
// ... and clone the values
if cloneFn != nil {
for i, v := range c.Prefixes.Items {
c.Prefixes.Items[i] = cloneFn(v)
}
}
// copy ...
c.Children = *(n.Children.Copy())
// Iterate over children to flat clone leaf/fringe nodes;
// for *BartNode[V] children, keep shallow references (no recursive clone)
for i, anyKid := range c.Children.Items {
switch kid := anyKid.(type) {
case *BartNode[V]:
// Shallow copy
case *LeafNode[V]:
// Clone leaf nodes, applying cloneFn as needed
c.Children.Items[i] = kid.CloneLeaf(cloneFn)
case *FringeNode[V]:
// Clone fringe nodes, applying cloneFn as needed
c.Children.Items[i] = kid.CloneFringe(cloneFn)
default:
panic("logic error, wrong node type")
}
}
return c
}
// CloneRec performs a recursive deep copy of the node and all its descendants.
//
// If cloneFn is nil, the stored values are copied directly without modification.
// Otherwise cloneFn is applied to each stored value for deep cloning.
//
// This method first creates a shallow clone of the current node using CloneFlat,
// applying cloneFn to values as described there. Then it recursively clones all
// child nodes of type *BartNode[V], performing a full deep clone down the subtree.
//
// Child nodes of type *LeafNode[V] and *FringeNode[V] are already cloned
// by CloneFlat.
//
// Returns a new instance of BartNode[V] which is a complete deep clone of the
// receiver node with all descendants.
func (n *BartNode[V]) CloneRec(cloneFn func(V) V) *BartNode[V] {
if n == nil {
return nil
}
// Perform a flat clone of the current node.
c := n.CloneFlat(cloneFn)
// Recursively clone all child nodes of type *BartNode[V]
for i, kidAny := range c.Children.Items {
if kid, ok := kidAny.(*BartNode[V]); ok {
c.Children.Items[i] = kid.CloneRec(cloneFn)
}
}
return c
}
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+286
View File
@@ -0,0 +1,286 @@
// Copyright (c) 2026 Karl Gaissmaier
// SPDX-License-Identifier: MIT
package nodes
import (
"iter"
"github.com/gaissmai/bart/internal/lpm"
"github.com/gaissmai/bart/internal/sparse"
)
// FastNode is based on [BartNode], but it also uses a cache ([256]uint8)
// per node to speed up traversal of the multi-bit trie.
// Lookups become faster, but this requires more memory per prefix,
// and updates (insertions/deletions) also become slower due to the
// overhead of managing the cache.
type FastNode[V any] struct {
Prefixes sparse.Array256[V]
Children sparse.Array256[any]
// map addr to slice idx (aka rank)
childRankCache [256]uint8
}
// IsEmpty returns true if the node contains no routing entries (prefixes)
// and no child nodes. Empty nodes are candidates for compression or removal
// during trie optimization.
func (n *FastNode[V]) IsEmpty() bool {
if n == nil {
return true
}
return n.Prefixes.Len() == 0 && n.Children.Len() == 0
}
// PrefixCount returns the number of prefixes stored in this node.
func (n *FastNode[V]) PrefixCount() int {
return n.Prefixes.Len()
}
// ChildCount returns the number of slots used in this node.
func (n *FastNode[V]) ChildCount() int {
return n.Children.Len()
}
// InsertPrefix adds or updates a routing entry at the specified index with the given value.
// It returns true if a prefix already existed at that index (indicating an update),
// false if this is a new insertion.
func (n *FastNode[V]) InsertPrefix(idx uint8, val V) (exists bool) {
_, exists = n.Prefixes.InsertAt(idx, val)
return
}
// GetPrefix retrieves the value associated with the prefix at the given index.
// Returns the value and true if found, or zero value and false if not present.
func (n *FastNode[V]) GetPrefix(idx uint8) (val V, exists bool) {
return n.Prefixes.Get(idx)
}
// MustGetPrefix retrieves the value at the specified index, panicking if not found.
// This method should only be used when the caller is certain the index exists.
func (n *FastNode[V]) MustGetPrefix(idx uint8) (val V) {
return n.Prefixes.MustGet(idx)
}
// AllIndices returns an iterator over all prefix entries.
// Each iteration yields the prefix index (uint8) and its associated value (V).
func (n *FastNode[V]) AllIndices() iter.Seq2[uint8, V] {
return func(yield func(uint8, V) bool) {
var buf [256]uint8
for i, idx := range n.Prefixes.AsSlice(&buf) {
if !yield(idx, n.Prefixes.Items[i]) {
return
}
}
}
}
// DeletePrefix removes the prefix at the specified index.
// Returns true if the prefix existed, otherwise false.
func (n *FastNode[V]) DeletePrefix(idx uint8) (exists bool) {
_, exists = n.Prefixes.DeleteAt(idx)
return exists
}
// InsertChild adds a child node at the specified address (0-255).
// The child can be a *FastNode[V], *LeafNode[V], or *FringeNode[V].
// Returns true if a child already existed at that address.
func (n *FastNode[V]) InsertChild(addr uint8, child any) (exists bool) {
var rank0 int
rank0, exists = n.Children.InsertAt(addr, child)
if exists {
// Update only: the value at addr is overwritten in-place by InsertAt.
// childRankCache[addr] is not refreshed because the rank is unchanged
// the position of addr in the sparse slice does not shift on an update.
return
}
// new child inserted? cache the rank value for this addr
//nolint:gosec // G115: integer overflow conversion int -> uint8
n.childRankCache[addr] = uint8(rank0)
// increment all cached ranks after addr
for i := 255; i > int(addr); i-- {
n.childRankCache[i]++
}
return
}
// DeleteChild removes the child node at the specified address.
// This operation is idempotent - removing a non-existent child is safe.
func (n *FastNode[V]) DeleteChild(addr uint8) (exists bool) {
_, exists = n.Children.DeleteAt(addr)
// nothing deleted
if !exists {
return exists
}
// decrement all cached ranks after addr
for i := 255; i > int(addr); i-- {
n.childRankCache[i]--
}
return exists
}
// GetChild retrieves the child node at the specified address.
// Returns the child and true if found, or nil and false if not present.
func (n *FastNode[V]) GetChild(addr uint8) (any, bool) {
if n.Children.Test(addr) {
rank0 := n.childRankCache[addr]
return n.Children.Items[rank0], true
}
return nil, false
}
// MustGetChild retrieves the child at addr using the pre-cached rank stored in
// childRankCache[addr] for a direct O(1) array access without an existence check.
//
// The caller must guarantee that addr is present (Children.Test(addr) == true).
// If addr is absent, childRankCache[addr] contains the number of occupied
// addresses less than addr (maintained by InsertChild/DeleteChild), so the
// behaviour is undefined: either a wrong child is returned silently, or the
// call panics with an index-out-of-range error.
func (n *FastNode[V]) MustGetChild(addr uint8) any {
rank0 := n.childRankCache[addr]
return n.Children.Items[rank0]
}
// AllChildren returns an iterator over all child nodes.
// Each iteration yields the child's address (uint8) and the child node (any).
func (n *FastNode[V]) AllChildren() iter.Seq2[uint8, any] {
return func(yield func(addr uint8, child any) bool) {
var buf [256]uint8
addrs := n.Children.AsSlice(&buf)
for i, addr := range addrs {
if !yield(addr, n.Children.Items[i]) {
return
}
}
}
}
// Contains returns true if an index (idx) has any matching longest-prefix
// in the current node’s prefix table.
//
// This function performs a presence check without retrieving the associated value.
// It is faster than a full lookup, as it only tests for intersection with the
// backtracking bitset for the given index.
//
// The prefix table is structured as a complete binary tree (CBT), and LPM testing
// is done via a bitset operation that maps the traversal path from the given index
// toward its possible ancestors.
func (n *FastNode[V]) Contains(idx uint8) bool {
return n.Prefixes.Intersects(&lpm.LookupTbl[idx])
}
// LookupIdx performs a longest-prefix match (LPM) lookup for the given index (idx)
// within the 8-bit stride-based prefix table at this trie depth.
//
// The function returns the matched base index, associated value, and true if a
// matching prefix exists at this level; otherwise, ok is false.
//
// Internally, the prefix table is organized as a complete binary tree (CBT) indexed
// via the baseIndex function. Unlike the original ART algorithm, this implementation
// does not use an allotment-based approach. Instead, it performs CBT backtracking
// using a bitset-based operation with a precomputed backtracking pattern specific to idx.
func (n *FastNode[V]) LookupIdx(idx uint8) (top uint8, val V, ok bool) {
// top is the idx of the longest-prefix-match
if top, ok = n.Prefixes.IntersectionTop(&lpm.LookupTbl[idx]); ok {
return top, n.MustGetPrefix(top), true
}
return top, val, ok
}
// Lookup is just a simple wrapper for LookupIdx.
func (n *FastNode[V]) Lookup(idx uint8) (val V, ok bool) {
_, val, ok = n.LookupIdx(idx)
return val, ok
}
// CloneFlat returns a shallow copy of the current node, optionally performing deep copies of values.
//
// If cloneFn is nil, the stored values in prefixes are copied directly without modification.
// Otherwise, cloneFn is applied to each stored value for deep cloning.
// Child nodes are cloned shallowly: LeafNode and FringeNode children are cloned via their clone methods,
// but child nodes of type *FastNode[V] (subnodes) are assigned as-is without recursive cloning.
// This method does not recursively clone descendants beyond the immediate children.
//
// Note: The returned node is a new instance with copied slices but only shallow copies of nested nodes,
// except for LeafNode and FringeNode children which are cloned according to cloneFn.
func (n *FastNode[V]) CloneFlat(cloneFn func(V) V) *FastNode[V] {
if n == nil {
return nil
}
c := new(FastNode[V])
if n.IsEmpty() {
return c
}
// copy ...
c.Prefixes = *(n.Prefixes.Copy())
// ... and clone the values
if cloneFn != nil {
for i, v := range c.Prefixes.Items {
c.Prefixes.Items[i] = cloneFn(v)
}
}
// copy ...
c.childRankCache = n.childRankCache
c.Children = *(n.Children.Copy())
// Iterate over children to flat clone leaf/fringe nodes;
// for *FastNode[V] children, keep shallow references (no recursive clone)
for i, anyKid := range c.Children.Items {
switch kid := anyKid.(type) {
case *FastNode[V]:
// Shallow copy
case *LeafNode[V]:
// Clone leaf nodes, applying cloneFn as needed
c.Children.Items[i] = kid.CloneLeaf(cloneFn)
case *FringeNode[V]:
// Clone fringe nodes, applying cloneFn as needed
c.Children.Items[i] = kid.CloneFringe(cloneFn)
default:
panic("logic error, wrong node type")
}
}
return c
}
// CloneRec performs a recursive deep copy of the node and all its descendants.
//
// If cloneFn is nil, the stored values are copied directly without modification.
// Otherwise cloneFn is applied to each stored value for deep cloning.
//
// This method first creates a shallow clone of the current node using CloneFlat,
// applying cloneFn to values as described there. Then it recursively clones all
// child nodes of type *FastNode[V], performing a full deep clone down the subtree.
//
// Child nodes of type *LeafNode[V] and *FringeNode[V] are already cloned
// by CloneFlat.
//
// Returns a new instance of FastNode[V] which is a complete deep clone of the
// receiver node with all descendants.
func (n *FastNode[V]) CloneRec(cloneFn func(V) V) *FastNode[V] {
if n == nil {
return nil
}
// Perform a flat clone of the current node.
c := n.CloneFlat(cloneFn)
// Recursively clone all child nodes of type *FastNode[V]
for i, kidAny := range c.Children.Items {
if kid, ok := kidAny.(*FastNode[V]); ok {
c.Children.Items[i] = kid.CloneRec(cloneFn)
}
}
return c
}
File diff suppressed because it is too large. Load diff
+221
View File
@@ -0,0 +1,221 @@
// Copyright (c) 2026 Karl Gaissmaier
// SPDX-License-Identifier: MIT
package nodes
import (
"iter"
"github.com/gaissmai/bart/internal/bitset"
"github.com/gaissmai/bart/internal/lpm"
"github.com/gaissmai/bart/internal/sparse"
)
// LiteNode is a space-optimized version of [BartNode] that tracks prefix existence
// without storing associated values.
type LiteNode[V any] struct {
Children sparse.Array256[any]
Prefixes struct {
// BitSet256 tracks the presence of prefixes at this level.
bitset.BitSet256
// Count maintains the current number of set bits, updated on modification
// to avoid expensive population counting.
Count uint16
}
}
// IsEmpty returns true if the node contains no routing entries (prefixes)
// and no child nodes. Empty nodes are candidates for compression or removal
// during trie optimization.
func (n *LiteNode[V]) IsEmpty() bool {
if n == nil {
return true
}
return n.Prefixes.Count == 0 && n.Children.Len() == 0
}
// PrefixCount returns the number of prefixes stored in this node.
func (n *LiteNode[V]) PrefixCount() int {
return int(n.Prefixes.Count)
}
// ChildCount returns the number of slots used in this node.
func (n *LiteNode[V]) ChildCount() int {
return n.Children.Len()
}
// InsertPrefix adds a routing entry at the specified index.
// It returns true if a prefix already existed at that index,
// false if this is a new insertion.
func (n *LiteNode[V]) InsertPrefix(idx uint8, _ V) (exists bool) {
if exists = n.Prefixes.Test(idx); exists {
return exists
}
n.Prefixes.Set(idx)
n.Prefixes.Count++
return exists
}
func (n *LiteNode[V]) GetPrefix(idx uint8) (_ V, exists bool) {
// no docstring by intention
exists = n.Prefixes.Test(idx)
return
}
func (n *LiteNode[V]) MustGetPrefix(idx uint8) (_ V) {
// no docstring by intention
return
}
// AllIndices returns an iterator over all prefix entries.
// Each iteration yields the prefix index (uint8) and its associated value (V).
func (n *LiteNode[V]) AllIndices() iter.Seq2[uint8, V] {
var zero V
return func(yield func(uint8, V) bool) {
var buf [256]uint8
for _, idx := range n.Prefixes.AsSlice(&buf) {
if !yield(idx, zero) {
return
}
}
}
}
// DeletePrefix removes the prefix at the specified index.
// Returns true if the prefix existed, and false otherwise.
func (n *LiteNode[V]) DeletePrefix(idx uint8) (exists bool) {
if exists = n.Prefixes.Test(idx); !exists {
return false
}
n.Prefixes.Clear(idx)
n.Prefixes.Count--
return true
}
// InsertChild adds a child node at the specified address (0-255).
// The child can be a *LiteNode[V], *LeafNode, or *FringeNode.
// Returns true if a child already existed at that address.
func (n *LiteNode[V]) InsertChild(addr uint8, child any) (exists bool) {
_, exists = n.Children.InsertAt(addr, child)
return
}
// GetChild retrieves the child node at the specified address.
// Returns the child and true if found, or nil and false if not present.
func (n *LiteNode[V]) GetChild(addr uint8) (any, bool) {
return n.Children.Get(addr)
}
// MustGetChild retrieves the child at the specified address, panicking if not found.
// This method should only be used when the caller is certain the child exists.
func (n *LiteNode[V]) MustGetChild(addr uint8) any {
return n.Children.MustGet(addr)
}
// AllChildren returns an iterator over all child nodes.
// Each iteration yields the child's address (uint8) and the child node (any).
func (n *LiteNode[V]) AllChildren() iter.Seq2[uint8, any] {
return func(yield func(addr uint8, child any) bool) {
var buf [256]uint8
addrs := n.Children.AsSlice(&buf)
for i, addr := range addrs {
if !yield(addr, n.Children.Items[i]) {
return
}
}
}
}
// DeleteChild removes the child node at the specified address.
// This operation is idempotent - removing a non-existent child is safe.
func (n *LiteNode[V]) DeleteChild(addr uint8) (exists bool) {
_, exists = n.Children.DeleteAt(addr)
return exists
}
// Contains returns true if an index (idx) has any matching longest-prefix
// in the current node’s prefix table.
//
// This function performs a presence check.
//
// The prefix table is structured as a complete binary tree (CBT), and LPM testing
// is done via a bitset operation that maps the traversal path from the given index
// toward its possible ancestors.
func (n *LiteNode[V]) Contains(idx uint8) bool {
return n.Prefixes.Intersects(&lpm.LookupTbl[idx])
}
// LookupIdx performs a longest-prefix match (LPM) lookup for the given index (idx)
// within the 8-bit stride-based prefix table at this trie depth.
//
// The function returns the matched index and whether a matching prefix
// exists at this level. The value type parameter exists only to satisfy interfaces.
//
// Internally, the prefix table is organized as a complete binary tree (CBT) indexed
// via the baseIndex function. Unlike the original ART algorithm, this implementation
// does not use an allotment-based approach. Instead, it performs CBT backtracking
// using a bitset-based operation with a precomputed backtracking pattern specific to idx.
func (n *LiteNode[V]) LookupIdx(idx uint8) (top uint8, _ V, ok bool) {
top, ok = n.Prefixes.IntersectionTop(&lpm.LookupTbl[idx])
return
}
// Lookup is just a simple wrapper for LookupIdx.
func (n *LiteNode[V]) Lookup(idx uint8) (_ V, ok bool) {
_, _, ok = n.LookupIdx(idx)
return
}
// CloneFlat returns a shallow copy of the current node.
//
// CloneFn is only used for interface satisfaction.
func (n *LiteNode[V]) CloneFlat(_ func(V) V) *LiteNode[V] {
if n == nil {
return nil
}
c := new(LiteNode[V])
if n.IsEmpty() {
return c
}
// copy simple values
c.Prefixes = n.Prefixes
// sparse array
c.Children = *(n.Children.Copy())
// no values to copy
return c
}
// CloneRec performs a recursive deep copy of the node and all its descendants.
//
// cloneFn is only used for interface satisfaction.
//
// It first creates a shallow clone of the current node using CloneFlat.
// Then it recursively clones all child nodes of type *LiteNode[V],
// performing a full deep clone down the subtree.
//
// Child nodes of type *LeafNode and *FringeNode are already copied
// by CloneFlat.
//
// Returns a new instance of LiteNode[V] which is a complete deep clone of the
// receiver node with all descendants.
func (n *LiteNode[V]) CloneRec(_ func(V) V) *LiteNode[V] {
if n == nil {
return nil
}
// Perform a flat clone of the current node.
c := n.CloneFlat(nil)
// Recursively clone all child nodes of type *LiteNode[V]
for i, kidAny := range c.Children.Items {
if kid, ok := kidAny.(*LiteNode[V]); ok {
c.Children.Items[i] = kid.CloneRec(nil)
}
}
return c
}
File diff suppressed because it is too large. Load diff
+337
View File
@@ -0,0 +1,337 @@
// Copyright (c) 2026 Karl Gaissmaier
// SPDX-License-Identifier: MIT
package nodes
import (
"cmp"
"fmt"
"net/netip"
"strconv"
"strings"
"github.com/gaissmai/bart/internal/art"
)
// strideLen represents the byte stride length for the multibit trie.
// Each stride processes 8 bits (1 byte) at a time.
const strideLen = 8
// MaxItems defines the maximum number of prefixes or children that can be stored in a single node.
// This corresponds to 256 possible values for an 8-bit stride.
const MaxItems = 256
// MaxTreeDepth represents the maximum depth of the trie structure.
// For IPv6 addresses, this allows up to 16 bytes of depth.
const MaxTreeDepth = 16
// DepthMask is used for bounds check elimination (BCE) when accessing depth-indexed arrays.
const DepthMask = MaxTreeDepth - 1
// StridePath represents a path through the trie, with a maximum depth of 16 octets for IPv6.
type StridePath [MaxTreeDepth]uint8
// TrieItem, a node has no path information about its predecessors,
// we collect this during the recursive descent.
type TrieItem[V any] struct {
// for traversing, Path/Depth/Idx is needed to get the CIDR back from the trie.
Node any // BartNode, FastNode, LiteNode
Is4 bool
Path StridePath
Depth int
Idx uint8
// for printing
Cidr netip.Prefix
Val V
}
// StatsT, only used for dump, tests and benchmarks
type StatsT struct {
Prefixes int
Children int
SubNodes int
Leaves int
Fringes int
}
type nodeType byte
const (
nullNode nodeType = iota // empty node
fullNode // prefixes and children or path-compressed prefixes
halfNode // no prefixes, only children and path-compressed prefixes
pathNode // only children, no prefix nor path-compressed prefixes
stopNode // no children, only prefixes or path-compressed prefixes
)
// String implements Stringer for nodeType.
func (nt nodeType) String() string {
switch nt {
case nullNode:
return "NULL"
case fullNode:
return "FULL"
case halfNode:
return "HALF"
case pathNode:
return "PATH"
case stopNode:
return "STOP"
default:
return "unreachable"
}
}
// addrFmt, different format strings for IPv4 and IPv6, decimal versus hex.
func addrFmt(addr byte, is4 bool) string {
if is4 {
return fmt.Sprintf("%d", addr)
}
return fmt.Sprintf("0x%02x", addr)
}
// ip stride path, different formats for IPv4 and IPv6, dotted decimal or hex.
//
// 127.0.0
// 2001:0d
func ipStridePath(path StridePath, depth int, is4 bool) string {
buf := new(strings.Builder)
if is4 {
for i, b := range path[:depth] {
if i != 0 {
buf.WriteString(".")
}
buf.WriteString(strconv.Itoa(int(b)))
}
return buf.String()
}
for i, b := range path[:depth] {
if i != 0 && i%2 == 0 {
buf.WriteString(":")
}
fmt.Fprintf(buf, "%02x", b)
}
return buf.String()
}
// CmpPrefix, helper function, compare func for prefix sort,
// all cidrs are already normalized
func CmpPrefix(a, b netip.Prefix) int {
if cmpAddr := a.Addr().Compare(b.Addr()); cmpAddr != 0 {
return cmpAddr
}
return cmp.Compare(a.Bits(), b.Bits())
}
// LeafNode represents a path-compressed routing entry that stores both prefix and value.
// Leaf nodes are used when a prefix doesn't align with trie stride boundaries
// and needs to be stored as a compressed path to save memory.
type LeafNode[V any] struct {
Value V
Prefix netip.Prefix
}
// NewLeafNode creates a new leaf node with the specified prefix and value.
func NewLeafNode[V any](pfx netip.Prefix, val V) *LeafNode[V] {
return &LeafNode[V]{Prefix: pfx, Value: val}
}
// FringeNode represents a path-compressed routing entry that stores only a value.
// The prefix is implicitly defined by the node's position in the trie.
// Fringe nodes are used for prefixes that align exactly with stride boundaries
// (/8, /16, /24, etc.) to save memory by not storing redundant prefix information.
type FringeNode[V any] struct {
Value V
}
// NewFringeNode creates a new fringe node with the specified value.
func NewFringeNode[V any](val V) *FringeNode[V] {
return &FringeNode[V]{Value: val}
}
// IsFringe determines whether a prefix qualifies as a "FringeNode".
// Only prefixes that are stride-aligned (i.e., /8, /16, ..., /128)
// can be fringe-compressed. If these prefixes are inserted at a position
// where depth == (strideCount-1), they are treated as FringeNodes;
// at positions where depth < (strideCount-1), they are treated as LeafNodes.
//
// Example for a stride-aligned prefix like 192.168.1.0/24 (strideCount = 3, modBits = 0):
//
// depth = 3, depth == strideCount : A direct prefix with 0/0 (default route for subtrie).
// depth = 2, depth == (strideCount-1) : A path-compressed fringe.
// depth < 2, depth < (strideCount-1) : A path-compressed leaf.
func IsFringe(depth int, pfxLen int) bool {
strideCount, modBits := DivMod8(pfxLen)
return depth == strideCount-1 && modBits == 0
}
// CmpIndexRank, sort indexes in prefix sort order.
func CmpIndexRank(aIdx, bIdx uint8) int {
// convert idx [1..255] to prefix
aOctet, aBits := art.IdxToPfx(aIdx)
bOctet, bBits := art.IdxToPfx(bIdx)
// cmp the prefixes, first by address and then by bits
if aOctet == bOctet {
return cmp.Compare(aBits, bBits)
}
return cmp.Compare(aOctet, bOctet)
}
// CidrFromPath reconstructs a CIDR prefix from a stride path, depth, and index.
// The prefix is determined by the node's position in the trie and the base index
// from the ART algorithm's complete binary tree representation.
//
// Parameters:
// - path: The stride path through the trie
// - depth: Current depth in the trie
// - is4: True for IPv4 processing, false for IPv6
// - idx: The base index from the prefix table
//
// Returns the reconstructed netip.Prefix.
func CidrFromPath(path StridePath, depth int, is4 bool, idx uint8) netip.Prefix {
depth &= DepthMask // BCE
// retrieve the last octet and pfxLen
octet, pfxLen := art.IdxToPfx(idx)
// set byte in path at depth with last octet
path[depth] = octet
// canonicalize
clear(path[depth+1:])
// make ip addr from octets
var ip netip.Addr
if is4 {
ip = netip.AddrFrom4([4]byte(path[:4]))
} else {
ip = netip.AddrFrom16(path)
}
// calc bits with pathLen and pfxLen
bits := depth<<3 + int(pfxLen)
// PrefixFrom does not allocate and does not mask off the host bits of ip.
// With the clear(), the non-canonical bytes have already been removed.
return netip.PrefixFrom(ip, bits)
}
// CidrForFringe reconstructs a CIDR prefix for a fringe node from the traversal path.
// Since fringe nodes don't store their prefix explicitly, it's derived entirely
// from the node's position in the trie and its final byte value.
//
// Parameters:
// - octets: The path of previous bytes leading up to the fringe.
// - depth: Current depth in the trie (which equals strideCount - 1).
// - is4: True for IPv4 processing, false for IPv6.
// - fringeByte: The actual 8-bit value (0-255) of the prefix at this final stride.
//
// Returns the reconstructed netip.Prefix for the fringe.
func CidrForFringe(octets []byte, depth int, is4 bool, fringeByte uint8) netip.Prefix {
depth &= DepthMask // BCE
var path StridePath
copy(path[:], octets)
path[depth] = fringeByte
// canonicalize, fringe bit boundaries are always a multiple of a byte
clear(path[depth+1:])
// make ip addr from octets
var ip netip.Addr
if is4 {
ip = netip.AddrFrom4([4]byte(path[:4]))
} else {
ip = netip.AddrFrom16(path)
}
// it's a fringe, bits are always aligned on stride boundaries (/8, /16, /24, ...)
bits := (depth + 1) << 3
// PrefixFrom does not allocate and does not mask off the host bits of ip.
// With the clear(), the non-canonical bytes have already been removed.
return netip.PrefixFrom(ip, bits)
}
// DivMod8 returns the count of full 8‑bit strides (bits/8)
// and the remaining bits in the final stride (bits%8) for pfxLen.
//
// ATTENTION: Split the IP prefixes at 8-bit borders, count from 0.
//
// /7, /15, /23, /31, ..., /127
//
// BitPos: [0-7],[8-15],[16-23],[24-31],[32]
// BitPos: [0-7],[8-15],[16-23],[24-31],[32-39],[40-47],[48-55],[56-63],...,[120-127],[128]
//
// 0.0.0.0/0 => strideCount: 0, modBits: 0 (default route)
// 0.0.0.0/7 => strideCount: 0, modBits: 7
// 0.0.0.0/8 => strideCount: 1, modBits: 0 (fringe candidate)
// 10.0.0.0/8 => strideCount: 1, modBits: 0 (fringe candidate)
// 10.0.0.0/22 => strideCount: 2, modBits: 6
// 10.0.0.0/29 => strideCount: 3, modBits: 5
// 10.0.0.0/32 => strideCount: 4, modBits: 0 (fringe candidate)
//
// ::/0 => strideCount: 0, modBits: 0 (default route)
// ::1/128 => strideCount: 16, modBits: 0 (fringe candidate)
// 2001:db8::/42 => strideCount: 5, modBits: 2
// 2001:db8::/56 => strideCount: 7, modBits: 0 (fringe candidate)
//
// /32 and /128 prefixes are special, they never form a new node,
// At the end of the trie (IPv4: depth 4, IPv6: depth 16) they are always
// inserted as a path‑compressed fringe.
//
// We are not splitting at /8, /16, ..., because this would mean that the
// first node would have 512 prefixes, 9 bits from [0-8]. All remaining nodes
// would then only have 8 bits from [9-16], [17-24], [25..32], ...
// but the algorithm would then require a variable length bitset
// or imply a double-sized bitset.
//
// If you can commit to a fixed size of [4]uint64, then the algorithm is
// much faster due to modern CPUs.
//
// Perhaps a future Go version that supports SIMD instructions for the [4]uint64 vectors
// will make the algorithm even faster on suitable hardware.
func DivMod8(pfxLen int) (strideCount int, modBits uint8) {
// strideCount: range from 0..4 or 0..16
// modBits: range from 0..7
return pfxLen >> 3, uint8(pfxLen & 7)
}
// CloneLeaf creates and returns a copy of the leafNode receiver.
// If cloneFn is nil, the value is copied directly without modification.
// Otherwise, cloneFn is applied to the value for deep cloning.
// The prefix field is always copied as is.
func (l *LeafNode[V]) CloneLeaf(cloneFn func(V) V) *LeafNode[V] {
if l == nil {
return nil
}
if cloneFn == nil {
return &LeafNode[V]{Prefix: l.Prefix, Value: l.Value}
}
return &LeafNode[V]{Prefix: l.Prefix, Value: cloneFn(l.Value)}
}
// CloneFringe creates and returns a copy of the FringeNode receiver.
// If cloneFn is nil, the value is copied directly without modification.
// Otherwise, cloneFn is applied to the value for deep cloning.
func (l *FringeNode[V]) CloneFringe(cloneFn func(V) V) *FringeNode[V] {
if l == nil {
return nil
}
if cloneFn == nil {
return &FringeNode[V]{Value: l.Value}
}
return &FringeNode[V]{Value: cloneFn(l.Value)}
}
-160
View File
@@ -1,160 +0,0 @@
// Copyright (c) 2024 Karl Gaissmaier
// SPDX-License-Identifier: MIT
// package sparse implements a generic sparse array
// with popcount compression.
package sparse
import (
"github.com/gaissmai/bart/internal/bitset"
)
// Array, a generic implementation of a sparse array
// with popcount compression and payload T.
type Array[T any] struct {
bitset.BitSet
Items []T
}
// Get the value at i from sparse array.
//
// example: Array.Get(5) -> Array.Items[1]
//
// ⬇
// BitSet: [0|0|1|0|0|1|0|1|...] <- 3 bits set
// Items: [*|*|*] <- len(Items) = 3
// ⬆
//
// BitSet.Test(5): true
// BitSet.popcount(5): 2, for interval [0,5]
// BitSet.Rank0(5): 1, equal popcount(5)-1
func (s *Array[T]) Get(i uint) (value T, ok bool) {
if s.Test(i) {
return s.Items[s.Rank0(i)], true
}
return
}
// MustGet, use it only after a successful test
// or the behavior is undefined, maybe it panics.
func (s *Array[T]) MustGet(i uint) T {
return s.Items[s.Rank0(i)]
}
// UpdateAt or set the value at i via callback. The new value is returned
// and true if the value was already present.
func (s *Array[T]) UpdateAt(i uint, cb func(T, bool) T) (newValue T, wasPresent bool) {
var rank0 int
// if already set, get current value
var oldValue T
if wasPresent = s.Test(i); wasPresent {
rank0 = s.Rank0(i)
oldValue = s.Items[rank0]
}
// callback function to get updated or new value
newValue = cb(oldValue, wasPresent)
// already set, update and return value
if wasPresent {
s.Items[rank0] = newValue
return newValue, wasPresent
}
// new value, insert into bitset ...
s.BitSet = s.Set(i)
// bitset has changed, recalc rank
rank0 = s.Rank0(i)
// ... and insert value into slice
s.insertItem(rank0, newValue)
return newValue, wasPresent
}
// Len returns the number of items in sparse array.
func (s *Array[T]) Len() int {
return len(s.Items)
}
// Copy returns a shallow copy of the Array.
// The elements are copied using assignment, this is no deep clone.
func (s *Array[T]) Copy() *Array[T] {
if s == nil {
return nil
}
return &Array[T]{
BitSet: s.BitSet.Clone(),
Items: append(s.Items[:0:0], s.Items...),
}
}
// InsertAt a value at i into the sparse array.
// If the value already exists, overwrite it with val and return true.
func (s *Array[T]) InsertAt(i uint, value T) (exists bool) {
// slot exists, overwrite value
if s.Len() != 0 && s.Test(i) {
s.Items[s.Rank0(i)] = value
return true
}
// new, insert into bitset ...
s.BitSet = s.Set(i)
// ... and slice
s.insertItem(s.Rank0(i), value)
return false
}
// DeleteAt a value at i from the sparse array, zeroes the tail.
func (s *Array[T]) DeleteAt(i uint) (value T, exists bool) {
if s.Len() == 0 || !s.Test(i) {
return
}
rank0 := s.Rank0(i)
value = s.Items[rank0]
// delete from slice
s.deleteItem(rank0)
// delete from bitset
s.BitSet = s.Clear(i)
return value, true
}
// insertItem inserts the item at index i, shift the rest one pos right
//
// It panics if i is out of range.
func (s *Array[T]) insertItem(i int, item T) {
if len(s.Items) < cap(s.Items) {
s.Items = s.Items[:len(s.Items)+1] // fast resize, no alloc
} else {
var zero T
s.Items = append(s.Items, zero) // append one item, mostly enlarge cap by more than one item
}
copy(s.Items[i+1:], s.Items[i:])
s.Items[i] = item
}
// deleteItem at index i, shift the rest one pos left and clears the tail item
//
// It panics if i is out of range.
func (s *Array[T]) deleteItem(i int) {
var zero T
nl := len(s.Items) - 1 // new len
copy(s.Items[i:], s.Items[i+1:]) // overwrite item at [i]
s.Items[nl] = zero // clear the tail item
s.Items = s.Items[:nl] // new len, keep cap is unchanged
}
+220
View File
@@ -0,0 +1,220 @@
// Copyright (c) 2026 Karl Gaissmaier
// SPDX-License-Identifier: MIT
// Package sparse provides a compact and efficient sparse array
// implementation for addressable key ranges from [0..255].
//
// It is optimized for use in radix tries and lookup structures
// where only a small subset of possible keys contain actual data.
//
// Internally, an Array256 combines a fixed-size BitSet256 with
// a compact packed Items slice. The bitset tracks which key slots
// are occupied and enables fast index mapping via popcount (Rank),
// while the Items slice stores the associated payloads.
//
// Lookup, insertion, and deletion operate in O(1) time (worst-case O(n)
// only for shifting/copying in extremely full arrays).
//
// This module avoids memory allocations where possible and provides
// predictable performance even under heavy insert/delete workloads.
package sparse
import (
"github.com/gaissmai/bart/internal/bitset"
)
// Array256 is a popcount-compressed sparse array for up to 256 item slots.
//
// Internally, it consists of:
// - a BitSet256, where each set bit marks a present slot (i.e., key i used)
// - a compact Items slice, which stores the payloads only for the set bits
//
// Each occupied index i ∈ [0..255] can be tested using .Test(i).
// The actual index within the Items slice is computed via .Rank(i)-1.
//
// All insert/delete operations update both the bitset and item slice
// to preserve this mapping invariant. Direct mutation of the underlying
// bitset (via Set or Clear) is forbidden and will panic.
//
// This structure allows extremely fast and compact prefix-based routing,
// highly efficient child-node containers in prefix tries, or memory-safe
// alternatives to map[uint8]T with deterministic iteration.
//
// Example layout:
//
// BitSet256: [0 1 0 0 0 1 ...] // bits 1 and 5 set
// Items: [T1, T2] // a.Items[0] ↔ key 1, a.Items[1] ↔ key 5
type Array256[T any] struct {
bitset.BitSet256
Items []T
}
// Set panics. The bitset is internally coupled with Items[].
// Use InsertAt to add or overwrite at index i.
func (a *Array256[T]) Set(uint8) {
panic("forbidden, use InsertAt")
}
// Clear panics. The bitset is internally coupled with Items[].
func (a *Array256[T]) Clear(uint8) {
panic("forbidden, use DeleteAt")
}
// Get returns the value at index i and whether it exists.
//
// If the bit for i is not set, ok is false and value is the zero-value of T.
//
// example: a.Get(5) -> a.Items[1]
//
// ⬇
// BitSet256: [0|0|1|0|0|1|0|...|1] <- 3 bits set
// Items: [*|*|*] <- len(Items) = 3
// ⬆
//
// BitSet256.Test(5): true
// BitSet256.Rank(5): 2
func (a *Array256[T]) Get(i uint8) (value T, ok bool) {
if a.Test(i) {
return a.Items[a.Rank(i)-1], true
}
return
}
// MustGet returns the value at index i without checking if it exists.
//
// Use only after ensuring i is set (via Test(i)); otherwise it may return
// an incorrect value or panic. Intended only for tight, validated loops.
func (a *Array256[T]) MustGet(i uint8) T {
return a.Items[a.Rank(i)-1]
}
// Len returns the number of items in sparse array.
func (a *Array256[T]) Len() int {
return len(a.Items)
}
// Copy returns a shallow copy of the Array.
// The elements are copied using assignment, this is no deep clone.
func (a *Array256[T]) Copy() *Array256[T] {
if a == nil {
return nil
}
c := &Array256[T]{
BitSet256: a.BitSet256,
Items: make([]T, len(a.Items)),
}
copy(c.Items, a.Items)
return c
}
// InsertAt inserts or overwrites the value at sparse index i.
//
// If the slot at i already exists, its value is overwritten in-place and
// (rank0, true) is returned, where rank0 = Rank(i)-1 is the 0-based index
// into Items[].
//
// If the slot is new, the bit for i is set, the value is inserted into Items[]
// at the correct packed position, and (rank0, false) is returned.
//
// rank0 can be cached by the caller to directly access Items[rank0]
// without a second Rank() call.
func (a *Array256[T]) InsertAt(i uint8, value T) (rank0 int, exists bool) {
// slot exists, just overwrite value
if a.Test(i) {
rank0 = a.Rank(i) - 1
a.Items[rank0] = value
return rank0, true
}
// Since i is not set yet, Rank(i) before Set(i) is exactly
// the index where the new item should be inserted (equivalent to Rank(i)-1 after Set(i)).
rank0 = a.Rank(i)
// new, insert into bitset ...
a.BitSet256.Set(i)
// ... and insert value into slice
a.insertItem(rank0, value)
return rank0, false
}
// DeleteAt removes the value at index i from the sparse array,
// shifting remaining items down in the slice and clearing the bit.
//
// If the entry exists, it is returned together with true.
// If i is not present, the zero value and false are returned.
func (a *Array256[T]) DeleteAt(i uint8) (value T, exists bool) {
if a.Len() == 0 || !a.Test(i) {
return value, exists
}
rank0 := a.Rank(i) - 1
value = a.Items[rank0]
// delete from slice
a.deleteItem(rank0)
// delete from bitset
a.BitSet256.Clear(i)
return value, true
}
// insertItem inserts a new element at the given index position i in the Items slice,
// shifting all following elements one position to the right to make space.
//
// This method must be called with the correct insertion index - that is,
// the rank-0 value of the corresponding bit index i in BitSet256 (calculated before Set(i)).
//
// The slice will be extended by one element. If the capacity allows, this is done
// without reallocation (fast path); otherwise slice growth occurs (slow path).
//
// Example (inserting at rank0 == 2):
//
// Items before: [A B C D]
// After insertItem(2, X): [A B X C D]
//
// Panics if i is out of range (i < 0 or i > len(Items)).
func (a *Array256[T]) insertItem(i int, item T) {
if len(a.Items) < cap(a.Items) {
a.Items = a.Items[:len(a.Items)+1] // fast resize, no alloc
} else {
var zero T
a.Items = append(a.Items, zero) // append one item, mostly enlarge cap by more than one item
}
_ = a.Items[i] // BCE
copy(a.Items[i+1:], a.Items[i:]) // shift one slot right, starting at [i]
a.Items[i] = item // insert new item at [i]
}
// deleteItem removes the item at index i from the Items slice,
// shifting all subsequent items one position to the left,
// and clearing the final (now duplicate) slot.
//
// This is used to remove Items[i] in response to a BitSet256.Clear(i)
// operation, where i is guaranteed to be set and valid.
//
// Example (deleting rank0 == 1):
//
// Items before: [A B C D]
// After deleteItem(1): [A C D]
//
// The tail item is explicitly cleared (assigned the zero-value of T)
// to avoid holding references (important for GC with pointer types).
//
// Panics if i is out of range (i < 0 or i >= len(Items)).
func (a *Array256[T]) deleteItem(i int) {
var zero T
_ = a.Items[i] // BCE
copy(a.Items[i:], a.Items[i+1:]) // shift left, overwrite item at [i]
nl := len(a.Items) - 1 // new len
a.Items[nl] = zero // clear the tail item
a.Items = a.Items[:nl] // new len, cap is unchanged
}
+207
View File
@@ -0,0 +1,207 @@
// Copyright (c) 2026 Karl Gaissmaier
// SPDX-License-Identifier: MIT
package golden
import (
"cmp"
"fmt"
"net/netip"
"slices"
)
// Table is a simple and slow route table, implemented as a slice of prefixes
// and values as a golden reference for bart.
type Table[V any] []TableItem[V]
type TableItem[V any] struct {
Pfx netip.Prefix
Val V
}
func (g TableItem[V]) String() string {
return fmt.Sprintf("(%s, %v)", g.Pfx, g.Val)
}
func (t *Table[V]) Insert(pfx netip.Prefix, val V) {
pfx = pfx.Masked()
for i, item := range *t {
if item.Pfx == pfx {
(*t)[i].Val = val // de-dupe
return
}
}
*t = append(*t, TableItem[V]{pfx, val})
}
func (t *Table[V]) Delete(pfx netip.Prefix) (exists bool) {
pfx = pfx.Masked()
for i, item := range *t {
if item.Pfx == pfx {
*t = slices.Delete(*t, i, i+1)
return true
}
}
return false
}
func (t Table[V]) AllSorted() []netip.Prefix {
var result []netip.Prefix
for _, item := range t {
result = append(result, item.Pfx)
}
slices.SortFunc(result, cmpPrefix)
return result
}
func (t Table[V]) Get(pfx netip.Prefix) (val V, ok bool) {
pfx = pfx.Masked()
for _, item := range t {
if item.Pfx == pfx {
return item.Val, true
}
}
return val, false
}
func (t *Table[V]) Update(pfx netip.Prefix, cb func(V, bool) V) (val V) {
pfx = pfx.Masked()
for i, item := range *t {
if item.Pfx == pfx {
// update val
val = cb(item.Val, true)
(*t)[i].Val = val
return val
}
}
// new val
val = cb(val, false)
*t = append(*t, TableItem[V]{pfx, val})
return val
}
func (ta *Table[V]) Union(tb *Table[V]) {
for _, bItem := range *tb {
var match bool
for i, aItem := range *ta {
if aItem.Pfx == bItem.Pfx {
(*ta)[i] = bItem
match = true
break
}
}
if !match {
*ta = append(*ta, bItem)
}
}
}
func (t Table[V]) Lookup(addr netip.Addr) (val V, ok bool) {
bestLen := -1
for _, item := range t {
if item.Pfx.Contains(addr) && item.Pfx.Bits() > bestLen {
val = item.Val
ok = true
bestLen = item.Pfx.Bits()
}
}
return val, ok
}
func (t Table[V]) LookupPrefix(pfx netip.Prefix) (val V, ok bool) {
pfx = pfx.Masked()
bestLen := -1
for _, item := range t {
if item.Pfx.Overlaps(pfx) && item.Pfx.Bits() <= pfx.Bits() && item.Pfx.Bits() > bestLen {
val = item.Val
ok = true
bestLen = item.Pfx.Bits()
}
}
return val, ok
}
func (t Table[V]) LookupPrefixLPM(pfx netip.Prefix) (lpm netip.Prefix, val V, ok bool) {
pfx = pfx.Masked()
bestLen := -1
for _, item := range t {
if item.Pfx.Overlaps(pfx) && item.Pfx.Bits() <= pfx.Bits() && item.Pfx.Bits() > bestLen {
val = item.Val
lpm = item.Pfx
ok = true
bestLen = item.Pfx.Bits()
}
}
return lpm, val, ok
}
func (t Table[V]) Subnets(pfx netip.Prefix) []netip.Prefix {
pfx = pfx.Masked()
var result []netip.Prefix
for _, item := range t {
if pfx.Overlaps(item.Pfx) && pfx.Bits() <= item.Pfx.Bits() {
result = append(result, item.Pfx)
}
}
slices.SortFunc(result, cmpPrefix)
return result
}
func (t Table[V]) Supernets(pfx netip.Prefix) []netip.Prefix {
pfx = pfx.Masked()
var result []netip.Prefix
for _, item := range t {
if item.Pfx.Overlaps(pfx) && item.Pfx.Bits() <= pfx.Bits() {
result = append(result, item.Pfx)
}
}
slices.SortFunc(result, cmpPrefix)
slices.Reverse(result)
return result
}
func (t Table[V]) OverlapsPrefix(pfx netip.Prefix) bool {
pfx = pfx.Masked()
for _, p := range t {
if p.Pfx.Overlaps(pfx) {
return true
}
}
return false
}
func (ta *Table[V]) Overlaps(tb *Table[V]) bool {
for _, aItem := range *ta {
for _, bItem := range *tb {
if aItem.Pfx.Overlaps(bItem.Pfx) {
return true
}
}
}
return false
}
// Sort, inplace by netip.Prefix, all prefixes are in normalized form
func (t *Table[V]) Sort() {
slices.SortFunc(*t, func(a, b TableItem[V]) int {
return cmpPrefix(a.Pfx, b.Pfx)
})
}
// cmpPrefix, helper function, compare func for prefix sort,
// all cidrs are already normalized
func cmpPrefix(a, b netip.Prefix) int {
if cmpAddr := a.Addr().Compare(b.Addr()); cmpAddr != 0 {
return cmpAddr
}
return cmp.Compare(a.Bits(), b.Bits())
}
+129
View File
@@ -0,0 +1,129 @@
// Copyright (c) 2026 Karl Gaissmaier
// SPDX-License-Identifier: MIT
package random
import (
"fmt"
"math/rand/v2"
"net/netip"
)
// mpp is a abbreviation and panics on non masked prefixes.
//
//nolint:gochecknoglobals
var mpp = func(s string) netip.Prefix {
pfx := netip.MustParsePrefix(s)
if pfx == pfx.Masked() {
return pfx
}
panic(fmt.Sprintf("%s is not canonicalized as %s", s, pfx.Masked()))
}
// Prefix returns a randomly generated prefix
func Prefix(prng *rand.Rand) netip.Prefix {
if prng.IntN(2) == 1 {
return Prefix4(prng)
}
return Prefix6(prng)
}
func Prefix4(prng *rand.Rand) netip.Prefix {
bits := prng.IntN(33)
return netip.PrefixFrom(IP4(prng), bits).Masked()
}
func Prefix6(prng *rand.Rand) netip.Prefix {
bits := prng.IntN(129)
return netip.PrefixFrom(IP6(prng), bits).Masked()
}
func IP4(prng *rand.Rand) netip.Addr {
var b [4]byte
for i := range b {
//nolint:gosec // G115: integer overflow conversion uint -> byte
b[i] = byte(prng.UintN(256))
}
return netip.AddrFrom4(b)
}
func IP6(prng *rand.Rand) netip.Addr {
var b [16]byte
for i := range b {
//nolint:gosec // G115: integer overflow conversion uint -> byte
b[i] = byte(prng.UintN(256))
}
return netip.AddrFrom16(b)
}
func IP(prng *rand.Rand) netip.Addr {
if prng.IntN(2) == 1 {
return IP4(prng)
}
return IP6(prng)
}
func RealWorldPrefixes4(prng *rand.Rand, n int) []netip.Prefix {
set := make(map[netip.Prefix]struct{})
pfxs := make([]netip.Prefix, 0, n)
for len(set) < n {
pfx := Prefix4(prng)
// skip too small or too big masks
if pfx.Bits() < 8 || pfx.Bits() > 28 {
continue
}
// skip reserved/experimental ranges (e.g., 240.0.0.0/8)
if pfx.Overlaps(mpp("240.0.0.0/8")) {
continue
}
if _, ok := set[pfx]; !ok {
set[pfx] = struct{}{}
pfxs = append(pfxs, pfx)
}
}
return pfxs
}
func RealWorldPrefixes6(prng *rand.Rand, n int) []netip.Prefix {
set := make(map[netip.Prefix]struct{})
pfxs := make([]netip.Prefix, 0, n)
for len(set) < n {
pfx := Prefix6(prng)
// skip too small or too big masks
if pfx.Bits() < 16 || pfx.Bits() > 56 {
continue
}
// skip non global routes seen in the real world
if !pfx.Overlaps(mpp("2000::/3")) {
continue
}
if pfx.Addr().Compare(mpp("2c0f::/16").Addr()) == 1 {
continue
}
if _, ok := set[pfx]; !ok {
set[pfx] = struct{}{}
pfxs = append(pfxs, pfx)
}
}
return pfxs
}
func RealWorldPrefixes(prng *rand.Rand, n int) []netip.Prefix {
pfxs := make([]netip.Prefix, 0, n)
pfxs = append(pfxs, RealWorldPrefixes4(prng, n/2)...)
pfxs = append(pfxs, RealWorldPrefixes6(prng, n-len(pfxs))...)
prng.Shuffle(len(pfxs), func(i, j int) {
pfxs[i], pfxs[j] = pfxs[j], pfxs[i]
})
return pfxs
}
+77
View File
@@ -0,0 +1,77 @@
// Copyright (c) 2026 Karl Gaissmaier
// SPDX-License-Identifier: MIT
// Package value provides runtime utilities for working with generic type
// parameters as payload values in bart data structures.
//
// The package offers three main categories of utilities:
//
// # V as type struct{} Detection
//
// IsEmptyStruct[V] detects whether a type parameter V is a `struct{}`. These
// types carry no information in their values. Omitting them from dumps
// and prints reduces line noise and improves readability.
//
// # Value Equality
//
// The [Equal] function enables custom equality logic for payload values.
// When V implements an `Equal(V) bool` method, [Equal] uses that implementation,
// avoiding the potentially expensive [reflect.DeepEqual] fallback.
//
// # Value Cloning
//
// The [CloneFnFactory] supports copying of payload values for persistent
// operations. When V implements a `Clone() V` method,
// bart methods like InsertPersist, DeletePersist, and UnionPersist use the
// generated clone function to create independent copies.
//
// This is an internal package used by the bart data structure implementation.
package value
// IsEmptyStruct reports whether type V is the unnamed empty struct type `struct{}`.
func IsEmptyStruct[V any]() bool {
var zero V
_, ok := any(zero).(struct{})
return ok
}
// Equal compares two values of type V for equality.
//
// If V implements an `Equal(V) bool` method, its custom equality logic is used.
// Otherwise, values are compared directly using the == operator.
//
// If V is not comparable at runtime (such as a slice or map without an Equal
// method), a runtime panic will occur.
//
// Note: If V implements Equal(V) bool with a pointer receiver, the Equal
// method should handle nil receivers gracefully.
func Equal[V any](v1, v2 V) bool {
if eq, ok := any(v1).(interface{ Equal(V) bool }); ok {
return eq.Equal(v2)
}
return any(v1) == any(v2)
}
// CloneFnFactory returns a function that takes a value of type V and returns
// a copy by calling its `Clone` method.
//
// If V does not implement a `Clone() V` method, it returns nil.
//
// Note: If V implements `Clone() V` with a pointer receiver, the `Clone`
// method should handle nil receivers gracefully.
func CloneFnFactory[V any]() func(V) V {
var zero V
// Safely check if V implements the clone method using an inline interface.
if _, ok := any(zero).(interface{ Clone() V }); ok {
// Return an anonymous closure directly.
// Since we already proved V implements the interface above,
// the direct type assertion here is guaranteed to succeed.
return func(val V) V {
return any(val).(interface{ Clone() V }).Clone()
}
}
return nil
}
-79
View File
@@ -1,79 +0,0 @@
package bart
// Copyright (c) 2024 Karl Gaissmaier
// SPDX-License-Identifier: MIT
import (
"encoding/json"
"net/netip"
"slices"
)
// DumpListNode contains CIDR, value and list of subnets (tree childrens).
type DumpListNode[V any] struct {
CIDR netip.Prefix `json:"cidr"`
Value V `json:"value"`
Subnets []DumpListNode[V] `json:"subnets,omitempty"`
}
// MarshalJSON dumps the table into two sorted lists: for ipv4 and ipv6.
// Every root and subnet is an array, not a map, because the order matters.
func (t *Table[V]) MarshalJSON() ([]byte, error) {
if t == nil {
return nil, nil
}
result := struct {
Ipv4 []DumpListNode[V] `json:"ipv4,omitempty"`
Ipv6 []DumpListNode[V] `json:"ipv6,omitempty"`
}{
Ipv4: t.DumpList4(),
Ipv6: t.DumpList6(),
}
buf, err := json.Marshal(result)
if err != nil {
return nil, err
}
return buf, nil
}
// DumpList4 dumps the ipv4 tree into a list of roots and their subnets.
// It can be used to analyze the tree or build custom json representation.
func (t *Table[V]) DumpList4() []DumpListNode[V] {
if t == nil {
return nil
}
return t.root4.dumpListRec(0, stridePath{}, 0, true)
}
// DumpList6 dumps the ipv6 tree into a list of roots and their subnets.
// It can be used to analyze the tree or build custom json representation.
func (t *Table[V]) DumpList6() []DumpListNode[V] {
if t == nil {
return nil
}
return t.root6.dumpListRec(0, stridePath{}, 0, false)
}
func (n *node[V]) dumpListRec(parentIdx uint, path stridePath, depth int, is4 bool) []DumpListNode[V] {
// recursion stop condition
if n == nil {
return nil
}
directKids := n.getKidsRec(parentIdx, path, depth, is4)
slices.SortFunc(directKids, cmpKidByPrefix[V])
nodes := make([]DumpListNode[V], 0, len(directKids))
for _, kid := range directKids {
nodes = append(nodes, DumpListNode[V]{
CIDR: kid.cidr,
Value: kid.val,
Subnets: kid.n.dumpListRec(kid.idx, kid.path, kid.depth, is4),
})
}
return nodes
}
+680
View File
@@ -0,0 +1,680 @@
// Copyright (c) 2026 Karl Gaissmaier
// SPDX-License-Identifier: MIT
package bart
import (
"io"
"iter"
"net/netip"
"sync"
"github.com/gaissmai/bart/internal/art"
"github.com/gaissmai/bart/internal/lpm"
"github.com/gaissmai/bart/internal/nodes"
)
// Lite follows the BART design but with no payload.
// It is ideal for simple IP ACLs (access-control-lists) with plain
// true/false results with the smallest memory consumption.
//
// The zero value is ready to use.
//
// A Lite table must not be copied by value; always pass by pointer.
// Nil pointers as receivers or arguments are forbidden and will panic.
//
// Performance note: Do not pass IPv4-in-IPv6 addresses (e.g., ::ffff:192.0.2.1)
// as input. The methods do not perform automatic unmapping to avoid unnecessary
// overhead for the common case where native addresses are used.
// Users should unmap IPv4-in-IPv6 addresses to their native IPv4 form
// (e.g., 192.0.2.1) before calling these methods.
type Lite struct {
liteTable[struct{}]
}
// BEGIN OF liteTable WRAPPER
// Get performs an exact-prefix lookup and returns whether the exact
// prefix exists. The prefix is canonicalized (Masked) before lookup.
//
// This is an exact-match operation (no LPM). The prefix must match exactly
// in both address and prefix length to be found.
// If pfx is valid and exists, true is returned, otherwise false.
//
// For longest-prefix-match (LPM) lookups, use Contains(ip), Lookup(ip),
// LookupPrefix(pfx) or LookupPrefixLPM(pfx) instead.
func (l *Lite) Get(pfx netip.Prefix) bool {
_, ok := l.liteTable.Get(pfx)
return ok
}
// Lookup performs a longest-prefix-match (LPM) for addr.
//
// Note: Lite stores no payload values, so this method is rarely useful.
// Prefer Contains(addr) to check whether any prefix matches the address.
// For exact prefix existence use Get(pfx). For prefix-based LPM use
// LookupPrefix or LookupPrefixLPM.
//
// Returns true if any prefix matches addr, otherwise false.
func (l *Lite) Lookup(ip netip.Addr) bool {
return l.Contains(ip)
}
// LookupPrefix performs a longest prefix match lookup for any address within
// the given prefix.
//
// Returns true if a matching prefix is found, otherwise false.
func (l *Lite) LookupPrefix(pfx netip.Prefix) bool {
_, ok := l.lookupPrefixLPM(pfx, false)
return ok
}
// LookupPrefixLPM performs a longest prefix match lookup for any address within
// the given prefix. It finds the most specific routing table entry that would
// match any address in the provided prefix range.
//
// This is functionally identical to LookupPrefix but returns the
// matching LPM prefix itself.
//
// This method is slower than LookupPrefix and should only be used if the
// matching lpm entry is also required for other reasons.
//
// Returns the matching prefix and true if found, otherwise the zero value and false.
func (l *Lite) LookupPrefixLPM(pfx netip.Prefix) (lpmPfx netip.Prefix, ok bool) {
return l.lookupPrefixLPM(pfx, true)
}
// Insert adds a prefix to the routing table.
// If the prefix already exists, it's a no-op; otherwise a new entry is created.
// Invalid prefixes are silently ignored.
//
// The prefix is automatically canonicalized using pfx.Masked() to ensure
// consistent behavior regardless of host bits in the input.
func (l *Lite) Insert(pfx netip.Prefix) {
l.liteTable.Insert(pfx, struct{}{})
}
// InsertPersist is similar to Insert but the receiver isn't modified.
//
// All nodes touched during insert are cloned and a new *Lite is returned.
// This is not a full [Lite.Clone], all untouched nodes are still referenced
// from both Tables.
//
// This is orders of magnitude slower than Insert,
// typically taking μsec instead of nsec.
//
// The bulk table load could be done with [Lite.Insert] and then you can
// use [Lite.InsertPersist], [Lite.ModifyPersist] and [Lite.DeletePersist]
// for further lock-free ops.
func (l *Lite) InsertPersist(pfx netip.Prefix) *Lite {
lp := l.liteTable.InsertPersist(pfx, struct{}{})
//nolint:govet // copy of *lp is here by intention
return &Lite{*lp}
}
// DeletePersist is similar to Delete but does not modify the receiver.
//
// It performs a copy-on-write delete operation, cloning all nodes
// touched during deletion and returning a new *Lite reflecting the change.
//
// If the prefix is invalid or doesn't exist, the original table is
// returned unchanged.
//
// Due to cloning overhead this is significantly slower than Delete,
// typically taking μsec instead of nsec.
func (l *Lite) DeletePersist(pfx netip.Prefix) *Lite {
lp := l.liteTable.DeletePersist(pfx)
if lp == &l.liteTable {
// pfx is invalid or didn't exist
return l
}
//nolint:govet // copy of *lp is here by intention
return &Lite{*lp}
}
// Modify applies an insert, update, or delete for the given prefix.
// The prefix is canonicalized (Masked) internally before the operation.
// The operation is determined by the callback function, which is called with:
//
// true: the prefix is in table
// false: the prefix is not in table
//
// The callback returns:
//
// true: delete the entry
// false: insert or update
//
// Summary of callback semantics:
//
// | input | return | op |
// ---------------------------
// | false | true | no-op |
// | false | false | insert |
// | true | false | update |
// | true | true | delete |
// ---------------------------
func (l *Lite) Modify(pfx netip.Prefix, cb func(exists bool) (del bool)) {
// Adapt the callback to work with liteTable's signature
adaptedCb := func(_ struct{}, exists bool) (_ struct{}, del bool) {
return struct{}{}, cb(exists)
}
l.liteTable.Modify(pfx, adaptedCb)
}
// ModifyPersist is similar to Modify but the receiver isn't modified and
// a new *Lite is returned.
func (l *Lite) ModifyPersist(pfx netip.Prefix, cb func(exists bool) (del bool)) *Lite {
// wrap callback to match the signature of liteTable.ModifyPersist
cbWrapper := func(_ struct{}, exists bool) (_ struct{}, del bool) {
return struct{}{}, cb(exists)
}
lp := l.liteTable.ModifyPersist(pfx, cbWrapper)
//nolint:govet // copy of *lp is here by intention
return &Lite{*lp}
}
// dropSeq2 converts a Seq2[netip.Prefix, V] into a Seq[netip.Prefix] by discarding the value.
func dropSeq2[V any](seq2 iter.Seq2[netip.Prefix, V]) iter.Seq[netip.Prefix] {
return func(yield func(netip.Prefix) bool) {
seq2(func(p netip.Prefix, _ V) bool {
return yield(p)
})
}
}
// Clone returns a copy of the routing table.
func (l *Lite) Clone() *Lite {
return &Lite{*l.liteTable.Clone()}
}
// Union merges another routing table into the receiver table, modifying it in-place.
//
// All prefixes from the other table (o) are inserted into the receiver.
func (l *Lite) Union(o *Lite) {
l.liteTable.Union(&o.liteTable)
}
// UnionPersist is similar to [Union] but the receiver isn't modified.
//
// All nodes touched during union are cloned and a new *Lite is returned.
// If o is empty, no nodes are touched and the receiver may be
// returned unchanged.
func (l *Lite) UnionPersist(o *Lite) *Lite {
lp := l.liteTable.UnionPersist(&o.liteTable)
if lp == &l.liteTable {
return l
}
//nolint:govet // copy of *lp is here by intention
return &Lite{*lp}
}
// All returns an iterator over all prefixes in the table.
//
// The iteration order is unspecified and may vary between calls; for a stable order,
// use [Lite.AllSorted].
//
// IMPORTANT: Modifying the table during iteration is not allowed,
// as this would interfere with the internal traversal and may corrupt or
// prematurely terminate the iteration.
func (l *Lite) All() iter.Seq[netip.Prefix] {
return dropSeq2(l.liteTable.All())
}
// All4 is like [Lite.All] but only for the v4 routing table.
func (l *Lite) All4() iter.Seq[netip.Prefix] {
return dropSeq2(l.liteTable.All4())
}
// All6 is like [Lite.All] but only for the v6 routing table.
func (l *Lite) All6() iter.Seq[netip.Prefix] {
return dropSeq2(l.liteTable.All6())
}
// AllSorted is like [Lite.All] but the iteration is ordered in canonical
// CIDR prefix sort order.
func (l *Lite) AllSorted() iter.Seq[netip.Prefix] {
return dropSeq2(l.liteTable.AllSorted())
}
// AllSorted4 is like [Lite.AllSorted] but only for the v4 routing table.
func (l *Lite) AllSorted4() iter.Seq[netip.Prefix] {
return dropSeq2(l.liteTable.AllSorted4())
}
// AllSorted6 is like [Lite.AllSorted] but only for the v6 routing table.
func (l *Lite) AllSorted6() iter.Seq[netip.Prefix] {
return dropSeq2(l.liteTable.AllSorted6())
}
// Subnets returns an iterator over all subnets of the given prefix
// in natural CIDR sort order. This includes prefixes of the same length
// (exact match) and longer (more specific) prefixes that are contained
// within the given prefix.
//
// Example:
//
// for sub := range table.Subnets(netip.MustParsePrefix("10.0.0.0/8")) {
// fmt.Println("Covered:", sub)
// }
//
// The iteration can be stopped early by breaking from the range loop.
// Returns an empty iterator if the prefix is invalid.
func (l *Lite) Subnets(pfx netip.Prefix) iter.Seq[netip.Prefix] {
return dropSeq2(l.liteTable.Subnets(pfx))
}
// Supernets returns an iterator over all supernet routes that cover the given prefix pfx.
//
// The traversal searches both exact-length and shorter (less specific) prefixes that
// overlap or include pfx. Starting from the most specific position in the trie,
// it walks upward through parent nodes and yields any matching entries found at each level.
//
// The iteration order is reverse-CIDR: from longest prefix match (LPM) towards
// least-specific routes.
//
// The search is protocol-specific (IPv4 or IPv6) and stops immediately if the yield
// function returns false. If pfx is invalid, the function silently returns.
//
// This can be used to enumerate all covering supernet routes in routing-based
// policy engines, diagnostics tools, or fallback resolution logic.
//
// Example:
//
// for supernet := range table.Supernets(netip.MustParsePrefix("192.0.2.128/25")) {
// fmt.Println("Matched covering route:", supernet)
// }
func (l *Lite) Supernets(pfx netip.Prefix) iter.Seq[netip.Prefix] {
return dropSeq2(l.liteTable.Supernets(pfx))
}
// Overlaps reports whether any route in the receiver table overlaps
// with a route in the other table, in either direction.
//
// The overlap check is bidirectional: it returns true if any IP prefix
// in the receiver is covered by the other table, or vice versa.
// This includes partial overlaps, exact matches, and supernet/subnet relationships.
//
// Both IPv4 and IPv6 route trees are compared independently. If either
// tree has overlapping routes, the function returns true.
//
// This is useful for conflict detection, policy enforcement,
// or validating mutually exclusive routing domains.
//
// It is intentionally not nil-receiver safe: calling with a nil
// receiver will panic by design.
func (l *Lite) Overlaps(o *Lite) bool {
return l.liteTable.Overlaps(&o.liteTable)
}
// Overlaps4 is like [Lite.Overlaps] but for the v4 routing table only.
func (l *Lite) Overlaps4(o *Lite) bool {
return l.liteTable.Overlaps4(&o.liteTable)
}
// Overlaps6 is like [Lite.Overlaps] but for the v6 routing table only.
func (l *Lite) Overlaps6(o *Lite) bool {
return l.liteTable.Overlaps6(&o.liteTable)
}
// Equal checks whether two tables are structurally and semantically equal.
// It ensures both trees (IPv4-based and IPv6-based) have the same sizes and
// recursively compares their root nodes.
//
// Note: Lite has no payload values, so this only checks structural equality.
func (l *Lite) Equal(o *Lite) bool {
return l.liteTable.Equal(&o.liteTable)
}
// DumpList4 dumps the ipv4 tree into a list of roots and their subnets.
// It can be used to analyze the tree or build the text or JSON serialization.
func (l *Lite) DumpList4() []DumpListNode[struct{}] {
return l.liteTable.DumpList4()
}
// DumpList6 dumps the ipv6 tree into a list of roots and their subnets.
// It can be used to analyze the tree or build custom JSON representation.
func (l *Lite) DumpList6() []DumpListNode[struct{}] {
return l.liteTable.DumpList6()
}
// Fprint writes a hierarchical tree diagram of the ordered CIDRs
// with default formatted payload V to w.
//
// The order from top to bottom is in ascending order of the prefix address
// and the subtree structure is determined by the CIDRs coverage.
//
// ▼
// ├─ 10.0.0.0/8 (V)
// │ ├─ 10.0.0.0/24 (V)
// │ └─ 10.0.1.0/24 (V)
// ├─ 127.0.0.0/8 (V)
// │ └─ 127.0.0.1/32 (V)
// ├─ 169.254.0.0/16 (V)
// ├─ 172.16.0.0/12 (V)
// └─ 192.168.0.0/16 (V)
// └─ 192.168.1.0/24 (V)
// ▼
// └─ ::/0 (V)
// ├─ ::1/128 (V)
// ├─ 2000::/3 (V)
// │ └─ 2001:db8::/32 (V)
// └─ fe80::/10 (V)
func (l *Lite) Fprint(w io.Writer) error {
return l.liteTable.Fprint(w)
}
// MarshalJSON dumps the table into two sorted lists: for ipv4 and ipv6.
// Every root and subnet is an array, not a map, because the order matters.
func (l *Lite) MarshalJSON() ([]byte, error) {
return l.liteTable.MarshalJSON()
}
// MarshalText implements the [encoding.TextMarshaler] interface,
// just a wrapper for [liteTable.Fprint].
func (l *Lite) MarshalText() ([]byte, error) {
return l.liteTable.MarshalText()
}
// END OF liteTable WRAPPER
// liteTable follows the BART design but with no payload.
// It is ideal for simple IP ACLs (access-control-lists) with plain
// true/false results with the smallest memory consumption.
//
// Performance note: Do not pass IPv4-in-IPv6 addresses (e.g., ::ffff:192.0.2.1)
// as input. The methods do not perform automatic unmapping to avoid unnecessary
// overhead for the common case where native addresses are used.
// Users should unmap IPv4-in-IPv6 addresses to their native IPv4 form
// (e.g., 192.0.2.1) before calling these methods.
type liteTable[V any] struct {
// used by -copylocks checker from `go vet`.
_ [0]sync.Mutex
root4 nodes.LiteNode[V]
root6 nodes.LiteNode[V]
// the number of prefixes in the routing table
size4 int
size6 int
}
// rootNodeByVersion, root node getter for ip version.
func (l *liteTable[V]) rootNodeByVersion(is4 bool) *nodes.LiteNode[V] {
if is4 {
return &l.root4
}
return &l.root6
}
// Insert adds or updates a prefix-value pair in the routing table.
// If the prefix already exists, its value is updated; otherwise a new entry is created.
// Invalid prefixes are silently ignored.
//
// The prefix is automatically canonicalized using pfx.Masked() to ensure
// consistent behavior regardless of host bits in the input.
func (l *liteTable[V]) Insert(pfx netip.Prefix, val V) {
l.insert(pfx, val)
}
// InsertPersist is similar to Insert but the receiver isn't modified.
//
// All nodes touched during insert are cloned and a new liteTable is returned.
// This is not a full [liteTable.Clone], all untouched nodes are still referenced
// from both Tables.
//
// Due to cloning overhead this is significantly slower than Insert,
// typically taking μsec instead of nsec.
func (l *liteTable[V]) InsertPersist(pfx netip.Prefix, val V) *liteTable[V] {
return l.insertPersist(pfx, val)
}
// Modify applies an insert, update, or delete operation for the value
// associated with the given prefix. The supplied callback decides the
// operation: it is called with the current value (or zero if not found)
// and a boolean indicating whether the prefix exists. The callback must
// return a new value and a delete flag: del == false inserts or updates,
// del == true deletes the entry if it exists (otherwise no-op).
//
// The operation is determined by the callback function, which is called with:
//
// val: the current value (or zero value if not found)
// found: true if the prefix currently exists, false otherwise
//
// The callback returns:
//
// val: the new value to insert or update (ignored if del == true)
// del: true to delete the entry, false to insert or update
//
// Summary of callback semantics:
//
// | cb-input | cb-return | Ops |
// ------------------------------------- --------
// | (zero, false) | (_, true) | no-op |
// | (zero, false) | (newVal, false) | insert |
// | (oldVal, true) | (newVal, false) | update |
// | (oldVal, true) | (_, true) | delete |
// ------------------------------------- --------
func (t *liteTable[V]) Modify(pfx netip.Prefix, cb func(_ V, ok bool) (_ V, del bool)) {
if !pfx.IsValid() {
return
}
// canonicalize prefix
pfx = pfx.Masked()
is4 := pfx.Addr().Is4()
n := t.rootNodeByVersion(is4)
delta := n.Modify(pfx, cb)
t.sizeUpdate(is4, delta)
}
// Contains reports whether any stored prefix covers the given IP address.
// Returns false for invalid IP addresses.
//
// This performs longest-prefix matching and returns true if any prefix
// in the routing table contains the IP address, regardless of the associated value.
//
// It does not return the value nor the prefix of the matching item,
// but as a test against an allow-/deny-list it's often sufficient
// and even few nanoseconds faster than [Table.Lookup].
func (l *liteTable[V]) Contains(ip netip.Addr) bool {
// speed is top priority: no explicit test for ip.IsValid
// if ip is invalid, AsSlice() returns nil, Contains returns false.
is4 := ip.Is4()
n := l.rootNodeByVersion(is4)
for _, octet := range ip.AsSlice() {
// for contains, any lpm match is good enough, no backtracking needed
if n.Prefixes.Count != 0 && n.Contains(art.OctetToIdx(octet)) {
return true
}
// stop traversing?
if !n.Children.Test(octet) {
return false
}
kid := n.MustGetChild(octet)
// kid is node or leaf or fringe at octet
switch kid := kid.(type) {
case *nodes.LiteNode[V]:
n = kid // descend down to next trie level
case *nodes.FringeNode[V]:
// fringe is the default-route for all possible octets below
return true
case *nodes.LeafNode[V]:
return kid.Prefix.Contains(ip)
}
}
return false
}
// Lookup is just a wrapper for Contains.
// Returns the zero value V and true if a prefix matches ip, otherwise zero value and false.
// This method exists to provide a consistent interface for code generation.
func (l *liteTable[V]) Lookup(ip netip.Addr) (val V, exists bool) {
return val, l.Contains(ip)
}
// LookupPrefix performs a longest prefix match lookup for any address within
// the given prefix. It finds the most specific routing table entry that would
// match any address in the provided prefix range.
//
// This is functionally identical to LookupPrefixLPM but returns only the
// associated value, not the matching prefix itself.
//
// Returns the zero value and true if a matching prefix is found.
// Returns zero value and false if no match exists.
func (l *liteTable[V]) LookupPrefix(pfx netip.Prefix) (val V, exists bool) {
_, exists = l.lookupPrefixLPM(pfx, false)
return
}
// LookupPrefixLPM performs a longest prefix match lookup for any address within
// the given prefix. It finds the most specific routing table entry that would
// match any address in the provided prefix range.
//
// This is functionally identical to LookupPrefix but additionally returns the
// matching LPM prefix itself along with the value.
//
// This method is slower than LookupPrefix and should only be used if the
// matching lpm entry is also required for other reasons.
//
// Returns the matching prefix, the zero value, and true if found.
// Returns zero values and false if no match exists.
func (l *liteTable[V]) LookupPrefixLPM(pfx netip.Prefix) (lpm netip.Prefix, val V, exists bool) {
lpm, exists = l.lookupPrefixLPM(pfx, true)
return
}
// lookupPrefixLPM performs a longest prefix match lookup for any address within
// the given prefix. It finds the most specific routing table entry that would
// match any address in the provided prefix range. If withLPM is true, it also
// returns the matching longest prefix.
func (l *liteTable[V]) lookupPrefixLPM(pfx netip.Prefix, withLPM bool) (lpmPfx netip.Prefix, ok bool) {
if !pfx.IsValid() {
return lpmPfx, ok
}
// canonicalize the prefix
pfx = pfx.Masked()
ip := pfx.Addr()
pfxLen := pfx.Bits()
is4 := ip.Is4()
octets := ip.AsSlice()
strideCount, modBits := nodes.DivMod8(pfxLen)
n := l.rootNodeByVersion(is4)
// record path to leaf node
stack := [nodes.MaxTreeDepth]*nodes.LiteNode[V]{}
var depth int
var octet byte
LOOP:
// find the last node on the octets path in the trie,
for depth, octet = range octets {
depth &= nodes.DepthMask // BCE
// stepped one past the last stride of interest; back up to last and break
if depth > strideCount {
depth--
break
}
// push current node on stack
stack[depth] = n
// go down in tight loop to leaf node
if !n.Children.Test(octet) {
break LOOP
}
kid := n.MustGetChild(octet)
// kid is node or leaf or fringe at octet
switch kid := kid.(type) {
case *nodes.LiteNode[V]:
n = kid
continue LOOP // descend down to next trie level
case *nodes.LeafNode[V]:
// reached a path compressed prefix, stop traversing
if kid.Prefix.Bits() > pfxLen || !kid.Prefix.Contains(ip) {
break LOOP
}
return kid.Prefix, true
case *nodes.FringeNode[V]:
// the bits of the fringe are defined by the depth
// maybe the LPM isn't needed, saves some cycles
fringeBits := (depth + 1) << 3
if fringeBits > pfxLen {
break LOOP
}
// the LPM isn't needed, saves some cycles
if !withLPM {
return netip.Prefix{}, true
}
// get the LPM prefix back from ip and depth
// it's a fringe, bits are always /8, /16, /24, ...
fringePfx, _ := ip.Prefix((depth + 1) << 3)
return fringePfx, true
}
}
// start backtracking, unwind the stack
for ; depth >= 0; depth-- {
depth &= nodes.DepthMask // BCE
n = stack[depth]
// longest prefix match, skip if node has no prefixes
if n.PrefixCount() == 0 {
continue
}
var idx uint8
octet = octets[depth]
// only the final stride may have a different prefix len
// all others are just host routes
if depth == strideCount {
idx = art.PfxToIdx(octet, modBits)
} else {
idx = art.OctetToIdx(octet)
}
// manually inlined: lookupIdx(idx)
var topIdx uint8
if topIdx, ok = n.Prefixes.IntersectionTop(&lpm.LookupTbl[idx]); ok {
// called from LookupPrefix
if !withLPM {
return netip.Prefix{}, ok
}
// called from LookupPrefixLPM
// get the bits from depth and top idx
pfxBits := int(art.PfxBits(depth, topIdx))
// calculate the lpmPfx from incoming ip and new mask
// netip.Addr.Prefix canonicalizes. Invariant: art.PfxBits(depth, topIdx)
// yields a valid mask (v4: 0..32, v6: 0..128), so error is impossible.
lpmPfx, _ = ip.Prefix(pfxBits)
return lpmPfx, ok
}
}
return lpmPfx, ok
}
+731
View File
@@ -0,0 +1,731 @@
// Code generated from file "commonmethods_tmpl.go"; DO NOT EDIT.
// Copyright (c) 2026 Karl Gaissmaier
// SPDX-License-Identifier: MIT
package bart
import (
"bytes"
"encoding/json"
"fmt"
"io"
"iter"
"net/netip"
"slices"
"strings"
"github.com/gaissmai/bart/internal/nodes"
"github.com/gaissmai/bart/internal/value"
)
func (t *liteTable[V]) sizeUpdate(is4 bool, delta int) {
if is4 {
t.size4 += delta
return
}
t.size6 += delta
}
// insert adds or updates a prefix-value pair in the routing table.
// If the prefix already exists, its value is updated; otherwise a new entry is created.
// Invalid prefixes are silently ignored.
//
// The prefix is automatically canonicalized using pfx.Masked() to ensure
// consistent behavior regardless of host bits in the input.
func (t *liteTable[V]) insert(pfx netip.Prefix, val V) {
if !pfx.IsValid() {
return
}
// canonicalize prefix
pfx = pfx.Masked()
is4 := pfx.Addr().Is4()
n := t.rootNodeByVersion(is4)
if exists := n.Insert(pfx, val, 0); exists {
return
}
// true insert, update size
t.sizeUpdate(is4, 1)
}
// insertPersist is similar to insert but the receiver isn't modified.
//
// All nodes touched during insert are cloned and a new liteTable is returned.
// This is not a full [liteTable.Clone], all untouched nodes are still referenced
// from both Tables.
//
// If the payload type V contains pointers or needs deep copying,
// implement:
//
// func (v V) Clone() V
//
// The bart package detects this via structural typing and deep‑copies
// values during persistent ops.
//
// Due to cloning overhead this is significantly slower than insert,
// typically taking μsec instead of nsec.
func (t *liteTable[V]) insertPersist(pfx netip.Prefix, val V) *liteTable[V] {
if !pfx.IsValid() {
return t
}
// canonicalize prefix
pfx = pfx.Masked()
is4 := pfx.Addr().Is4()
// share size counters; root nodes cloned selectively.
pt := &liteTable[V]{
size4: t.size4,
size6: t.size6,
}
// Create a cloning function for deep copying values;
// returns nil if V does not provide a Clone() V method.
cloneFn := value.CloneFnFactory[V]()
// Clone root node corresponding to the IP version, for copy-on-write.
n := &pt.root4
if is4 {
pt.root4 = *t.root4.CloneFlat(cloneFn)
pt.root6 = t.root6
} else {
pt.root4 = t.root4
pt.root6 = *t.root6.CloneFlat(cloneFn)
n = &pt.root6
}
if !n.InsertPersist(cloneFn, pfx, val, 0) {
pt.sizeUpdate(is4, 1)
}
return pt
}
// Delete removes the exact prefix pfx from the table in-place.
//
// This is an exact-match operation (no LPM). If pfx exists, the entry is
// removed. If pfx does not exist or pfx is invalid, the table is left unchanged.
//
// The prefix is canonicalized (Masked) before lookup.
func (t *liteTable[V]) Delete(pfx netip.Prefix) {
if !pfx.IsValid() {
return
}
// canonicalize prefix
pfx = pfx.Masked()
is4 := pfx.Addr().Is4()
n := t.rootNodeByVersion(is4)
if exists := n.Delete(pfx); exists {
t.sizeUpdate(is4, -1)
}
}
// Get performs an exact-prefix lookup and returns whether the exact
// prefix exists. The prefix is canonicalized (Masked) before lookup.
//
// This is an exact-match operation (no LPM). The prefix must match exactly
// in both address and prefix length to be found. If pfx exists, the
// associated value (zero value for Lite) and found=true is returned.
// If pfx does not exist or pfx is invalid, the zero value for V and
// exists=false is returned.
//
// For longest-prefix-match (LPM) lookups, use Contains(ip), Lookup(ip),
// LookupPrefix(pfx) or LookupPrefixLPM(pfx) instead.
func (t *liteTable[V]) Get(pfx netip.Prefix) (val V, exists bool) {
if !pfx.IsValid() {
return val, exists
}
// canonicalize prefix
pfx = pfx.Masked()
is4 := pfx.Addr().Is4()
n := t.rootNodeByVersion(is4)
return n.Get(pfx)
}
// DeletePersist is similar to Delete but does not modify the receiver.
//
// It performs a copy-on-write delete operation, cloning all nodes touched during
// deletion and returning a new liteTable reflecting the change.
//
// If the prefix is invalid or doesn't exist, the original table is
// returned unchanged.
//
// If the payload type V contains pointers or requires deep copying,
// it must implement the Clone method for correct cloning.
//
// Due to cloning overhead this is significantly slower than Delete,
// typically taking μsec instead of nsec.
func (t *liteTable[V]) DeletePersist(pfx netip.Prefix) *liteTable[V] {
if !pfx.IsValid() {
return t
}
// canonicalize prefix
pfx = pfx.Masked()
is4 := pfx.Addr().Is4()
// Preflight check: avoid cloning if prefix doesn't exist
n := t.rootNodeByVersion(is4)
if _, found := n.Get(pfx); !found {
return t
}
// share size counters; root nodes cloned selectively.
pt := &liteTable[V]{
size4: t.size4,
size6: t.size6,
}
// Create a cloning function for deep copying values;
// returns nil if V does not provide a Clone() V method.
cloneFn := value.CloneFnFactory[V]()
// Clone root node corresponding to the IP version, for copy-on-write.
if is4 {
pt.root4 = *t.root4.CloneFlat(cloneFn)
pt.root6 = t.root6
n = &pt.root4
} else {
pt.root4 = t.root4
pt.root6 = *t.root6.CloneFlat(cloneFn)
n = &pt.root6
}
if exists := n.DeletePersist(cloneFn, pfx); exists {
pt.sizeUpdate(is4, -1)
}
return pt
}
// ModifyPersist is similar to Modify but the receiver isn't modified and
// a new *liteTable is returned.
func (t *liteTable[V]) ModifyPersist(pfx netip.Prefix, cb func(_ V, ok bool) (_ V, del bool)) *liteTable[V] {
if !pfx.IsValid() {
return t
}
// make a cheap test in front of expensive operation
oldVal, ok := t.Get(pfx)
val := oldVal
// to clone or not to clone ...
cloneFn := value.CloneFnFactory[V]()
if cloneFn != nil && ok {
val = cloneFn(oldVal)
}
newVal, del := cb(val, ok)
switch {
case !ok && del: // no-op
return t
case !ok && !del: // insert
return t.InsertPersist(pfx, newVal)
case ok && !del: // update
return t.InsertPersist(pfx, newVal)
case ok && del: // delete
return t.DeletePersist(pfx)
}
panic("unreachable")
}
// Supernets returns an iterator over all supernet routes that cover the given prefix pfx.
//
// The traversal searches both exact-length and shorter (less specific) prefixes that
// include pfx. Starting from the most specific position in the trie,
// it walks upward through parent nodes and yields any matching entries found at each level.
//
// The iteration order is reverse-CIDR: from longest prefix match (LPM) towards
// least-specific routes.
//
// This can be used to enumerate all covering supernet routes in routing-based
// policy engines, diagnostics tools, or fallback resolution logic.
//
// Example:
//
// for supernet, val := range table.Supernets(netip.MustParsePrefix("192.0.2.128/25")) {
// fmt.Println("Covered by:", supernet, "->", val)
// }
//
// The iteration can be stopped early by breaking from the range loop.
// Returns an empty iterator if the prefix is invalid.
func (t *liteTable[V]) Supernets(pfx netip.Prefix) iter.Seq2[netip.Prefix, V] {
return func(yield func(netip.Prefix, V) bool) {
if !pfx.IsValid() {
return
}
// canonicalize the prefix
pfx = pfx.Masked()
is4 := pfx.Addr().Is4()
n := t.rootNodeByVersion(is4)
n.Supernets(pfx, yield)
}
}
// Subnets returns an iterator over all subnets of the given prefix
// in natural CIDR sort order. This includes prefixes of the same length
// (exact match) and longer (more specific) prefixes that are contained
// within the given prefix.
//
// Example:
//
// for sub, val := range table.Subnets(netip.MustParsePrefix("10.0.0.0/8")) {
// fmt.Println("Covered:", sub, "->", val)
// }
//
// The iteration can be stopped early by breaking from the range loop.
// Returns an empty iterator if the prefix is invalid.
func (t *liteTable[V]) Subnets(pfx netip.Prefix) iter.Seq2[netip.Prefix, V] {
return func(yield func(netip.Prefix, V) bool) {
if !pfx.IsValid() {
return
}
pfx = pfx.Masked()
is4 := pfx.Addr().Is4()
n := t.rootNodeByVersion(is4)
n.Subnets(pfx, yield)
}
}
// OverlapsPrefix reports whether any prefix in the routing table overlaps with
// the given prefix. Two prefixes overlap if they share any IP addresses.
//
// The check is bidirectional: it returns true if the input prefix is covered by an existing
// route, or if any stored route is itself contained within the input prefix.
//
// Internally, the function normalizes the prefix and descends the relevant trie branch,
// using stride-based logic to identify overlap without performing a full lookup.
//
// This is useful for containment tests, route validation, or policy checks using prefix
// semantics without retrieving exact matches.
func (t *liteTable[V]) OverlapsPrefix(pfx netip.Prefix) bool {
if !pfx.IsValid() {
return false
}
// canonicalize the prefix
pfx = pfx.Masked()
is4 := pfx.Addr().Is4()
n := t.rootNodeByVersion(is4)
return n.OverlapsPrefixAtDepth(pfx, 0)
}
// Overlaps reports whether any route in the receiver table overlaps
// with a route in the other table, in either direction.
//
// The overlap check is bidirectional: it returns true if any IP prefix
// in the receiver is covered by the other table, or vice versa.
// This includes partial overlaps, exact matches, and supernet/subnet relationships.
//
// Both IPv4 and IPv6 route trees are compared independently. If either
// tree has overlapping routes, the function returns true.
//
// This is useful for conflict detection, policy enforcement,
// or validating mutually exclusive routing domains.
func (t *liteTable[V]) Overlaps(o *liteTable[V]) bool {
return t.Overlaps4(o) || t.Overlaps6(o)
}
// Overlaps4 is like [liteTable.Overlaps] but for the v4 routing table only.
func (t *liteTable[V]) Overlaps4(o *liteTable[V]) bool {
if t.size4 == 0 || o.size4 == 0 {
return false
}
return t.root4.Overlaps(&o.root4, 0)
}
// Overlaps6 is like [liteTable.Overlaps] but for the v6 routing table only.
func (t *liteTable[V]) Overlaps6(o *liteTable[V]) bool {
if t.size6 == 0 || o.size6 == 0 {
return false
}
return t.root6.Overlaps(&o.root6, 0)
}
// Union merges another routing table into the receiver table, modifying it in-place.
//
// All prefixes and values from the other table (o) are inserted into the receiver.
// If a duplicate prefix exists in both tables, the value from o replaces the existing entry.
// This duplicate is shallow-copied by default, but if the value type V implements the
// Clone method, the value is deeply cloned before insertion. See also liteTable.Clone.
func (t *liteTable[V]) Union(o *liteTable[V]) {
// panics on nil receiver
_ = t.size4
// panics on nil argument
if o.size4 == 0 && o.size6 == 0 {
return
}
// t is unchanged
if o == t {
return
}
// Create a cloning function for deep copying values;
// returns nil if V does not provide a Clone() V method.
cloneFn := value.CloneFnFactory[V]()
dup4 := t.root4.UnionRec(cloneFn, &o.root4, 0)
dup6 := t.root6.UnionRec(cloneFn, &o.root6, 0)
t.size4 += o.size4 - dup4
t.size6 += o.size6 - dup6
}
// UnionPersist is similar to [Union] but the receiver isn't modified.
//
// All nodes touched during union are cloned and a new *liteTable is returned.
// If o is empty, no nodes are touched and the receiver may be
// returned unchanged.
func (t *liteTable[V]) UnionPersist(o *liteTable[V]) *liteTable[V] {
// panics on nil receiver
_ = t.size4
// panics on nil argument
if o.size4 == 0 && o.size6 == 0 {
return t
}
if o == t {
return t
}
// Create a cloning function for deep copying values;
// returns nil if V does not provide a Clone() V method.
cloneFn := value.CloneFnFactory[V]()
// new liteTable with root nodes just copied.
pt := &liteTable[V]{
root4: t.root4,
root6: t.root6,
//
size4: t.size4,
size6: t.size6,
}
// only clone the root node if there is something to union
if o.size4 != 0 {
pt.root4 = *t.root4.CloneFlat(cloneFn)
}
if o.size6 != 0 {
pt.root6 = *t.root6.CloneFlat(cloneFn)
}
dup4 := pt.root4.UnionRecPersist(cloneFn, &o.root4, 0)
dup6 := pt.root6.UnionRecPersist(cloneFn, &o.root6, 0)
pt.size4 += o.size4 - dup4
pt.size6 += o.size6 - dup6
return pt
}
// Equal checks whether two tables are structurally and semantically equal.
// It ensures both trees (IPv4-based and IPv6-based) have the same sizes and
// recursively compares their root nodes.
//
// If V implements an `Equal(V) bool` method, its custom equality logic is used.
// Otherwise, values are compared directly using the == operator.
//
// Note: If V implements `Equal(V) bool` with a pointer receiver, the Equal
// method should handle nil receivers gracefully.
//
// ATTENTION: If V is not comparable at runtime (such as a slice or map without an `Equal`
// method), a runtime panic will occur.
func (t *liteTable[V]) Equal(o *liteTable[V]) bool {
if t.size4 != o.size4 || t.size6 != o.size6 {
return false
}
if o == t {
return true
}
return t.root4.EqualRec(&o.root4) && t.root6.EqualRec(&o.root6)
}
// Clone returns a copy of the routing table.
// The payload of type V is shallow copied by default. To enable deep copying,
// implement the following method on your value type:
//
// Clone() V
//
// Example:
//
// type MyValue struct { Data []byte }
// func (v MyValue) Clone() MyValue {
// return MyValue{Data: slices.Clone(v.Data)}
// }
//
// The bart package will automatically detect and use this method via Go's
// structural typing.
//
// Note: If V implements Clone() V with a pointer receiver, the Clone
// method should handle nil receivers gracefully.
func (t *liteTable[V]) Clone() *liteTable[V] {
c := new(liteTable[V])
cloneFn := value.CloneFnFactory[V]()
c.root4 = *t.root4.CloneRec(cloneFn)
c.root6 = *t.root6.CloneRec(cloneFn)
c.size4 = t.size4
c.size6 = t.size6
return c
}
// Size returns the prefix count.
func (t *liteTable[V]) Size() int {
return t.size4 + t.size6
}
// Size4 returns the IPv4 prefix count.
func (t *liteTable[V]) Size4() int {
return t.size4
}
// Size6 returns the IPv6 prefix count.
func (t *liteTable[V]) Size6() int {
return t.size6
}
// All returns an iterator over all prefix–value pairs in the table.
//
// The iteration order is unspecified and may vary between calls; for a stable order,
// use [liteTable.AllSorted].
//
// IMPORTANT: Modifying the table during iteration is not allowed,
// as this would interfere with the internal traversal and may corrupt or
// prematurely terminate the iteration.
func (t *liteTable[V]) All() iter.Seq2[netip.Prefix, V] {
return func(yield func(netip.Prefix, V) bool) {
_ = t.root4.AllRec(stridePath{}, 0, true, yield) && t.root6.AllRec(stridePath{}, 0, false, yield)
}
}
// All4 is like [liteTable.All] but only for the v4 routing table.
func (t *liteTable[V]) All4() iter.Seq2[netip.Prefix, V] {
return func(yield func(netip.Prefix, V) bool) {
_ = t.root4.AllRec(stridePath{}, 0, true, yield)
}
}
// All6 is like [liteTable.All] but only for the v6 routing table.
func (t *liteTable[V]) All6() iter.Seq2[netip.Prefix, V] {
return func(yield func(netip.Prefix, V) bool) {
_ = t.root6.AllRec(stridePath{}, 0, false, yield)
}
}
// AllSorted is like [liteTable.All] but the iteration is ordered in canonical
// CIDR prefix sort order.
func (t *liteTable[V]) AllSorted() iter.Seq2[netip.Prefix, V] {
return func(yield func(netip.Prefix, V) bool) {
_ = t.root4.AllRecSorted(stridePath{}, 0, true, yield) &&
t.root6.AllRecSorted(stridePath{}, 0, false, yield)
}
}
// AllSorted4 is like [liteTable.AllSorted] but only for the v4 routing table.
func (t *liteTable[V]) AllSorted4() iter.Seq2[netip.Prefix, V] {
return func(yield func(netip.Prefix, V) bool) {
_ = t.root4.AllRecSorted(stridePath{}, 0, true, yield)
}
}
// AllSorted6 is like [liteTable.AllSorted] but only for the v6 routing table.
func (t *liteTable[V]) AllSorted6() iter.Seq2[netip.Prefix, V] {
return func(yield func(netip.Prefix, V) bool) {
_ = t.root6.AllRecSorted(stridePath{}, 0, false, yield)
}
}
// Fprint writes a hierarchical tree diagram of the ordered CIDRs
// with default formatted payload V to w.
//
// The order from top to bottom is in ascending order of the prefix address
// and the subtree structure is determined by the CIDRs coverage.
//
// ▼
// ├─ 10.0.0.0/8 (V)
// │ ├─ 10.0.0.0/24 (V)
// │ └─ 10.0.1.0/24 (V)
// ├─ 127.0.0.0/8 (V)
// │ └─ 127.0.0.1/32 (V)
// ├─ 169.254.0.0/16 (V)
// ├─ 172.16.0.0/12 (V)
// └─ 192.168.0.0/16 (V)
// └─ 192.168.1.0/24 (V)
// ▼
// └─ ::/0 (V)
// ├─ ::1/128 (V)
// ├─ 2000::/3 (V)
// │ └─ 2001:db8::/32 (V)
// └─ fe80::/10 (V)
func (t *liteTable[V]) Fprint(w io.Writer) error {
if w == nil && t != nil {
return fmt.Errorf("nil writer")
}
// v4
if err := t.fprint(w, true); err != nil {
return err
}
// v6
if err := t.fprint(w, false); err != nil {
return err
}
return nil
}
// fprint is the version dependent adapter to fprintRec.
func (t *liteTable[V]) fprint(w io.Writer, is4 bool) error {
n := t.rootNodeByVersion(is4)
if n.IsEmpty() {
return nil
}
if _, err := fmt.Fprint(w, "▼\n"); err != nil {
return err
}
startParent := nodes.TrieItem[V]{
Node: nil,
Idx: 0,
Path: stridePath{},
Is4: is4,
}
return n.FprintRec(w, startParent, "")
}
// MarshalText implements the [encoding.TextMarshaler] interface,
// just a wrapper for [liteTable.Fprint].
func (t *liteTable[V]) MarshalText() ([]byte, error) {
w := new(bytes.Buffer)
if err := t.Fprint(w); err != nil {
return nil, err
}
return w.Bytes(), nil
}
// MarshalJSON dumps the table into two sorted lists: for ipv4 and ipv6.
// Every root and subnet is an array, not a map, because the order matters.
func (t *liteTable[V]) MarshalJSON() ([]byte, error) {
result := struct {
Ipv4 []DumpListNode[V] `json:"ipv4,omitempty"`
Ipv6 []DumpListNode[V] `json:"ipv6,omitempty"`
}{
Ipv4: t.DumpList4(),
Ipv6: t.DumpList6(),
}
buf, err := json.Marshal(result)
if err != nil {
return nil, err
}
return buf, nil
}
// DumpList4 dumps the ipv4 tree into a list of roots and their subnets.
// It can be used to analyze the tree or build the text or JSON serialization.
func (t *liteTable[V]) DumpList4() []DumpListNode[V] {
return t.dumpListRec(&t.root4, 0, stridePath{}, 0, true)
}
// DumpList6 dumps the ipv6 tree into a list of roots and their subnets.
// It can be used to analyze the tree or build custom JSON representation.
func (t *liteTable[V]) DumpList6() []DumpListNode[V] {
return t.dumpListRec(&t.root6, 0, stridePath{}, 0, false)
}
// dumpListRec, build the data structure rec-descent with the help of directItemsRec.
// anyNode is nodes.BartNode, nodes.FastNode or nodes.LiteNode
func (t *liteTable[V]) dumpListRec(anyNode any, parentIdx uint8, path stridePath, depth int, is4 bool) []DumpListNode[V] {
// recursion stop condition
if anyNode == nil {
return nil
}
// the same method is generated for all table types, therefore
// type assert to the smallest needed interface.
// The panic on wrong type assertion is by intention, MUST NOT happen
n := anyNode.(interface {
DirectItemsRec(uint8, stridePath, int, bool) []nodes.TrieItem[V]
})
directItems := n.DirectItemsRec(parentIdx, path, depth, is4)
// sort the items by prefix
slices.SortFunc(directItems, func(a, b nodes.TrieItem[V]) int {
return nodes.CmpPrefix(a.Cidr, b.Cidr)
})
dumpNodes := make([]DumpListNode[V], 0, len(directItems))
for _, item := range directItems {
dumpNodes = append(dumpNodes, DumpListNode[V]{
CIDR: item.Cidr,
Value: item.Val,
// build it rec-descent, item.Node is also from type any
Subnets: t.dumpListRec(item.Node, item.Idx, item.Path, item.Depth, is4),
})
}
return dumpNodes
}
// dumpString is just a wrapper for dump.
func (t *liteTable[V]) dumpString() string {
w := new(strings.Builder)
t.dump(w)
return w.String()
}
// dump the table structure and all the nodes to w.
func (t *liteTable[V]) dump(w io.Writer) {
if t.size4 > 0 {
stats := t.root4.StatsRec()
fmt.Fprintln(w)
fmt.Fprintf(w, "### IPv4: size(%d), subnodes(%d), prefixes(%d), leaves(%d), fringes(%d)",
t.size4, stats.SubNodes, stats.Prefixes, stats.Leaves, stats.Fringes)
t.root4.DumpRec(w, stridePath{}, 0, true)
}
if t.size6 > 0 {
stats := t.root6.StatsRec()
fmt.Fprintln(w)
fmt.Fprintf(w, "### IPv6: size(%d), subnodes(%d), prefixes(%d), leaves(%d), fringes(%d)",
t.size6, stats.SubNodes, stats.Prefixes, stats.Leaves, stats.Fringes)
t.root6.DumpRec(w, stridePath{}, 0, false)
}
}
-538
View File
@@ -1,538 +0,0 @@
package bart
import "github.com/gaissmai/bart/internal/bitset"
// lpmLookupTbl is the backtracking sequence in the complete binary tree as bitstring.
//
// for idx := 1; idx > 0; idx >>= 1 { b.Set(idx) }
//
// allows a one shot bitset intersection algorithm:
//
// func (n *node[V]) lpmTest(idx uint) bool {
// return n.prefixes.IntersectsAny(lpmLookupTbl[idx])
// }
//
// instead of a sequence of single bitset tests:
//
// func (n *node[V]) lpmTest(idx uint) bool {
// for ; idx > 0; idx >>= 1 {
// if n.prefixes.Test(idx) {
// return true
// }
// }
// return false
// }
var lpmLookupTbl = [512]bitset.BitSet{
/* idx: 0 */ {}, // invalid
/* idx: 1 */ {0x2}, // 0b0000_0010
/* idx: 2 */ {0x6}, // 0b0000_0110
/* idx: 3 */ {0xa}, // 0b0000_1010
/* idx: 4 */ {0x16}, // ...
/* idx: 5 */ {0x26},
/* idx: 6 */ {0x4a},
/* idx: 7 */ {0x8a},
/* idx: 8 */ {0x116},
/* idx: 9 */ {0x216},
/* idx: 10 */ {0x426},
/* idx: 11 */ {0x826},
/* idx: 12 */ {0x104a},
/* idx: 13 */ {0x204a},
/* idx: 14 */ {0x408a},
/* idx: 15 */ {0x808a},
/* idx: 16 */ {0x10116},
/* idx: 17 */ {0x20116},
/* idx: 18 */ {0x40216},
/* idx: 19 */ {0x80216},
/* idx: 20 */ {0x100426},
/* idx: 21 */ {0x200426},
/* idx: 22 */ {0x400826},
/* idx: 23 */ {0x800826},
/* idx: 24 */ {0x100104a},
/* idx: 25 */ {0x200104a},
/* idx: 26 */ {0x400204a},
/* idx: 27 */ {0x800204a},
/* idx: 28 */ {0x1000408a},
/* idx: 29 */ {0x2000408a},
/* idx: 30 */ {0x4000808a},
/* idx: 31 */ {0x8000808a},
/* idx: 32 */ {0x100010116},
/* idx: 33 */ {0x200010116},
/* idx: 34 */ {0x400020116},
/* idx: 35 */ {0x800020116},
/* idx: 36 */ {0x1000040216},
/* idx: 37 */ {0x2000040216},
/* idx: 38 */ {0x4000080216},
/* idx: 39 */ {0x8000080216},
/* idx: 40 */ {0x10000100426},
/* idx: 41 */ {0x20000100426},
/* idx: 42 */ {0x40000200426},
/* idx: 43 */ {0x80000200426},
/* idx: 44 */ {0x100000400826},
/* idx: 45 */ {0x200000400826},
/* idx: 46 */ {0x400000800826},
/* idx: 47 */ {0x800000800826},
/* idx: 48 */ {0x100000100104a},
/* idx: 49 */ {0x200000100104a},
/* idx: 50 */ {0x400000200104a},
/* idx: 51 */ {0x800000200104a},
/* idx: 52 */ {0x1000000400204a},
/* idx: 53 */ {0x2000000400204a},
/* idx: 54 */ {0x4000000800204a},
/* idx: 55 */ {0x8000000800204a},
/* idx: 56 */ {0x10000001000408a},
/* idx: 57 */ {0x20000001000408a},
/* idx: 58 */ {0x40000002000408a},
/* idx: 59 */ {0x80000002000408a},
/* idx: 60 */ {0x100000004000808a},
/* idx: 61 */ {0x200000004000808a},
/* idx: 62 */ {0x400000008000808a},
/* idx: 63 */ {0x800000008000808a},
/* idx: 64 */ {0x100010116, 0x1},
/* idx: 65 */ {0x100010116, 0x2},
/* idx: 66 */ {0x200010116, 0x4},
/* idx: 67 */ {0x200010116, 0x8},
/* idx: 68 */ {0x400020116, 0x10},
/* idx: 69 */ {0x400020116, 0x20},
/* idx: 70 */ {0x800020116, 0x40},
/* idx: 71 */ {0x800020116, 0x80},
/* idx: 72 */ {0x1000040216, 0x100},
/* idx: 73 */ {0x1000040216, 0x200},
/* idx: 74 */ {0x2000040216, 0x400},
/* idx: 75 */ {0x2000040216, 0x800},
/* idx: 76 */ {0x4000080216, 0x1000},
/* idx: 77 */ {0x4000080216, 0x2000},
/* idx: 78 */ {0x8000080216, 0x4000},
/* idx: 79 */ {0x8000080216, 0x8000},
/* idx: 80 */ {0x10000100426, 0x10000},
/* idx: 81 */ {0x10000100426, 0x20000},
/* idx: 82 */ {0x20000100426, 0x40000},
/* idx: 83 */ {0x20000100426, 0x80000},
/* idx: 84 */ {0x40000200426, 0x100000},
/* idx: 85 */ {0x40000200426, 0x200000},
/* idx: 86 */ {0x80000200426, 0x400000},
/* idx: 87 */ {0x80000200426, 0x800000},
/* idx: 88 */ {0x100000400826, 0x1000000},
/* idx: 89 */ {0x100000400826, 0x2000000},
/* idx: 90 */ {0x200000400826, 0x4000000},
/* idx: 91 */ {0x200000400826, 0x8000000},
/* idx: 92 */ {0x400000800826, 0x10000000},
/* idx: 93 */ {0x400000800826, 0x20000000},
/* idx: 94 */ {0x800000800826, 0x40000000},
/* idx: 95 */ {0x800000800826, 0x80000000},
/* idx: 96 */ {0x100000100104a, 0x100000000},
/* idx: 97 */ {0x100000100104a, 0x200000000},
/* idx: 98 */ {0x200000100104a, 0x400000000},
/* idx: 99 */ {0x200000100104a, 0x800000000},
/* idx: 100 */ {0x400000200104a, 0x1000000000},
/* idx: 101 */ {0x400000200104a, 0x2000000000},
/* idx: 102 */ {0x800000200104a, 0x4000000000},
/* idx: 103 */ {0x800000200104a, 0x8000000000},
/* idx: 104 */ {0x1000000400204a, 0x10000000000},
/* idx: 105 */ {0x1000000400204a, 0x20000000000},
/* idx: 106 */ {0x2000000400204a, 0x40000000000},
/* idx: 107 */ {0x2000000400204a, 0x80000000000},
/* idx: 108 */ {0x4000000800204a, 0x100000000000},
/* idx: 109 */ {0x4000000800204a, 0x200000000000},
/* idx: 110 */ {0x8000000800204a, 0x400000000000},
/* idx: 111 */ {0x8000000800204a, 0x800000000000},
/* idx: 112 */ {0x10000001000408a, 0x1000000000000},
/* idx: 113 */ {0x10000001000408a, 0x2000000000000},
/* idx: 114 */ {0x20000001000408a, 0x4000000000000},
/* idx: 115 */ {0x20000001000408a, 0x8000000000000},
/* idx: 116 */ {0x40000002000408a, 0x10000000000000},
/* idx: 117 */ {0x40000002000408a, 0x20000000000000},
/* idx: 118 */ {0x80000002000408a, 0x40000000000000},
/* idx: 119 */ {0x80000002000408a, 0x80000000000000},
/* idx: 120 */ {0x100000004000808a, 0x100000000000000},
/* idx: 121 */ {0x100000004000808a, 0x200000000000000},
/* idx: 122 */ {0x200000004000808a, 0x400000000000000},
/* idx: 123 */ {0x200000004000808a, 0x800000000000000},
/* idx: 124 */ {0x400000008000808a, 0x1000000000000000},
/* idx: 125 */ {0x400000008000808a, 0x2000000000000000},
/* idx: 126 */ {0x800000008000808a, 0x4000000000000000},
/* idx: 127 */ {0x800000008000808a, 0x8000000000000000},
/* idx: 128 */ {0x100010116, 0x1, 0x1},
/* idx: 129 */ {0x100010116, 0x1, 0x2},
/* idx: 130 */ {0x100010116, 0x2, 0x4},
/* idx: 131 */ {0x100010116, 0x2, 0x8},
/* idx: 132 */ {0x200010116, 0x4, 0x10},
/* idx: 133 */ {0x200010116, 0x4, 0x20},
/* idx: 134 */ {0x200010116, 0x8, 0x40},
/* idx: 135 */ {0x200010116, 0x8, 0x80},
/* idx: 136 */ {0x400020116, 0x10, 0x100},
/* idx: 137 */ {0x400020116, 0x10, 0x200},
/* idx: 138 */ {0x400020116, 0x20, 0x400},
/* idx: 139 */ {0x400020116, 0x20, 0x800},
/* idx: 140 */ {0x800020116, 0x40, 0x1000},
/* idx: 141 */ {0x800020116, 0x40, 0x2000},
/* idx: 142 */ {0x800020116, 0x80, 0x4000},
/* idx: 143 */ {0x800020116, 0x80, 0x8000},
/* idx: 144 */ {0x1000040216, 0x100, 0x10000},
/* idx: 145 */ {0x1000040216, 0x100, 0x20000},
/* idx: 146 */ {0x1000040216, 0x200, 0x40000},
/* idx: 147 */ {0x1000040216, 0x200, 0x80000},
/* idx: 148 */ {0x2000040216, 0x400, 0x100000},
/* idx: 149 */ {0x2000040216, 0x400, 0x200000},
/* idx: 150 */ {0x2000040216, 0x800, 0x400000},
/* idx: 151 */ {0x2000040216, 0x800, 0x800000},
/* idx: 152 */ {0x4000080216, 0x1000, 0x1000000},
/* idx: 153 */ {0x4000080216, 0x1000, 0x2000000},
/* idx: 154 */ {0x4000080216, 0x2000, 0x4000000},
/* idx: 155 */ {0x4000080216, 0x2000, 0x8000000},
/* idx: 156 */ {0x8000080216, 0x4000, 0x10000000},
/* idx: 157 */ {0x8000080216, 0x4000, 0x20000000},
/* idx: 158 */ {0x8000080216, 0x8000, 0x40000000},
/* idx: 159 */ {0x8000080216, 0x8000, 0x80000000},
/* idx: 160 */ {0x10000100426, 0x10000, 0x100000000},
/* idx: 161 */ {0x10000100426, 0x10000, 0x200000000},
/* idx: 162 */ {0x10000100426, 0x20000, 0x400000000},
/* idx: 163 */ {0x10000100426, 0x20000, 0x800000000},
/* idx: 164 */ {0x20000100426, 0x40000, 0x1000000000},
/* idx: 165 */ {0x20000100426, 0x40000, 0x2000000000},
/* idx: 166 */ {0x20000100426, 0x80000, 0x4000000000},
/* idx: 167 */ {0x20000100426, 0x80000, 0x8000000000},
/* idx: 168 */ {0x40000200426, 0x100000, 0x10000000000},
/* idx: 169 */ {0x40000200426, 0x100000, 0x20000000000},
/* idx: 170 */ {0x40000200426, 0x200000, 0x40000000000},
/* idx: 171 */ {0x40000200426, 0x200000, 0x80000000000},
/* idx: 172 */ {0x80000200426, 0x400000, 0x100000000000},
/* idx: 173 */ {0x80000200426, 0x400000, 0x200000000000},
/* idx: 174 */ {0x80000200426, 0x800000, 0x400000000000},
/* idx: 175 */ {0x80000200426, 0x800000, 0x800000000000},
/* idx: 176 */ {0x100000400826, 0x1000000, 0x1000000000000},
/* idx: 177 */ {0x100000400826, 0x1000000, 0x2000000000000},
/* idx: 178 */ {0x100000400826, 0x2000000, 0x4000000000000},
/* idx: 179 */ {0x100000400826, 0x2000000, 0x8000000000000},
/* idx: 180 */ {0x200000400826, 0x4000000, 0x10000000000000},
/* idx: 181 */ {0x200000400826, 0x4000000, 0x20000000000000},
/* idx: 182 */ {0x200000400826, 0x8000000, 0x40000000000000},
/* idx: 183 */ {0x200000400826, 0x8000000, 0x80000000000000},
/* idx: 184 */ {0x400000800826, 0x10000000, 0x100000000000000},
/* idx: 185 */ {0x400000800826, 0x10000000, 0x200000000000000},
/* idx: 186 */ {0x400000800826, 0x20000000, 0x400000000000000},
/* idx: 187 */ {0x400000800826, 0x20000000, 0x800000000000000},
/* idx: 188 */ {0x800000800826, 0x40000000, 0x1000000000000000},
/* idx: 189 */ {0x800000800826, 0x40000000, 0x2000000000000000},
/* idx: 190 */ {0x800000800826, 0x80000000, 0x4000000000000000},
/* idx: 191 */ {0x800000800826, 0x80000000, 0x8000000000000000},
/* idx: 192 */ {0x100000100104a, 0x100000000, 0x0, 0x1},
/* idx: 193 */ {0x100000100104a, 0x100000000, 0x0, 0x2},
/* idx: 194 */ {0x100000100104a, 0x200000000, 0x0, 0x4},
/* idx: 195 */ {0x100000100104a, 0x200000000, 0x0, 0x8},
/* idx: 196 */ {0x200000100104a, 0x400000000, 0x0, 0x10},
/* idx: 197 */ {0x200000100104a, 0x400000000, 0x0, 0x20},
/* idx: 198 */ {0x200000100104a, 0x800000000, 0x0, 0x40},
/* idx: 199 */ {0x200000100104a, 0x800000000, 0x0, 0x80},
/* idx: 200 */ {0x400000200104a, 0x1000000000, 0x0, 0x100},
/* idx: 201 */ {0x400000200104a, 0x1000000000, 0x0, 0x200},
/* idx: 202 */ {0x400000200104a, 0x2000000000, 0x0, 0x400},
/* idx: 203 */ {0x400000200104a, 0x2000000000, 0x0, 0x800},
/* idx: 204 */ {0x800000200104a, 0x4000000000, 0x0, 0x1000},
/* idx: 205 */ {0x800000200104a, 0x4000000000, 0x0, 0x2000},
/* idx: 206 */ {0x800000200104a, 0x8000000000, 0x0, 0x4000},
/* idx: 207 */ {0x800000200104a, 0x8000000000, 0x0, 0x8000},
/* idx: 208 */ {0x1000000400204a, 0x10000000000, 0x0, 0x10000},
/* idx: 209 */ {0x1000000400204a, 0x10000000000, 0x0, 0x20000},
/* idx: 210 */ {0x1000000400204a, 0x20000000000, 0x0, 0x40000},
/* idx: 211 */ {0x1000000400204a, 0x20000000000, 0x0, 0x80000},
/* idx: 212 */ {0x2000000400204a, 0x40000000000, 0x0, 0x100000},
/* idx: 213 */ {0x2000000400204a, 0x40000000000, 0x0, 0x200000},
/* idx: 214 */ {0x2000000400204a, 0x80000000000, 0x0, 0x400000},
/* idx: 215 */ {0x2000000400204a, 0x80000000000, 0x0, 0x800000},
/* idx: 216 */ {0x4000000800204a, 0x100000000000, 0x0, 0x1000000},
/* idx: 217 */ {0x4000000800204a, 0x100000000000, 0x0, 0x2000000},
/* idx: 218 */ {0x4000000800204a, 0x200000000000, 0x0, 0x4000000},
/* idx: 219 */ {0x4000000800204a, 0x200000000000, 0x0, 0x8000000},
/* idx: 220 */ {0x8000000800204a, 0x400000000000, 0x0, 0x10000000},
/* idx: 221 */ {0x8000000800204a, 0x400000000000, 0x0, 0x20000000},
/* idx: 222 */ {0x8000000800204a, 0x800000000000, 0x0, 0x40000000},
/* idx: 223 */ {0x8000000800204a, 0x800000000000, 0x0, 0x80000000},
/* idx: 224 */ {0x10000001000408a, 0x1000000000000, 0x0, 0x100000000},
/* idx: 225 */ {0x10000001000408a, 0x1000000000000, 0x0, 0x200000000},
/* idx: 226 */ {0x10000001000408a, 0x2000000000000, 0x0, 0x400000000},
/* idx: 227 */ {0x10000001000408a, 0x2000000000000, 0x0, 0x800000000},
/* idx: 228 */ {0x20000001000408a, 0x4000000000000, 0x0, 0x1000000000},
/* idx: 229 */ {0x20000001000408a, 0x4000000000000, 0x0, 0x2000000000},
/* idx: 230 */ {0x20000001000408a, 0x8000000000000, 0x0, 0x4000000000},
/* idx: 231 */ {0x20000001000408a, 0x8000000000000, 0x0, 0x8000000000},
/* idx: 232 */ {0x40000002000408a, 0x10000000000000, 0x0, 0x10000000000},
/* idx: 233 */ {0x40000002000408a, 0x10000000000000, 0x0, 0x20000000000},
/* idx: 234 */ {0x40000002000408a, 0x20000000000000, 0x0, 0x40000000000},
/* idx: 235 */ {0x40000002000408a, 0x20000000000000, 0x0, 0x80000000000},
/* idx: 236 */ {0x80000002000408a, 0x40000000000000, 0x0, 0x100000000000},
/* idx: 237 */ {0x80000002000408a, 0x40000000000000, 0x0, 0x200000000000},
/* idx: 238 */ {0x80000002000408a, 0x80000000000000, 0x0, 0x400000000000},
/* idx: 239 */ {0x80000002000408a, 0x80000000000000, 0x0, 0x800000000000},
/* idx: 240 */ {0x100000004000808a, 0x100000000000000, 0x0, 0x1000000000000},
/* idx: 241 */ {0x100000004000808a, 0x100000000000000, 0x0, 0x2000000000000},
/* idx: 242 */ {0x100000004000808a, 0x200000000000000, 0x0, 0x4000000000000},
/* idx: 243 */ {0x100000004000808a, 0x200000000000000, 0x0, 0x8000000000000},
/* idx: 244 */ {0x200000004000808a, 0x400000000000000, 0x0, 0x10000000000000},
/* idx: 245 */ {0x200000004000808a, 0x400000000000000, 0x0, 0x20000000000000},
/* idx: 246 */ {0x200000004000808a, 0x800000000000000, 0x0, 0x40000000000000},
/* idx: 247 */ {0x200000004000808a, 0x800000000000000, 0x0, 0x80000000000000},
/* idx: 248 */ {0x400000008000808a, 0x1000000000000000, 0x0, 0x100000000000000},
/* idx: 249 */ {0x400000008000808a, 0x1000000000000000, 0x0, 0x200000000000000},
/* idx: 250 */ {0x400000008000808a, 0x2000000000000000, 0x0, 0x400000000000000},
/* idx: 251 */ {0x400000008000808a, 0x2000000000000000, 0x0, 0x800000000000000},
/* idx: 252 */ {0x800000008000808a, 0x4000000000000000, 0x0, 0x1000000000000000},
/* idx: 253 */ {0x800000008000808a, 0x4000000000000000, 0x0, 0x2000000000000000},
/* idx: 254 */ {0x800000008000808a, 0x8000000000000000, 0x0, 0x4000000000000000},
/* idx: 255 */ {0x800000008000808a, 0x8000000000000000, 0x0, 0x8000000000000000},
/* idx: 256 */ {0x100010116, 0x1, 0x1, 0x0, 0x1},
/* idx: 257 */ {0x100010116, 0x1, 0x1, 0x0, 0x2},
/* idx: 258 */ {0x100010116, 0x1, 0x2, 0x0, 0x4},
/* idx: 259 */ {0x100010116, 0x1, 0x2, 0x0, 0x8},
/* idx: 260 */ {0x100010116, 0x2, 0x4, 0x0, 0x10},
/* idx: 261 */ {0x100010116, 0x2, 0x4, 0x0, 0x20},
/* idx: 262 */ {0x100010116, 0x2, 0x8, 0x0, 0x40},
/* idx: 263 */ {0x100010116, 0x2, 0x8, 0x0, 0x80},
/* idx: 264 */ {0x200010116, 0x4, 0x10, 0x0, 0x100},
/* idx: 265 */ {0x200010116, 0x4, 0x10, 0x0, 0x200},
/* idx: 266 */ {0x200010116, 0x4, 0x20, 0x0, 0x400},
/* idx: 267 */ {0x200010116, 0x4, 0x20, 0x0, 0x800},
/* idx: 268 */ {0x200010116, 0x8, 0x40, 0x0, 0x1000},
/* idx: 269 */ {0x200010116, 0x8, 0x40, 0x0, 0x2000},
/* idx: 270 */ {0x200010116, 0x8, 0x80, 0x0, 0x4000},
/* idx: 271 */ {0x200010116, 0x8, 0x80, 0x0, 0x8000},
/* idx: 272 */ {0x400020116, 0x10, 0x100, 0x0, 0x10000},
/* idx: 273 */ {0x400020116, 0x10, 0x100, 0x0, 0x20000},
/* idx: 274 */ {0x400020116, 0x10, 0x200, 0x0, 0x40000},
/* idx: 275 */ {0x400020116, 0x10, 0x200, 0x0, 0x80000},
/* idx: 276 */ {0x400020116, 0x20, 0x400, 0x0, 0x100000},
/* idx: 277 */ {0x400020116, 0x20, 0x400, 0x0, 0x200000},
/* idx: 278 */ {0x400020116, 0x20, 0x800, 0x0, 0x400000},
/* idx: 279 */ {0x400020116, 0x20, 0x800, 0x0, 0x800000},
/* idx: 280 */ {0x800020116, 0x40, 0x1000, 0x0, 0x1000000},
/* idx: 281 */ {0x800020116, 0x40, 0x1000, 0x0, 0x2000000},
/* idx: 282 */ {0x800020116, 0x40, 0x2000, 0x0, 0x4000000},
/* idx: 283 */ {0x800020116, 0x40, 0x2000, 0x0, 0x8000000},
/* idx: 284 */ {0x800020116, 0x80, 0x4000, 0x0, 0x10000000},
/* idx: 285 */ {0x800020116, 0x80, 0x4000, 0x0, 0x20000000},
/* idx: 286 */ {0x800020116, 0x80, 0x8000, 0x0, 0x40000000},
/* idx: 287 */ {0x800020116, 0x80, 0x8000, 0x0, 0x80000000},
/* idx: 288 */ {0x1000040216, 0x100, 0x10000, 0x0, 0x100000000},
/* idx: 289 */ {0x1000040216, 0x100, 0x10000, 0x0, 0x200000000},
/* idx: 290 */ {0x1000040216, 0x100, 0x20000, 0x0, 0x400000000},
/* idx: 291 */ {0x1000040216, 0x100, 0x20000, 0x0, 0x800000000},
/* idx: 292 */ {0x1000040216, 0x200, 0x40000, 0x0, 0x1000000000},
/* idx: 293 */ {0x1000040216, 0x200, 0x40000, 0x0, 0x2000000000},
/* idx: 294 */ {0x1000040216, 0x200, 0x80000, 0x0, 0x4000000000},
/* idx: 295 */ {0x1000040216, 0x200, 0x80000, 0x0, 0x8000000000},
/* idx: 296 */ {0x2000040216, 0x400, 0x100000, 0x0, 0x10000000000},
/* idx: 297 */ {0x2000040216, 0x400, 0x100000, 0x0, 0x20000000000},
/* idx: 298 */ {0x2000040216, 0x400, 0x200000, 0x0, 0x40000000000},
/* idx: 299 */ {0x2000040216, 0x400, 0x200000, 0x0, 0x80000000000},
/* idx: 300 */ {0x2000040216, 0x800, 0x400000, 0x0, 0x100000000000},
/* idx: 301 */ {0x2000040216, 0x800, 0x400000, 0x0, 0x200000000000},
/* idx: 302 */ {0x2000040216, 0x800, 0x800000, 0x0, 0x400000000000},
/* idx: 303 */ {0x2000040216, 0x800, 0x800000, 0x0, 0x800000000000},
/* idx: 304 */ {0x4000080216, 0x1000, 0x1000000, 0x0, 0x1000000000000},
/* idx: 305 */ {0x4000080216, 0x1000, 0x1000000, 0x0, 0x2000000000000},
/* idx: 306 */ {0x4000080216, 0x1000, 0x2000000, 0x0, 0x4000000000000},
/* idx: 307 */ {0x4000080216, 0x1000, 0x2000000, 0x0, 0x8000000000000},
/* idx: 308 */ {0x4000080216, 0x2000, 0x4000000, 0x0, 0x10000000000000},
/* idx: 309 */ {0x4000080216, 0x2000, 0x4000000, 0x0, 0x20000000000000},
/* idx: 310 */ {0x4000080216, 0x2000, 0x8000000, 0x0, 0x40000000000000},
/* idx: 311 */ {0x4000080216, 0x2000, 0x8000000, 0x0, 0x80000000000000},
/* idx: 312 */ {0x8000080216, 0x4000, 0x10000000, 0x0, 0x100000000000000},
/* idx: 313 */ {0x8000080216, 0x4000, 0x10000000, 0x0, 0x200000000000000},
/* idx: 314 */ {0x8000080216, 0x4000, 0x20000000, 0x0, 0x400000000000000},
/* idx: 315 */ {0x8000080216, 0x4000, 0x20000000, 0x0, 0x800000000000000},
/* idx: 316 */ {0x8000080216, 0x8000, 0x40000000, 0x0, 0x1000000000000000},
/* idx: 317 */ {0x8000080216, 0x8000, 0x40000000, 0x0, 0x2000000000000000},
/* idx: 318 */ {0x8000080216, 0x8000, 0x80000000, 0x0, 0x4000000000000000},
/* idx: 319 */ {0x8000080216, 0x8000, 0x80000000, 0x0, 0x8000000000000000},
/* idx: 320 */ {0x10000100426, 0x10000, 0x100000000, 0x0, 0x0, 0x1},
/* idx: 321 */ {0x10000100426, 0x10000, 0x100000000, 0x0, 0x0, 0x2},
/* idx: 322 */ {0x10000100426, 0x10000, 0x200000000, 0x0, 0x0, 0x4},
/* idx: 323 */ {0x10000100426, 0x10000, 0x200000000, 0x0, 0x0, 0x8},
/* idx: 324 */ {0x10000100426, 0x20000, 0x400000000, 0x0, 0x0, 0x10},
/* idx: 325 */ {0x10000100426, 0x20000, 0x400000000, 0x0, 0x0, 0x20},
/* idx: 326 */ {0x10000100426, 0x20000, 0x800000000, 0x0, 0x0, 0x40},
/* idx: 327 */ {0x10000100426, 0x20000, 0x800000000, 0x0, 0x0, 0x80},
/* idx: 328 */ {0x20000100426, 0x40000, 0x1000000000, 0x0, 0x0, 0x100},
/* idx: 329 */ {0x20000100426, 0x40000, 0x1000000000, 0x0, 0x0, 0x200},
/* idx: 330 */ {0x20000100426, 0x40000, 0x2000000000, 0x0, 0x0, 0x400},
/* idx: 331 */ {0x20000100426, 0x40000, 0x2000000000, 0x0, 0x0, 0x800},
/* idx: 332 */ {0x20000100426, 0x80000, 0x4000000000, 0x0, 0x0, 0x1000},
/* idx: 333 */ {0x20000100426, 0x80000, 0x4000000000, 0x0, 0x0, 0x2000},
/* idx: 334 */ {0x20000100426, 0x80000, 0x8000000000, 0x0, 0x0, 0x4000},
/* idx: 335 */ {0x20000100426, 0x80000, 0x8000000000, 0x0, 0x0, 0x8000},
/* idx: 336 */ {0x40000200426, 0x100000, 0x10000000000, 0x0, 0x0, 0x10000},
/* idx: 337 */ {0x40000200426, 0x100000, 0x10000000000, 0x0, 0x0, 0x20000},
/* idx: 338 */ {0x40000200426, 0x100000, 0x20000000000, 0x0, 0x0, 0x40000},
/* idx: 339 */ {0x40000200426, 0x100000, 0x20000000000, 0x0, 0x0, 0x80000},
/* idx: 340 */ {0x40000200426, 0x200000, 0x40000000000, 0x0, 0x0, 0x100000},
/* idx: 341 */ {0x40000200426, 0x200000, 0x40000000000, 0x0, 0x0, 0x200000},
/* idx: 342 */ {0x40000200426, 0x200000, 0x80000000000, 0x0, 0x0, 0x400000},
/* idx: 343 */ {0x40000200426, 0x200000, 0x80000000000, 0x0, 0x0, 0x800000},
/* idx: 344 */ {0x80000200426, 0x400000, 0x100000000000, 0x0, 0x0, 0x1000000},
/* idx: 345 */ {0x80000200426, 0x400000, 0x100000000000, 0x0, 0x0, 0x2000000},
/* idx: 346 */ {0x80000200426, 0x400000, 0x200000000000, 0x0, 0x0, 0x4000000},
/* idx: 347 */ {0x80000200426, 0x400000, 0x200000000000, 0x0, 0x0, 0x8000000},
/* idx: 348 */ {0x80000200426, 0x800000, 0x400000000000, 0x0, 0x0, 0x10000000},
/* idx: 349 */ {0x80000200426, 0x800000, 0x400000000000, 0x0, 0x0, 0x20000000},
/* idx: 350 */ {0x80000200426, 0x800000, 0x800000000000, 0x0, 0x0, 0x40000000},
/* idx: 351 */ {0x80000200426, 0x800000, 0x800000000000, 0x0, 0x0, 0x80000000},
/* idx: 352 */ {0x100000400826, 0x1000000, 0x1000000000000, 0x0, 0x0, 0x100000000},
/* idx: 353 */ {0x100000400826, 0x1000000, 0x1000000000000, 0x0, 0x0, 0x200000000},
/* idx: 354 */ {0x100000400826, 0x1000000, 0x2000000000000, 0x0, 0x0, 0x400000000},
/* idx: 355 */ {0x100000400826, 0x1000000, 0x2000000000000, 0x0, 0x0, 0x800000000},
/* idx: 356 */ {0x100000400826, 0x2000000, 0x4000000000000, 0x0, 0x0, 0x1000000000},
/* idx: 357 */ {0x100000400826, 0x2000000, 0x4000000000000, 0x0, 0x0, 0x2000000000},
/* idx: 358 */ {0x100000400826, 0x2000000, 0x8000000000000, 0x0, 0x0, 0x4000000000},
/* idx: 359 */ {0x100000400826, 0x2000000, 0x8000000000000, 0x0, 0x0, 0x8000000000},
/* idx: 360 */ {0x200000400826, 0x4000000, 0x10000000000000, 0x0, 0x0, 0x10000000000},
/* idx: 361 */ {0x200000400826, 0x4000000, 0x10000000000000, 0x0, 0x0, 0x20000000000},
/* idx: 362 */ {0x200000400826, 0x4000000, 0x20000000000000, 0x0, 0x0, 0x40000000000},
/* idx: 363 */ {0x200000400826, 0x4000000, 0x20000000000000, 0x0, 0x0, 0x80000000000},
/* idx: 364 */ {0x200000400826, 0x8000000, 0x40000000000000, 0x0, 0x0, 0x100000000000},
/* idx: 365 */ {0x200000400826, 0x8000000, 0x40000000000000, 0x0, 0x0, 0x200000000000},
/* idx: 366 */ {0x200000400826, 0x8000000, 0x80000000000000, 0x0, 0x0, 0x400000000000},
/* idx: 367 */ {0x200000400826, 0x8000000, 0x80000000000000, 0x0, 0x0, 0x800000000000},
/* idx: 368 */ {0x400000800826, 0x10000000, 0x100000000000000, 0x0, 0x0, 0x1000000000000},
/* idx: 369 */ {0x400000800826, 0x10000000, 0x100000000000000, 0x0, 0x0, 0x2000000000000},
/* idx: 370 */ {0x400000800826, 0x10000000, 0x200000000000000, 0x0, 0x0, 0x4000000000000},
/* idx: 371 */ {0x400000800826, 0x10000000, 0x200000000000000, 0x0, 0x0, 0x8000000000000},
/* idx: 372 */ {0x400000800826, 0x20000000, 0x400000000000000, 0x0, 0x0, 0x10000000000000},
/* idx: 373 */ {0x400000800826, 0x20000000, 0x400000000000000, 0x0, 0x0, 0x20000000000000},
/* idx: 374 */ {0x400000800826, 0x20000000, 0x800000000000000, 0x0, 0x0, 0x40000000000000},
/* idx: 375 */ {0x400000800826, 0x20000000, 0x800000000000000, 0x0, 0x0, 0x80000000000000},
/* idx: 376 */ {0x800000800826, 0x40000000, 0x1000000000000000, 0x0, 0x0, 0x100000000000000},
/* idx: 377 */ {0x800000800826, 0x40000000, 0x1000000000000000, 0x0, 0x0, 0x200000000000000},
/* idx: 378 */ {0x800000800826, 0x40000000, 0x2000000000000000, 0x0, 0x0, 0x400000000000000},
/* idx: 379 */ {0x800000800826, 0x40000000, 0x2000000000000000, 0x0, 0x0, 0x800000000000000},
/* idx: 380 */ {0x800000800826, 0x80000000, 0x4000000000000000, 0x0, 0x0, 0x1000000000000000},
/* idx: 381 */ {0x800000800826, 0x80000000, 0x4000000000000000, 0x0, 0x0, 0x2000000000000000},
/* idx: 382 */ {0x800000800826, 0x80000000, 0x8000000000000000, 0x0, 0x0, 0x4000000000000000},
/* idx: 383 */ {0x800000800826, 0x80000000, 0x8000000000000000, 0x0, 0x0, 0x8000000000000000},
/* idx: 384 */ {0x100000100104a, 0x100000000, 0x0, 0x1, 0x0, 0x0, 0x1},
/* idx: 385 */ {0x100000100104a, 0x100000000, 0x0, 0x1, 0x0, 0x0, 0x2},
/* idx: 386 */ {0x100000100104a, 0x100000000, 0x0, 0x2, 0x0, 0x0, 0x4},
/* idx: 387 */ {0x100000100104a, 0x100000000, 0x0, 0x2, 0x0, 0x0, 0x8},
/* idx: 388 */ {0x100000100104a, 0x200000000, 0x0, 0x4, 0x0, 0x0, 0x10},
/* idx: 389 */ {0x100000100104a, 0x200000000, 0x0, 0x4, 0x0, 0x0, 0x20},
/* idx: 390 */ {0x100000100104a, 0x200000000, 0x0, 0x8, 0x0, 0x0, 0x40},
/* idx: 391 */ {0x100000100104a, 0x200000000, 0x0, 0x8, 0x0, 0x0, 0x80},
/* idx: 392 */ {0x200000100104a, 0x400000000, 0x0, 0x10, 0x0, 0x0, 0x100},
/* idx: 393 */ {0x200000100104a, 0x400000000, 0x0, 0x10, 0x0, 0x0, 0x200},
/* idx: 394 */ {0x200000100104a, 0x400000000, 0x0, 0x20, 0x0, 0x0, 0x400},
/* idx: 395 */ {0x200000100104a, 0x400000000, 0x0, 0x20, 0x0, 0x0, 0x800},
/* idx: 396 */ {0x200000100104a, 0x800000000, 0x0, 0x40, 0x0, 0x0, 0x1000},
/* idx: 397 */ {0x200000100104a, 0x800000000, 0x0, 0x40, 0x0, 0x0, 0x2000},
/* idx: 398 */ {0x200000100104a, 0x800000000, 0x0, 0x80, 0x0, 0x0, 0x4000},
/* idx: 399 */ {0x200000100104a, 0x800000000, 0x0, 0x80, 0x0, 0x0, 0x8000},
/* idx: 400 */ {0x400000200104a, 0x1000000000, 0x0, 0x100, 0x0, 0x0, 0x10000},
/* idx: 401 */ {0x400000200104a, 0x1000000000, 0x0, 0x100, 0x0, 0x0, 0x20000},
/* idx: 402 */ {0x400000200104a, 0x1000000000, 0x0, 0x200, 0x0, 0x0, 0x40000},
/* idx: 403 */ {0x400000200104a, 0x1000000000, 0x0, 0x200, 0x0, 0x0, 0x80000},
/* idx: 404 */ {0x400000200104a, 0x2000000000, 0x0, 0x400, 0x0, 0x0, 0x100000},
/* idx: 405 */ {0x400000200104a, 0x2000000000, 0x0, 0x400, 0x0, 0x0, 0x200000},
/* idx: 406 */ {0x400000200104a, 0x2000000000, 0x0, 0x800, 0x0, 0x0, 0x400000},
/* idx: 407 */ {0x400000200104a, 0x2000000000, 0x0, 0x800, 0x0, 0x0, 0x800000},
/* idx: 408 */ {0x800000200104a, 0x4000000000, 0x0, 0x1000, 0x0, 0x0, 0x1000000},
/* idx: 409 */ {0x800000200104a, 0x4000000000, 0x0, 0x1000, 0x0, 0x0, 0x2000000},
/* idx: 410 */ {0x800000200104a, 0x4000000000, 0x0, 0x2000, 0x0, 0x0, 0x4000000},
/* idx: 411 */ {0x800000200104a, 0x4000000000, 0x0, 0x2000, 0x0, 0x0, 0x8000000},
/* idx: 412 */ {0x800000200104a, 0x8000000000, 0x0, 0x4000, 0x0, 0x0, 0x10000000},
/* idx: 413 */ {0x800000200104a, 0x8000000000, 0x0, 0x4000, 0x0, 0x0, 0x20000000},
/* idx: 414 */ {0x800000200104a, 0x8000000000, 0x0, 0x8000, 0x0, 0x0, 0x40000000},
/* idx: 415 */ {0x800000200104a, 0x8000000000, 0x0, 0x8000, 0x0, 0x0, 0x80000000},
/* idx: 416 */ {0x1000000400204a, 0x10000000000, 0x0, 0x10000, 0x0, 0x0, 0x100000000},
/* idx: 417 */ {0x1000000400204a, 0x10000000000, 0x0, 0x10000, 0x0, 0x0, 0x200000000},
/* idx: 418 */ {0x1000000400204a, 0x10000000000, 0x0, 0x20000, 0x0, 0x0, 0x400000000},
/* idx: 419 */ {0x1000000400204a, 0x10000000000, 0x0, 0x20000, 0x0, 0x0, 0x800000000},
/* idx: 420 */ {0x1000000400204a, 0x20000000000, 0x0, 0x40000, 0x0, 0x0, 0x1000000000},
/* idx: 421 */ {0x1000000400204a, 0x20000000000, 0x0, 0x40000, 0x0, 0x0, 0x2000000000},
/* idx: 422 */ {0x1000000400204a, 0x20000000000, 0x0, 0x80000, 0x0, 0x0, 0x4000000000},
/* idx: 423 */ {0x1000000400204a, 0x20000000000, 0x0, 0x80000, 0x0, 0x0, 0x8000000000},
/* idx: 424 */ {0x2000000400204a, 0x40000000000, 0x0, 0x100000, 0x0, 0x0, 0x10000000000},
/* idx: 425 */ {0x2000000400204a, 0x40000000000, 0x0, 0x100000, 0x0, 0x0, 0x20000000000},
/* idx: 426 */ {0x2000000400204a, 0x40000000000, 0x0, 0x200000, 0x0, 0x0, 0x40000000000},
/* idx: 427 */ {0x2000000400204a, 0x40000000000, 0x0, 0x200000, 0x0, 0x0, 0x80000000000},
/* idx: 428 */ {0x2000000400204a, 0x80000000000, 0x0, 0x400000, 0x0, 0x0, 0x100000000000},
/* idx: 429 */ {0x2000000400204a, 0x80000000000, 0x0, 0x400000, 0x0, 0x0, 0x200000000000},
/* idx: 430 */ {0x2000000400204a, 0x80000000000, 0x0, 0x800000, 0x0, 0x0, 0x400000000000},
/* idx: 431 */ {0x2000000400204a, 0x80000000000, 0x0, 0x800000, 0x0, 0x0, 0x800000000000},
/* idx: 432 */ {0x4000000800204a, 0x100000000000, 0x0, 0x1000000, 0x0, 0x0, 0x1000000000000},
/* idx: 433 */ {0x4000000800204a, 0x100000000000, 0x0, 0x1000000, 0x0, 0x0, 0x2000000000000},
/* idx: 434 */ {0x4000000800204a, 0x100000000000, 0x0, 0x2000000, 0x0, 0x0, 0x4000000000000},
/* idx: 435 */ {0x4000000800204a, 0x100000000000, 0x0, 0x2000000, 0x0, 0x0, 0x8000000000000},
/* idx: 436 */ {0x4000000800204a, 0x200000000000, 0x0, 0x4000000, 0x0, 0x0, 0x10000000000000},
/* idx: 437 */ {0x4000000800204a, 0x200000000000, 0x0, 0x4000000, 0x0, 0x0, 0x20000000000000},
/* idx: 438 */ {0x4000000800204a, 0x200000000000, 0x0, 0x8000000, 0x0, 0x0, 0x40000000000000},
/* idx: 439 */ {0x4000000800204a, 0x200000000000, 0x0, 0x8000000, 0x0, 0x0, 0x80000000000000},
/* idx: 440 */ {0x8000000800204a, 0x400000000000, 0x0, 0x10000000, 0x0, 0x0, 0x100000000000000},
/* idx: 441 */ {0x8000000800204a, 0x400000000000, 0x0, 0x10000000, 0x0, 0x0, 0x200000000000000},
/* idx: 442 */ {0x8000000800204a, 0x400000000000, 0x0, 0x20000000, 0x0, 0x0, 0x400000000000000},
/* idx: 443 */ {0x8000000800204a, 0x400000000000, 0x0, 0x20000000, 0x0, 0x0, 0x800000000000000},
/* idx: 444 */ {0x8000000800204a, 0x800000000000, 0x0, 0x40000000, 0x0, 0x0, 0x1000000000000000},
/* idx: 445 */ {0x8000000800204a, 0x800000000000, 0x0, 0x40000000, 0x0, 0x0, 0x2000000000000000},
/* idx: 446 */ {0x8000000800204a, 0x800000000000, 0x0, 0x80000000, 0x0, 0x0, 0x4000000000000000},
/* idx: 447 */ {0x8000000800204a, 0x800000000000, 0x0, 0x80000000, 0x0, 0x0, 0x8000000000000000},
/* idx: 448 */ {0x10000001000408a, 0x1000000000000, 0x0, 0x100000000, 0x0, 0x0, 0x0, 0x1},
/* idx: 449 */ {0x10000001000408a, 0x1000000000000, 0x0, 0x100000000, 0x0, 0x0, 0x0, 0x2},
/* idx: 450 */ {0x10000001000408a, 0x1000000000000, 0x0, 0x200000000, 0x0, 0x0, 0x0, 0x4},
/* idx: 451 */ {0x10000001000408a, 0x1000000000000, 0x0, 0x200000000, 0x0, 0x0, 0x0, 0x8},
/* idx: 452 */ {0x10000001000408a, 0x2000000000000, 0x0, 0x400000000, 0x0, 0x0, 0x0, 0x10},
/* idx: 453 */ {0x10000001000408a, 0x2000000000000, 0x0, 0x400000000, 0x0, 0x0, 0x0, 0x20},
/* idx: 454 */ {0x10000001000408a, 0x2000000000000, 0x0, 0x800000000, 0x0, 0x0, 0x0, 0x40},
/* idx: 455 */ {0x10000001000408a, 0x2000000000000, 0x0, 0x800000000, 0x0, 0x0, 0x0, 0x80},
/* idx: 456 */ {0x20000001000408a, 0x4000000000000, 0x0, 0x1000000000, 0x0, 0x0, 0x0, 0x100},
/* idx: 457 */ {0x20000001000408a, 0x4000000000000, 0x0, 0x1000000000, 0x0, 0x0, 0x0, 0x200},
/* idx: 458 */ {0x20000001000408a, 0x4000000000000, 0x0, 0x2000000000, 0x0, 0x0, 0x0, 0x400},
/* idx: 459 */ {0x20000001000408a, 0x4000000000000, 0x0, 0x2000000000, 0x0, 0x0, 0x0, 0x800},
/* idx: 460 */ {0x20000001000408a, 0x8000000000000, 0x0, 0x4000000000, 0x0, 0x0, 0x0, 0x1000},
/* idx: 461 */ {0x20000001000408a, 0x8000000000000, 0x0, 0x4000000000, 0x0, 0x0, 0x0, 0x2000},
/* idx: 462 */ {0x20000001000408a, 0x8000000000000, 0x0, 0x8000000000, 0x0, 0x0, 0x0, 0x4000},
/* idx: 463 */ {0x20000001000408a, 0x8000000000000, 0x0, 0x8000000000, 0x0, 0x0, 0x0, 0x8000},
/* idx: 464 */ {0x40000002000408a, 0x10000000000000, 0x0, 0x10000000000, 0x0, 0x0, 0x0, 0x10000},
/* idx: 465 */ {0x40000002000408a, 0x10000000000000, 0x0, 0x10000000000, 0x0, 0x0, 0x0, 0x20000},
/* idx: 466 */ {0x40000002000408a, 0x10000000000000, 0x0, 0x20000000000, 0x0, 0x0, 0x0, 0x40000},
/* idx: 467 */ {0x40000002000408a, 0x10000000000000, 0x0, 0x20000000000, 0x0, 0x0, 0x0, 0x80000},
/* idx: 468 */ {0x40000002000408a, 0x20000000000000, 0x0, 0x40000000000, 0x0, 0x0, 0x0, 0x100000},
/* idx: 469 */ {0x40000002000408a, 0x20000000000000, 0x0, 0x40000000000, 0x0, 0x0, 0x0, 0x200000},
/* idx: 470 */ {0x40000002000408a, 0x20000000000000, 0x0, 0x80000000000, 0x0, 0x0, 0x0, 0x400000},
/* idx: 471 */ {0x40000002000408a, 0x20000000000000, 0x0, 0x80000000000, 0x0, 0x0, 0x0, 0x800000},
/* idx: 472 */ {0x80000002000408a, 0x40000000000000, 0x0, 0x100000000000, 0x0, 0x0, 0x0, 0x1000000},
/* idx: 473 */ {0x80000002000408a, 0x40000000000000, 0x0, 0x100000000000, 0x0, 0x0, 0x0, 0x2000000},
/* idx: 474 */ {0x80000002000408a, 0x40000000000000, 0x0, 0x200000000000, 0x0, 0x0, 0x0, 0x4000000},
/* idx: 475 */ {0x80000002000408a, 0x40000000000000, 0x0, 0x200000000000, 0x0, 0x0, 0x0, 0x8000000},
/* idx: 476 */ {0x80000002000408a, 0x80000000000000, 0x0, 0x400000000000, 0x0, 0x0, 0x0, 0x10000000},
/* idx: 477 */ {0x80000002000408a, 0x80000000000000, 0x0, 0x400000000000, 0x0, 0x0, 0x0, 0x20000000},
/* idx: 478 */ {0x80000002000408a, 0x80000000000000, 0x0, 0x800000000000, 0x0, 0x0, 0x0, 0x40000000},
/* idx: 479 */ {0x80000002000408a, 0x80000000000000, 0x0, 0x800000000000, 0x0, 0x0, 0x0, 0x80000000},
/* idx: 480 */ {0x100000004000808a, 0x100000000000000, 0x0, 0x1000000000000, 0x0, 0x0, 0x0, 0x100000000},
/* idx: 481 */ {0x100000004000808a, 0x100000000000000, 0x0, 0x1000000000000, 0x0, 0x0, 0x0, 0x200000000},
/* idx: 482 */ {0x100000004000808a, 0x100000000000000, 0x0, 0x2000000000000, 0x0, 0x0, 0x0, 0x400000000},
/* idx: 483 */ {0x100000004000808a, 0x100000000000000, 0x0, 0x2000000000000, 0x0, 0x0, 0x0, 0x800000000},
/* idx: 484 */ {0x100000004000808a, 0x200000000000000, 0x0, 0x4000000000000, 0x0, 0x0, 0x0, 0x1000000000},
/* idx: 485 */ {0x100000004000808a, 0x200000000000000, 0x0, 0x4000000000000, 0x0, 0x0, 0x0, 0x2000000000},
/* idx: 486 */ {0x100000004000808a, 0x200000000000000, 0x0, 0x8000000000000, 0x0, 0x0, 0x0, 0x4000000000},
/* idx: 487 */ {0x100000004000808a, 0x200000000000000, 0x0, 0x8000000000000, 0x0, 0x0, 0x0, 0x8000000000},
/* idx: 488 */ {0x200000004000808a, 0x400000000000000, 0x0, 0x10000000000000, 0x0, 0x0, 0x0, 0x10000000000},
/* idx: 489 */ {0x200000004000808a, 0x400000000000000, 0x0, 0x10000000000000, 0x0, 0x0, 0x0, 0x20000000000},
/* idx: 490 */ {0x200000004000808a, 0x400000000000000, 0x0, 0x20000000000000, 0x0, 0x0, 0x0, 0x40000000000},
/* idx: 491 */ {0x200000004000808a, 0x400000000000000, 0x0, 0x20000000000000, 0x0, 0x0, 0x0, 0x80000000000},
/* idx: 492 */ {0x200000004000808a, 0x800000000000000, 0x0, 0x40000000000000, 0x0, 0x0, 0x0, 0x100000000000},
/* idx: 493 */ {0x200000004000808a, 0x800000000000000, 0x0, 0x40000000000000, 0x0, 0x0, 0x0, 0x200000000000},
/* idx: 494 */ {0x200000004000808a, 0x800000000000000, 0x0, 0x80000000000000, 0x0, 0x0, 0x0, 0x400000000000},
/* idx: 495 */ {0x200000004000808a, 0x800000000000000, 0x0, 0x80000000000000, 0x0, 0x0, 0x0, 0x800000000000},
/* idx: 496 */ {0x400000008000808a, 0x1000000000000000, 0x0, 0x100000000000000, 0x0, 0x0, 0x0, 0x1000000000000},
/* idx: 497 */ {0x400000008000808a, 0x1000000000000000, 0x0, 0x100000000000000, 0x0, 0x0, 0x0, 0x2000000000000},
/* idx: 498 */ {0x400000008000808a, 0x1000000000000000, 0x0, 0x200000000000000, 0x0, 0x0, 0x0, 0x4000000000000},
/* idx: 499 */ {0x400000008000808a, 0x1000000000000000, 0x0, 0x200000000000000, 0x0, 0x0, 0x0, 0x8000000000000},
/* idx: 500 */ {0x400000008000808a, 0x2000000000000000, 0x0, 0x400000000000000, 0x0, 0x0, 0x0, 0x10000000000000},
/* idx: 501 */ {0x400000008000808a, 0x2000000000000000, 0x0, 0x400000000000000, 0x0, 0x0, 0x0, 0x20000000000000},
/* idx: 502 */ {0x400000008000808a, 0x2000000000000000, 0x0, 0x800000000000000, 0x0, 0x0, 0x0, 0x40000000000000},
/* idx: 503 */ {0x400000008000808a, 0x2000000000000000, 0x0, 0x800000000000000, 0x0, 0x0, 0x0, 0x80000000000000},
/* idx: 504 */ {0x800000008000808a, 0x4000000000000000, 0x0, 0x1000000000000000, 0x0, 0x0, 0x0, 0x100000000000000},
/* idx: 505 */ {0x800000008000808a, 0x4000000000000000, 0x0, 0x1000000000000000, 0x0, 0x0, 0x0, 0x200000000000000},
/* idx: 506 */ {0x800000008000808a, 0x4000000000000000, 0x0, 0x2000000000000000, 0x0, 0x0, 0x0, 0x400000000000000},
/* idx: 507 */ {0x800000008000808a, 0x4000000000000000, 0x0, 0x2000000000000000, 0x0, 0x0, 0x0, 0x800000000000000},
/* idx: 508 */ {0x800000008000808a, 0x8000000000000000, 0x0, 0x4000000000000000, 0x0, 0x0, 0x0, 0x1000000000000000},
/* idx: 509 */ {0x800000008000808a, 0x8000000000000000, 0x0, 0x4000000000000000, 0x0, 0x0, 0x0, 0x2000000000000000},
/* idx: 510 */ {0x800000008000808a, 0x8000000000000000, 0x0, 0x8000000000000000, 0x0, 0x0, 0x0, 0x4000000000000000},
/* idx: 511 */ {0x800000008000808a, 0x8000000000000000, 0x0, 0x8000000000000000, 0x0, 0x0, 0x0, 0x8000000000000000},
}
-729
View File
@@ -1,729 +0,0 @@
// Copyright (c) 2024 Karl Gaissmaier
// SPDX-License-Identifier: MIT
package bart
import (
"net/netip"
"slices"
"github.com/gaissmai/bart/internal/sparse"
)
const (
strideLen = 8 // octet
maxTreeDepth = 16 // 16 for IPv6
maxNodeChildren = 256 // 256
maxNodePrefixes = 512 // 512
)
// stridePath, max 16 octets deep
type stridePath [maxTreeDepth]uint8
// node is a level node in the multibit-trie.
// A node has prefixes and children, forming the multibit trie.
//
// The prefixes form a complete binary tree, see the artlookup.pdf
// paper in the doc folder to understand the data structure.
//
// In contrast to the ART algorithm, sparse arrays
// (popcount-compressed slices) are used instead of fixed-size arrays.
//
// The array slots are also not pre-allocated (alloted) as described
// in the ART algorithm, fast backtracking with a bitset vector is used
// to get the longest-prefix-match.
//
// The sparse child array recursively spans the trie with a branching factor of 256
// and also records path-compressed leaves in the free node slots.
type node[V any] struct {
// prefixes contains the routes indexed as a complete binary tree with payload V
// with the help of the baseIndex function from the ART algorithm.
prefixes sparse.Array[V]
// children, recursively spans the trie with a branching factor of 256.
//
// Sorry, here we have to use a mixture of generics and interfaces:
//
// Without path compression, the definition would naturally be:
// children sparse.Array[*node[V]]
//
// ... but with path compression the child can now be a node or a path compressed leaf.
//
// With path compression we could define:
// type noder[V any] interface {
// isLeaf[V]() bool
// }
//
// and:
// children sparse.Array[noder[V]]
//
// But we use the empty interface{} instead, by intention, see below!
//
// The empty interface{} consumes less memory and type assertions are faster than
// indirect method calls like node.isLeaf()
children sparse.Array[interface{}]
}
// isEmpty returns true if node has neither prefixes nor children
func (n *node[V]) isEmpty() bool {
return n.prefixes.Len() == 0 && n.children.Len() == 0
}
// leaf is a prefix with value, used as a path compressed child
type leaf[V any] struct {
prefix netip.Prefix
value V
}
// cloneOrCopy, helper function,
// deep copy if v implements the Cloner interface.
func cloneOrCopy[V any](val V) V {
if cloner, ok := any(val).(Cloner[V]); ok {
return cloner.Clone()
}
// just a shallow copy
return val
}
// cloneLeaf returns a clone of the leaf
// if the value implements the Cloner interface.
func (l *leaf[V]) cloneLeaf() *leaf[V] {
return &leaf[V]{l.prefix, cloneOrCopy(l.value)}
}
// insertAtDepth insert a prefix/val into a node tree at depth.
// n must not be nil, prefix must be valid and already in canonical form.
//
// If a path compression has to be resolved because a new value is added
// that collides with a leaf, the compressed leaf is then reinserted
// one depth down in the node trie.
func (n *node[V]) insertAtDepth(pfx netip.Prefix, val V, depth int) (exists bool) {
ip := pfx.Addr()
bits := pfx.Bits()
lastIdx, lastBits := lastOctetIdxAndBits(bits)
octets := ip.AsSlice()
octets = octets[:lastIdx+1]
// find the proper trie node to insert prefix
// start with prefix octet at depth
for ; depth < len(octets); depth++ {
octet := octets[depth]
addr := uint(octet)
// last significant octet: insert/override prefix/val into node
if depth == lastIdx {
return n.prefixes.InsertAt(pfxToIdx(octet, lastBits), val)
}
if !n.children.Test(addr) {
// insert prefix path compressed
return n.children.InsertAt(addr, &leaf[V]{pfx, val})
}
// get the child: node or leaf
switch kid := n.children.MustGet(addr).(type) {
case *node[V]:
n = kid
continue // descend down to next trie level
case *leaf[V]:
// reached a path compressed prefix
// override value in slot if prefixes are equal
if kid.prefix == pfx {
kid.value = val
// exists
return true
}
// create new node
// push the leaf down
// insert new child at current leaf position (addr)
// descend down, replace n with new child
newNode := new(node[V])
newNode.insertAtDepth(kid.prefix, kid.value, depth+1)
n.children.InsertAt(addr, newNode)
n = newNode
default:
panic("logic error, wrong node type")
}
}
panic("unreachable")
}
// insertAtDepthPersist is the immutable version of insertAtDepth.
// All visited nodes are cloned during insertion.
func (n *node[V]) insertAtDepthPersist(pfx netip.Prefix, val V, depth int) (exists bool) {
ip := pfx.Addr()
bits := pfx.Bits()
lastIdx, lastBits := lastOctetIdxAndBits(bits)
octets := ip.AsSlice()
octets = octets[:lastIdx+1]
// find the proper trie node to insert prefix
// start with prefix octet at depth
for ; depth < len(octets); depth++ {
octet := octets[depth]
addr := uint(octet)
// last significant octet: insert/override prefix/val into node
if depth == lastIdx {
return n.prefixes.InsertAt(pfxToIdx(octet, lastBits), val)
}
if !n.children.Test(addr) {
// insert new prefix path compressed
return n.children.InsertAt(addr, &leaf[V]{pfx, val})
}
// get the child: node or leaf, but clone the path down
switch kid := n.children.MustGet(addr).(type) {
case *node[V]:
// proceed to next level
kid = kid.cloneFlat()
n.children.InsertAt(addr, kid)
n = kid
continue // descend down to next trie level
case *leaf[V]:
kid = kid.cloneLeaf()
// override value in slot if prefixes are equal
if kid.prefix == pfx {
// exists
return n.children.InsertAt(addr, &leaf[V]{pfx, val})
}
// create new node
// push the leaf down
// insert new child at current leaf position (addr)
// descend down, replace n with new child
newNode := new(node[V])
newNode.insertAtDepth(kid.prefix, kid.value, depth+1)
n.children.InsertAt(addr, newNode)
n = newNode
default:
panic("logic error, wrong node type")
}
}
panic("unreachable")
}
// purgeAndCompress, purge empty nodes or compress nodes with single prefix or leaf.
func (n *node[V]) purgeAndCompress(parentStack []*node[V], childPath []uint8, is4 bool) {
// unwind the stack
for i := len(parentStack) - 1; i >= 0; i-- {
parent := parentStack[i]
addr := uint(childPath[i])
pfxCount := n.prefixes.Len()
childCount := n.children.Len()
switch {
case n.isEmpty():
// purge empty node
parent.children.DeleteAt(addr)
case pfxCount == 1 && childCount == 0:
// make leaf from prefix idx, shift leaf one level up
// and override current node with new leaf
idx, _ := n.prefixes.FirstSet()
val := n.prefixes.Items[0]
path := stridePath{}
copy(path[:], childPath)
pfx := cidrFromPath(path, i+1, is4, idx)
parent.children.InsertAt(addr, &leaf[V]{pfx, val})
case pfxCount == 0 && childCount == 1:
// if single child is a leaf, shift it up one level
// and override current node with this leaf
if leafPtr, ok := n.children.Items[0].(*leaf[V]); ok {
parent.children.InsertAt(addr, leafPtr)
}
}
n = parent
}
}
// lpmGet does a route lookup for idx in the 8-bit (stride) routing table
// at this depth and returns (baseIdx, value, true) if a matching
// longest prefix exists, or ok=false otherwise.
//
// The prefixes in the stride form a complete binary tree (CBT) using the baseIndex function.
// In contrast to the ART algorithm, I do not use an allotment approach but map
// the backtracking in the CBT by a bitset operation with a precalculated backtracking path
// for the respective idx.
func (n *node[V]) lpmGet(idx uint) (baseIdx uint, val V, ok bool) {
// top is the idx of the longest-prefix-match
if top, ok := n.prefixes.IntersectionTop(lpmLookupTbl[idx]); ok {
return top, n.prefixes.MustGet(top), true
}
// not found (on this level)
return 0, val, false
}
// lpmTest for faster lpm tests without value returns.
func (n *node[V]) lpmTest(idx uint) bool {
return n.prefixes.IntersectsAny(lpmLookupTbl[idx])
}
// cloneRec, clones the node recursive.
func (n *node[V]) cloneRec() *node[V] {
if n == nil {
return nil
}
c := new(node[V])
if n.isEmpty() {
return c
}
// shallow
c.prefixes = *(n.prefixes.Copy())
_, isCloner := any(*new(V)).(Cloner[V])
// deep copy if V implements Cloner[V]
if isCloner {
for i, val := range c.prefixes.Items {
c.prefixes.Items[i] = cloneOrCopy(val)
}
}
// shallow
c.children = *(n.children.Copy())
// deep copy of nodes and leaves
for i, kidAny := range c.children.Items {
switch kid := kidAny.(type) {
case *node[V]:
// clone the child node rec-descent
c.children.Items[i] = kid.cloneRec()
case *leaf[V]:
// deep copy if V implements Cloner[V]
c.children.Items[i] = kid.cloneLeaf()
default:
panic("logic error, wrong node type")
}
}
return c
}
// cloneFlat, copies the node and clone the values in prefixes and path compressed leaves
// if V implements Cloner. Used in the various ...Persist functions.
func (n *node[V]) cloneFlat() *node[V] {
if n == nil {
return nil
}
c := new(node[V])
if n.isEmpty() {
return c
}
// shallow copy
c.prefixes = *(n.prefixes.Copy())
c.children = *(n.children.Copy())
if _, ok := any(*new(V)).(Cloner[V]); !ok {
// if V doesn't implement Cloner[V], return early
return c
}
// deep copy of values in prefixes
for i, val := range c.prefixes.Items {
c.prefixes.Items[i] = cloneOrCopy(val)
}
// deep copy of values in path compressed leaves
for i, kidAny := range c.children.Items {
if kidLeaf, ok := kidAny.(*leaf[V]); ok {
c.children.Items[i] = kidLeaf.cloneLeaf()
}
}
return c
}
// allRec runs recursive the trie, starting at this node and
// the yield function is called for each route entry with prefix and value.
// If the yield function returns false the recursion ends prematurely and the
// false value is propagated.
//
// The iteration order is not defined, just the simplest and fastest recursive implementation.
func (n *node[V]) allRec(path stridePath, depth int, is4 bool, yield func(netip.Prefix, V) bool) bool {
// for all prefixes in this node do ...
allIndices := n.prefixes.AsSlice(make([]uint, 0, maxNodePrefixes))
for _, idx := range allIndices {
cidr := cidrFromPath(path, depth, is4, idx)
// callback for this prefix and val
if !yield(cidr, n.prefixes.MustGet(idx)) {
// early exit
return false
}
}
// for all children (nodes and leaves) in this node do ...
allChildAddrs := n.children.AsSlice(make([]uint, 0, maxNodeChildren))
for i, addr := range allChildAddrs {
switch kid := n.children.Items[i].(type) {
case *node[V]:
// rec-descent with this node
path[depth] = byte(addr)
if !kid.allRec(path, depth+1, is4, yield) {
// early exit
return false
}
case *leaf[V]:
// callback for this leaf
if !yield(kid.prefix, kid.value) {
// early exit
return false
}
default:
panic("logic error, wrong node type")
}
}
return true
}
// allRecSorted runs recursive the trie, starting at node and
// the yield function is called for each route entry with prefix and value.
// The iteration is in prefix sort order.
//
// If the yield function returns false the recursion ends prematurely and the
// false value is propagated.
func (n *node[V]) allRecSorted(path stridePath, depth int, is4 bool, yield func(netip.Prefix, V) bool) bool {
// get slice of all child octets, sorted by addr
allChildAddrs := n.children.AsSlice(make([]uint, 0, maxNodeChildren))
// get slice of all indexes, sorted by idx
allIndices := n.prefixes.AsSlice(make([]uint, 0, maxNodePrefixes))
// sort indices in CIDR sort order
slices.SortFunc(allIndices, cmpIndexRank)
childCursor := 0
// yield indices and childs in CIDR sort order
for _, pfxIdx := range allIndices {
pfxOctet, _ := idxToPfx(pfxIdx)
// yield all childs before idx
for j := childCursor; j < len(allChildAddrs); j++ {
childAddr := allChildAddrs[j]
if childAddr >= uint(pfxOctet) {
break
}
// yield the node (rec-descent) or leaf
switch kid := n.children.Items[j].(type) {
case *node[V]:
path[depth] = byte(childAddr)
if !kid.allRecSorted(path, depth+1, is4, yield) {
return false
}
case *leaf[V]:
if !yield(kid.prefix, kid.value) {
return false
}
default:
panic("logic error, wrong node type")
}
childCursor++
}
// yield the prefix for this idx
cidr := cidrFromPath(path, depth, is4, pfxIdx)
// n.prefixes.Items[i] not possible after sorting allIndices
if !yield(cidr, n.prefixes.MustGet(pfxIdx)) {
return false
}
}
// yield the rest of leaves and nodes (rec-descent)
for j := childCursor; j < len(allChildAddrs); j++ {
addr := allChildAddrs[j]
switch kid := n.children.Items[j].(type) {
case *node[V]:
path[depth] = byte(addr)
if !kid.allRecSorted(path, depth+1, is4, yield) {
return false
}
case *leaf[V]:
if !yield(kid.prefix, kid.value) {
return false
}
default:
panic("logic error, wrong node type")
}
}
return true
}
// unionRec combines two nodes, changing the receiver node.
// If there are duplicate entries, the value is taken from the other node.
// Count duplicate entries to adjust the t.size struct members.
func (n *node[V]) unionRec(o *node[V], depth int) (duplicates int) {
// for all prefixes in other node do ...
allIndices := o.prefixes.AsSlice(make([]uint, 0, maxNodePrefixes))
for i, oIdx := range allIndices {
// insert/overwrite prefix/value from oNode to nNode
exists := n.prefixes.InsertAt(oIdx, o.prefixes.Items[i])
// this prefix is duplicate in n and o
if exists {
duplicates++
}
}
// for all child addrs in other node do ...
allOtherChildAddrs := o.children.AsSlice(make([]uint, 0, maxNodeChildren))
LOOP:
for i, addr := range allOtherChildAddrs {
// 6 possible combinations for this child and other child child
//
// THIS, OTHER:
// ----------
// NULL, node <-- easy, insert at cloned node
// NULL, leaf <-- easy, insert at cloned leaf
// node, node <-- easy, union rec-descent
// node, leaf <-- easy, insert other cloned leaf at depth+1
// leaf, node <-- complex, push this leaf down, union rec-descent
// leaf, leaf <-- complex, push this leaf down, insert other cloned leaf at depth+1
//
// try to get child at same addr from n
thisChild, thisExists := n.children.Get(addr)
if !thisExists {
switch otherChild := o.children.Items[i].(type) {
case *node[V]: // NULL, node
if !thisExists {
n.children.InsertAt(addr, otherChild.cloneRec())
continue LOOP
}
case *leaf[V]: // NULL, leaf
if !thisExists {
n.children.InsertAt(addr, otherChild.cloneLeaf())
continue LOOP
}
default:
panic("logic error, wrong node type")
}
}
switch otherChild := o.children.Items[i].(type) {
case *node[V]:
switch this := thisChild.(type) {
case *node[V]: // node, node
// both childs have node in octet, call union rec-descent on child nodes
duplicates += this.unionRec(otherChild, depth+1)
continue LOOP
case *leaf[V]: // leaf, node
// create new node
nc := new(node[V])
// push this leaf down
nc.insertAtDepth(this.prefix, this.value, depth+1)
// insert new node at current addr
n.children.InsertAt(addr, nc)
// union rec-descent new node with other node
duplicates += nc.unionRec(otherChild, depth+1)
continue LOOP
}
case *leaf[V]:
switch this := thisChild.(type) {
case *node[V]: // node, leaf
clonedLeaf := otherChild.cloneLeaf()
if this.insertAtDepth(clonedLeaf.prefix, clonedLeaf.value, depth+1) {
duplicates++
}
continue LOOP
case *leaf[V]: // leaf, leaf
// create new node
nc := new(node[V])
// push this leaf down
nc.insertAtDepth(this.prefix, this.value, depth+1)
// insert at depth cloned leaf
clonedLeaf := otherChild.cloneLeaf()
if nc.insertAtDepth(clonedLeaf.prefix, clonedLeaf.value, depth+1) {
duplicates++
}
// insert the new node at current addr
n.children.InsertAt(addr, nc)
continue LOOP
}
default:
panic("logic error, wrong node type")
}
}
return duplicates
}
// eachLookupPrefix does an all prefix match in the 8-bit (stride) routing table
// at this depth and calls yield() for any matching CIDR.
func (n *node[V]) eachLookupPrefix(octets []byte, depth int, is4 bool, pfxLen int, yield func(netip.Prefix, V) bool) (ok bool) {
if n.prefixes.Len() == 0 {
return true
}
// octets as array, needed below more than once
var path stridePath
copy(path[:], octets)
// backtracking the CBT
for idx := pfxToIdx(octets[depth], pfxLen); idx > 0; idx >>= 1 {
if n.prefixes.Test(idx) {
val := n.prefixes.MustGet(idx)
cidr := cidrFromPath(path, depth, is4, idx)
if !yield(cidr, val) {
return false
}
}
}
return true
}
// eachSubnet calls yield() for any covered CIDR by parent prefix in natural CIDR sort order.
func (n *node[V]) eachSubnet(octets []byte, depth int, is4 bool, pfxLen int, yield func(netip.Prefix, V) bool) bool {
// octets as array, needed below more than once
var path stridePath
copy(path[:], octets)
pfxFirstAddr := uint(octets[depth])
pfxLastAddr := uint(octets[depth] | ^netMask(pfxLen))
// 1. collect all indices in n covered by prefix
allCoveredIndices := make([]uint, 0, maxNodePrefixes)
for _, idx := range n.prefixes.AsSlice(make([]uint, 0, maxNodePrefixes)) {
thisOctet, thisPfxLen := idxToPfx(idx)
thisFirstAddr := uint(thisOctet)
thisLastAddr := uint(thisOctet | ^netMask(thisPfxLen))
if thisFirstAddr >= pfxFirstAddr && thisLastAddr <= pfxLastAddr {
allCoveredIndices = append(allCoveredIndices, idx)
}
}
// sort indices in CIDR sort order
slices.SortFunc(allCoveredIndices, cmpIndexRank)
// 2. collect all covered child addrs by prefix
allCoveredChildAddrs := make([]uint, 0, maxNodeChildren)
for _, addr := range n.children.AsSlice(make([]uint, 0, maxNodeChildren)) {
if addr >= pfxFirstAddr && addr <= pfxLastAddr {
allCoveredChildAddrs = append(allCoveredChildAddrs, addr)
}
}
// 3. yield covered indices, pathcomp prefixes and childs in CIDR sort order
addrCursor := 0
// yield indices and childs in CIDR sort order
for _, pfxIdx := range allCoveredIndices {
pfxOctet, _ := idxToPfx(pfxIdx)
// yield all childs before idx
for j := addrCursor; j < len(allCoveredChildAddrs); j++ {
addr := allCoveredChildAddrs[j]
if addr >= uint(pfxOctet) {
break
}
// yield the node or leaf?
switch kid := n.children.MustGet(addr).(type) {
case *node[V]:
path[depth] = byte(addr)
if !kid.allRecSorted(path, depth+1, is4, yield) {
return false
}
case *leaf[V]:
if !yield(kid.prefix, kid.value) {
return false
}
default:
panic("logic error, wrong node type")
}
addrCursor++
}
// yield the prefix for this idx
cidr := cidrFromPath(path, depth, is4, pfxIdx)
// n.prefixes.Items[i] not possible after sorting allIndices
if !yield(cidr, n.prefixes.MustGet(pfxIdx)) {
return false
}
}
// yield the rest of leaves and nodes (rec-descent)
for _, addr := range allCoveredChildAddrs[addrCursor:] {
// yield the node or leaf?
switch kid := n.children.MustGet(addr).(type) {
case *node[V]:
path[depth] = byte(addr)
if !kid.allRecSorted(path, depth+1, is4, yield) {
return false
}
case *leaf[V]:
if !yield(kid.prefix, kid.value) {
return false
}
default:
panic("logic error, wrong node type")
}
}
return true
}
-305
View File
@@ -1,305 +0,0 @@
package bart
import (
"net/netip"
"github.com/gaissmai/bart/internal/bitset"
)
// overlaps returns true if any IP in the nodes n or o overlaps.
func (n *node[V]) overlaps(o *node[V], depth int) bool {
nPfxCount := n.prefixes.Len()
oPfxCount := o.prefixes.Len()
nChildCount := n.children.Len()
oChildCount := o.children.Len()
// ##############################
// 1. Test if any routes overlaps
// ##############################
// full cross check
if nPfxCount > 0 && oPfxCount > 0 {
if n.overlapsRoutes(o) {
return true
}
}
// ####################################
// 2. Test if routes overlaps any child
// ####################################
// swap nodes to help chance on its way,
// if the first call to expensive overlapsChildrenIn() is already true,
// if both orders are false it doesn't help either
if nChildCount > oChildCount {
n, o = o, n
nPfxCount = n.prefixes.Len()
oPfxCount = o.prefixes.Len()
nChildCount = n.children.Len()
oChildCount = o.children.Len()
}
if nPfxCount > 0 && oChildCount > 0 {
if n.overlapsChildrenIn(o) {
return true
}
}
// symmetric reverse
if oPfxCount > 0 && nChildCount > 0 {
if o.overlapsChildrenIn(n) {
return true
}
}
// ###########################################
// 3. childs with same octet in nodes n and o
// ###########################################
// stop condition, n or o have no childs
if nChildCount == 0 || oChildCount == 0 {
return false
}
// stop condition, no child with identical octet in n and o
if !n.children.IntersectsAny(o.children.BitSet) {
return false
}
return n.overlapsSameChildren(o, depth)
}
// overlapsRoutes, test if n overlaps o prefixes and vice versa
func (n *node[V]) overlapsRoutes(o *node[V]) bool {
// some prefixes are identical, trivial overlap
if n.prefixes.IntersectsAny(o.prefixes.BitSet) {
return true
}
// get the lowest idx (biggest prefix)
nFirstIdx, _ := n.prefixes.FirstSet()
oFirstIdx, _ := o.prefixes.FirstSet()
// start with other min value, see ART algo
nIdx := oFirstIdx
oIdx := nFirstIdx
// make full cross check
nOK := true
oOK := true
// zip, range over n and o together to help chance on its way
for nOK || oOK {
if nOK {
// does any route in o overlap this prefix from n
if nIdx, nOK = n.prefixes.NextSet(nIdx); nOK {
if o.lpmTest(nIdx) {
return true
}
nIdx++
}
}
if oOK {
// does any route in n overlap this prefix from o
if oIdx, oOK = o.prefixes.NextSet(oIdx); oOK {
if n.lpmTest(oIdx) {
return true
}
oIdx++
}
}
}
return false
}
// overlapsChildrenIn, test if prefixes in n overlaps child octets in o.
func (n *node[V]) overlapsChildrenIn(o *node[V]) bool {
pfxCount := n.prefixes.Len()
childCount := o.children.Len()
// heuristic, compare benchmarks
// when will we range over the children and when will we do bitset calc?
magicNumber := 15
doRange := childCount < magicNumber || pfxCount > magicNumber
// do range over, not so many childs and maybe to many prefixes for other algo below
if doRange {
lowerBound, _ := n.prefixes.FirstSet()
for _, addr := range o.children.AsSlice(make([]uint, 0, maxNodeChildren)) {
idx := hostIndex(addr)
if idx < lowerBound { // lpm match impossible
continue
}
if n.lpmTest(idx) {
return true
}
}
return false
}
// do bitset intersection, alloted route table with child octets
// maybe to many childs for range over or not so many prefixes to
// build the alloted routing table from them
// make allot table with prefixes as bitsets, bitsets are precalculated
// just union the bitsets to one bitset (allot table) for all prefixes
// in this node
prefixRoutes := bitset.BitSet(make([]uint64, 8))
allIndices := n.prefixes.AsSlice(make([]uint, 0, maxNodePrefixes))
for _, idx := range allIndices {
// get pre alloted bitset for idx
prefixRoutes.InPlaceUnion(allotLookupTbl[idx])
}
// clone a bitset without heap allocation
// shift-right children bitset by 256 (firstHostIndex)
c8 := make([]uint64, 8)
copy(c8[4:], o.children.BitSet) // 4*64= 256
hostRoutes := bitset.BitSet(c8)
return prefixRoutes.IntersectsAny(hostRoutes)
}
// overlapsSameChildren, find same octets with bitset intersection.
func (n *node[V]) overlapsSameChildren(o *node[V], depth int) bool {
// clone a bitset without heap allocation
c4 := [4]uint64{}
copy(c4[:], n.children.BitSet)
nChildrenBitsetCloned := bitset.BitSet(c4[:])
// intersect in place the cloned child bitset from n with o
nChildrenBitsetCloned.InPlaceIntersection(o.children.BitSet)
allCommonChildren := nChildrenBitsetCloned.AsSlice(make([]uint, 0, maxNodeChildren))
// range over all child addrs, common in n and o
for _, addr := range allCommonChildren {
nChild := n.children.MustGet(addr)
oChild := o.children.MustGet(addr)
if overlapsTwoChilds[V](nChild, oChild, depth+1) {
return true
}
}
return false
}
// overlapsTwoChilds, childs can be node or leaf.
func overlapsTwoChilds[V any](nChild, oChild any, depth int) bool {
// 4 possible different combinations for n and o
//
// node, node --> overlapsRec
// node, leaf --> overlapsPrefixAtDepth
// leaf, node --> overlapsPrefixAtDepth
// leaf, leaf --> netip.Prefix.Overlaps
//
switch nKind := nChild.(type) {
case *node[V]:
switch oKind := oChild.(type) {
case *node[V]: // node, node
return nKind.overlaps(oKind, depth)
case *leaf[V]: // node, leaf
return nKind.overlapsPrefixAtDepth(oKind.prefix, depth)
}
case *leaf[V]:
switch oKind := oChild.(type) {
case *node[V]: // leaf, node
return oKind.overlapsPrefixAtDepth(nKind.prefix, depth)
case *leaf[V]: // leaf, leaf
return oKind.prefix.Overlaps(nKind.prefix)
}
default:
panic("logic error, wrong node type")
}
return false
}
// overlapsPrefixAtDepth, returns true if node overlaps with prefix
// starting with prefix octet at depth.
//
// Needed for path compressed prefix some level down in the node trie.
func (n *node[V]) overlapsPrefixAtDepth(pfx netip.Prefix, depth int) bool {
ip := pfx.Addr()
bits := pfx.Bits()
lastIdx, lastBits := lastOctetIdxAndBits(bits)
octets := ip.AsSlice()
octets = octets[:lastIdx+1]
for ; depth < len(octets); depth++ {
octet := octets[depth]
addr := uint(octet)
// full octet path in node trie, check overlap with last prefix octet
if depth == lastIdx {
return n.overlapsIdx(pfxToIdx(octet, lastBits))
}
// test if any route overlaps prefix´ so far
// no best match needed, forward tests without backtracking
if n.prefixes.Len() != 0 && n.lpmTest(hostIndex(addr)) {
return true
}
if !n.children.Test(addr) {
return false
}
// next child, node or leaf
switch kid := n.children.MustGet(addr).(type) {
case *node[V]:
n = kid
continue
case *leaf[V]:
return kid.prefix.Overlaps(pfx)
default:
panic("logic error, wrong node type")
}
}
panic("unreachable: " + pfx.String())
}
// overlapsIdx returns true if node overlaps with prefix.
func (n *node[V]) overlapsIdx(idx uint) bool {
// 1. Test if any route in this node overlaps prefix?
if n.lpmTest(idx) {
return true
}
// 2. Test if prefix overlaps any route in this node
// use bitset intersections instead of range loops
// shallow copy pre alloted bitset for idx
allotedPrefixRoutes := allotLookupTbl[idx]
if allotedPrefixRoutes.IntersectsAny(n.prefixes.BitSet) {
return true
}
// 3. Test if prefix overlaps any child in this node
// shift-right children bitset by 256 (firstHostIndex)
c8 := [8]uint64{}
copy(c8[4:], n.children.BitSet) // 4*64= 256
hostRoutes := bitset.BitSet(c8[:])
// use bitsets intersection instead of range loops
return allotedPrefixRoutes.IntersectsAny(hostRoutes)
}
-263
View File
@@ -1,263 +0,0 @@
// Copyright (c) 2024 Karl Gaissmaier
// SPDX-License-Identifier: MIT
package bart
import (
"bytes"
"cmp"
"fmt"
"io"
"net/netip"
"slices"
"strings"
)
// kid, a node has no path information about its predecessors,
// we collect this during the recursive descent.
// The path/depth/idx is needed to get the CIDR back.
type kid[V any] struct {
// for traversing
n *node[V]
is4 bool
path stridePath
depth int
idx uint
// for printing
cidr netip.Prefix
val V
}
// MarshalText implements the [encoding.TextMarshaler] interface,
// just a wrapper for [Table.Fprint].
func (t *Table[V]) MarshalText() ([]byte, error) {
w := new(bytes.Buffer)
if err := t.Fprint(w); err != nil {
return nil, err
}
return w.Bytes(), nil
}
// String returns a hierarchical tree diagram of the ordered CIDRs
// as string, just a wrapper for [Table.Fprint].
// If Fprint returns an error, String panics.
func (t *Table[V]) String() string {
w := new(strings.Builder)
if err := t.Fprint(w); err != nil {
panic(err)
}
return w.String()
}
// Fprint writes a hierarchical tree diagram of the ordered CIDRs
// with default formatted payload V to w. If w is nil, Fprint panics.
//
// The order from top to bottom is in ascending order of the prefix address
// and the subtree structure is determined by the CIDRs coverage.
//
// ▼
// ├─ 10.0.0.0/8 (V)
// │ ├─ 10.0.0.0/24 (V)
// │ └─ 10.0.1.0/24 (V)
// ├─ 127.0.0.0/8 (V)
// │ └─ 127.0.0.1/32 (V)
// ├─ 169.254.0.0/16 (V)
// ├─ 172.16.0.0/12 (V)
// └─ 192.168.0.0/16 (V)
// └─ 192.168.1.0/24 (V)
// ▼
// └─ ::/0 (V)
// ├─ ::1/128 (V)
// ├─ 2000::/3 (V)
// │ └─ 2001:db8::/32 (V)
// └─ fe80::/10 (V)
func (t *Table[V]) Fprint(w io.Writer) error {
// v4
if err := t.fprint(w, true); err != nil {
return err
}
// v6
if err := t.fprint(w, false); err != nil {
return err
}
return nil
}
// fprint is the version dependent adapter to fprintRec.
func (t *Table[V]) fprint(w io.Writer, is4 bool) error {
n := t.rootNodeByVersion(is4)
if n.isEmpty() {
return nil
}
if _, err := fmt.Fprint(w, "▼\n"); err != nil {
return err
}
startKid := kid[V]{
n: nil,
idx: 0,
path: stridePath{},
is4: is4,
}
if err := n.fprintRec(w, startKid, ""); err != nil {
return err
}
return nil
}
// fprintRec, the output is a hierarchical CIDR tree starting with this kid.
func (n *node[V]) fprintRec(w io.Writer, parent kid[V], pad string) error {
// recursion stop condition
if n == nil {
return nil
}
// get direct childs for this kid ...
directKids := n.getKidsRec(parent.idx, parent.path, parent.depth, parent.is4)
// sort them by netip.Prefix, not by baseIndex
slices.SortFunc(directKids, cmpKidByPrefix[V])
// symbols used in tree
glyphe := "├─ "
spacer := "│ "
// for all direct kids under this node ...
for i, kid := range directKids {
// ... treat last kid special
if i == len(directKids)-1 {
glyphe = "└─ "
spacer = " "
}
// print prefix and val, padded with glyphe
if _, err := fmt.Fprintf(w, "%s%s (%v)\n", pad+glyphe, kid.cidr, kid.val); err != nil {
return err
}
// rec-descent with this prefix as parentIdx.
// hierarchical nested tree view, two rec-descent functions
// work together to spoil the reader.
if err := kid.n.fprintRec(w, kid, pad+spacer); err != nil {
return err
}
}
return nil
}
// getKidsRec, returns the direct kids below path and parentIdx.
// It's a recursive monster together with printRec,
// you have to know the data structure by heart to understand this function!
//
// See the artlookup.pdf paper in the doc folder,
// the baseIndex function is the key.
func (n *node[V]) getKidsRec(parentIdx uint, path stridePath, depth int, is4 bool) []kid[V] {
// recursion stop condition
if n == nil {
return nil
}
var directKids []kid[V]
for _, idx := range n.prefixes.All() {
// parent or self, handled alreday in an upper stack frame.
if idx <= parentIdx {
continue
}
// check if lpmIdx for this idx' parent is equal to parentIdx
lpmIdx, _, _ := n.lpmGet(idx >> 1)
// if idx is directKid?
if lpmIdx == parentIdx {
cidr := cidrFromPath(path, depth, is4, idx)
kid := kid[V]{
n: n,
is4: is4,
path: path,
depth: depth,
idx: idx,
cidr: cidr,
val: n.prefixes.MustGet(idx),
}
directKids = append(directKids, kid)
}
}
// the node may have childs and leaves, the rec-descent monster starts
for i, addr := range n.children.All() {
// do a longest-prefix-match
lpmIdx, _, _ := n.lpmGet(hostIndex(addr))
if lpmIdx == parentIdx {
switch k := n.children.Items[i].(type) {
case *node[V]:
path[depth] = byte(addr)
// traverse, rec-descent call with next child node
directKids = append(directKids, k.getKidsRec(0, path, depth+1, is4)...)
case *leaf[V]:
kid := kid[V]{
n: nil, // path compressed item, stop recursion
is4: is4,
cidr: k.prefix,
val: k.value,
}
directKids = append(directKids, kid)
}
}
}
return directKids
}
// cmpKidByPrefix, all prefixes are already normalized (Masked).
func cmpKidByPrefix[V any](a, b kid[V]) int {
return cmpPrefix(a.cidr, b.cidr)
}
// cmpPrefix, compare func for prefix sort,
// all cidrs are already normalized
func cmpPrefix(a, b netip.Prefix) int {
if cmp := a.Addr().Compare(b.Addr()); cmp != 0 {
return cmp
}
return cmp.Compare(a.Bits(), b.Bits())
}
// cidrFromPath, get prefix back from byte path, depth, octet and pfxLen.
func cidrFromPath(path stridePath, depth int, is4 bool, idx uint) netip.Prefix {
octet, pfxLen := idxToPfx(idx)
// set masked byte in path at depth
path[depth] = octet
// zero/mask the bytes after prefix bits
clear(path[depth+1:])
// make ip addr from octets
var ip netip.Addr
if is4 {
ip = netip.AddrFrom4([4]byte(path[:4]))
} else {
ip = netip.AddrFrom16(path)
}
// calc bits with pathLen and pfxLen
bits := depth<<3 + pfxLen
// return a normalized prefix from ip/bits
return netip.PrefixFrom(ip, bits)
}
-1131
View File
File diff suppressed because it is too large. Load diff