Update
This commit is contained in:
1 parent
3530afb854
commit
6903fcff27
2146 files changed
+112256
-190703
No files matched your search
+731
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user