208 lines
4.1 KiB
Go
208 lines
4.1 KiB
Go
// 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())
|
|
}
|