Files
2025-04-09 01:00:12 +01:00

264 lines
6.0 KiB
Go

// 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)
}