diff --git a/bench_test.go b/bench_test.go index e80c92d..27a8e79 100644 --- a/bench_test.go +++ b/bench_test.go @@ -25,7 +25,7 @@ var intMap = map[int]string{ func BenchmarkLookup(b *testing.B) { for k := 1; k <= 100_000; k *= 10 { - rt := new(cidrtree.Table) + rt := new(cidrtree.Table[any]) cidrs := shuffleFullTable(k) for _, cidr := range cidrs { rt.Insert(cidr, nil) @@ -45,7 +45,7 @@ func BenchmarkLookup(b *testing.B) { func BenchmarkLookupPrefix(b *testing.B) { for k := 1; k <= 100_000; k *= 10 { - rt := new(cidrtree.Table) + rt := new(cidrtree.Table[any]) cidrs := shuffleFullTable(k) for _, cidr := range cidrs { rt.Insert(cidr, nil) @@ -64,7 +64,7 @@ func BenchmarkLookupPrefix(b *testing.B) { func BenchmarkClone(b *testing.B) { for k := 1; k <= 100_000; k *= 10 { - rt := new(cidrtree.Table) + rt := new(cidrtree.Table[any]) for _, cidr := range shuffleFullTable(k) { rt.Insert(cidr, nil) } @@ -80,7 +80,7 @@ func BenchmarkClone(b *testing.B) { func BenchmarkInsert(b *testing.B) { for k := 1; k <= 100_000; k *= 10 { - rt := new(cidrtree.Table) + rt := new(cidrtree.Table[any]) cidrs := shuffleFullTable(k) for _, cidr := range cidrs { rt.Insert(cidr, nil) @@ -99,7 +99,7 @@ func BenchmarkInsert(b *testing.B) { func BenchmarkDelete(b *testing.B) { for k := 1; k <= 100_000; k *= 10 { - rt := new(cidrtree.Table) + rt := new(cidrtree.Table[any]) cidrs := shuffleFullTable(k) for _, cidr := range cidrs { rt.Insert(cidr, nil) diff --git a/debug.go b/debug.go index 2a397fa..252f37d 100644 --- a/debug.go +++ b/debug.go @@ -10,7 +10,7 @@ import ( // fprintBST writes a horizontal tree diagram of the binary search tree (BST) to w. // // Note: This is for debugging purposes only. -func (t Table) fprintBST(w io.Writer) error { +func (t Table[T]) fprintBST(w io.Writer) error { if t.root4 != nil { if _, err := fmt.Fprint(w, "R "); err != nil { return err @@ -33,7 +33,7 @@ func (t Table) fprintBST(w io.Writer) error { } // fprintBST recursive helper. -func (n *node) fprintBST(w io.Writer, pad string) error { +func (n *node[T]) fprintBST(w io.Writer, pad string) error { // stringify this node _, err := fmt.Fprintf(w, "%v [prio:%.4g] [subtree maxUpper: %v]\n", n.cidr, float64(n.prio)/math.MaxUint64, n.maxUpper.cidr) if err != nil { @@ -80,7 +80,7 @@ func (n *node) fprintBST(w io.Writer, pad string) error { // If the skip function is not nil, a true return value defines which nodes must be skipped in the statistics. // // Note: This is for debugging and testing purposes only during development. -func (t Table) statistics(skip func(netip.Prefix, any, int) bool) (size int, maxDepth int, average, deviation float64) { +func (t Table[T]) statistics(skip func(netip.Prefix, any, int) bool) (size int, maxDepth int, average, deviation float64) { // key is depth, value is the sum of nodes with this depth depths := make(map[int]int) @@ -120,7 +120,7 @@ func (t Table) statistics(skip func(netip.Prefix, any, int) bool) (size int, max } // walkWithDepth in ascending prefix order. -func (n *node) walkWithDepth(cb func(netip.Prefix, any, int) bool, depth int) bool { +func (n *node[T]) walkWithDepth(cb func(netip.Prefix, any, int) bool, depth int) bool { if n == nil { return true } diff --git a/example_test.go b/example_test.go index 10d02ca..870a79f 100644 --- a/example_test.go +++ b/example_test.go @@ -34,7 +34,7 @@ var input = []netip.Prefix{ } func ExampleTable_Lookup() { - rtbl := new(cidrtree.Table) + rtbl := new(cidrtree.Table[any]) for _, cidr := range input { rtbl.Insert(cidr, nil) } @@ -83,7 +83,7 @@ func ExampleTable_Walk() { return true } - rtbl := new(cidrtree.Table) + rtbl := new(cidrtree.Table[any]) for _, cidr := range input { rtbl.Insert(cidr, nil) } diff --git a/stringify.go b/stringify.go index 5361935..9c52576 100644 --- a/stringify.go +++ b/stringify.go @@ -7,7 +7,7 @@ import ( ) // String returns a hierarchical tree diagram of the ordered CIDRs as string, just a wrapper for [Tree.Fprint]. -func (t Table) String() string { +func (t Table[T]) String() string { w := new(strings.Builder) _ = t.Fprint(w) return w.String() @@ -17,7 +17,7 @@ func (t Table) String() string { // // The order from top to bottom is in ascending order of the start address // and the subtree structure is determined by the CIDRs coverage. -func (t Table) Fprint(w io.Writer) error { +func (t Table[T]) Fprint(w io.Writer) error { if err := t.root4.fprint(w); err != nil { return err } @@ -27,16 +27,16 @@ func (t Table) Fprint(w io.Writer) error { return nil } -func (n *node) fprint(w io.Writer) error { +func (n *node[T]) fprint(w io.Writer) error { if n == nil { return nil } // pcm = parent-child-mapping - var pcm parentChildsMap + var pcm parentChildsMap[T] // init map - pcm.pcMap = make(map[*node][]*node) + pcm.pcMap = make(map[*node[T]][]*node[T]) pcm = n.buildParentChildsMap(pcm) @@ -50,11 +50,11 @@ func (n *node) fprint(w io.Writer) error { } // start recursion with root and empty padding - var root *node + var root *node[T] return root.walkAndStringify(w, pcm, "") } -func (n *node) walkAndStringify(w io.Writer, pcm parentChildsMap, pad string) error { +func (n *node[T]) walkAndStringify(w io.Writer, pcm parentChildsMap[T], pad string) error { // the prefix (pad + glyphe) is already printed on the line on upper level if n != nil { if _, err := fmt.Fprintf(w, "%v (%v)\n", n.cidr, n.value); err != nil { @@ -92,13 +92,13 @@ func (n *node) walkAndStringify(w io.Writer, pcm parentChildsMap, pad string) er // parentChildsMap, needed for hierarchical tree printing, this is not BST printing! // // CIDR tree, parent->childs relation printed. A parent CIDR covers a child CIDR. -type parentChildsMap struct { - pcMap map[*node][]*node // parent -> []child map - stack []*node // just needed for the algo +type parentChildsMap[T any] struct { + pcMap map[*node[T]][]*node[T] // parent -> []child map + stack []*node[T] // just needed for the algo } // buildParentChildsMap, in-order traversal -func (n *node) buildParentChildsMap(pcm parentChildsMap) parentChildsMap { +func (n *node[T]) buildParentChildsMap(pcm parentChildsMap[T]) parentChildsMap[T] { if n == nil { return pcm } @@ -114,7 +114,7 @@ func (n *node) buildParentChildsMap(pcm parentChildsMap) parentChildsMap { } // pcmForNode, find parent in stack, remove cidrs from stack, put this cidr on stack. -func (n *node) pcmForNode(pcm parentChildsMap) parentChildsMap { +func (n *node[T]) pcmForNode(pcm parentChildsMap[T]) parentChildsMap[T] { // if this cidr is covered by a prev cidr on stack for j := len(pcm.stack) - 1; j >= 0; j-- { that := pcm.stack[j] diff --git a/treap.go b/treap.go index cc9bab2..4c96ac4 100644 --- a/treap.go +++ b/treap.go @@ -16,25 +16,25 @@ import ( ) // Table is an IPv4 and IPv6 routing table. The zero value is ready to use. -type Table struct { +type Table[T any] struct { // make a treap for every IP version, the bits of the prefix are part of the weighted priority - root4 *node - root6 *node + root4 *node[T] + root6 *node[T] } // node is the recursive data structure of the treap. -type node struct { - maxUpper *node // augment the treap, see also recalc() - left *node - right *node - value any +type node[T any] struct { + maxUpper *node[T] // augment the treap, see also recalc() + left *node[T] + right *node[T] + value T cidr netip.Prefix prio uint64 } // Insert adds pfx to the table with value val, changing the original table. // If pfx is already present in the table, its value is set to val. -func (t *Table) Insert(pfx netip.Prefix, val any) { +func (t *Table[T]) Insert(pfx netip.Prefix, val T) { pfx = pfx.Masked() // always canonicalize! if pfx.Addr().Is4() { @@ -46,7 +46,7 @@ func (t *Table) Insert(pfx netip.Prefix, val any) { // InsertImmutable adds pfx to the table with value val, returning a new table. // If pfx is already present in the table, its value is set to val. -func (t Table) InsertImmutable(pfx netip.Prefix, val any) *Table { +func (t Table[T]) InsertImmutable(pfx netip.Prefix, val T) *Table[T] { pfx = pfx.Masked() // always canonicalize! if pfx.Addr().Is4() { @@ -60,7 +60,7 @@ func (t Table) InsertImmutable(pfx netip.Prefix, val any) *Table { // insert into treap, changing nodes are copied, new treap is returned, // old treap is modified if immutable is false. // If node is already present in the table, its value is set to val. -func (n *node) insert(m *node, immutable bool) *node { +func (n *node[T]) insert(m *node[T], immutable bool) *node[T] { if n == nil { // recursion stop condition return m @@ -130,7 +130,7 @@ func (n *node) insert(m *node, immutable bool) *node { } // DeleteImmutable removes the prefix if it exists, returns the new table and true, false if not found. -func (t Table) DeleteImmutable(pfx netip.Prefix) (*Table, bool) { +func (t Table[T]) DeleteImmutable(pfx netip.Prefix) (*Table[T], bool) { pfx = pfx.Masked() // always canonicalize! is4 := pfx.Addr().Is4() @@ -155,7 +155,7 @@ func (t Table) DeleteImmutable(pfx netip.Prefix) (*Table, bool) { } // Delete removes the prefix from table, returns true if it exists, false otherwise. -func (t *Table) Delete(pfx netip.Prefix) bool { +func (t *Table[T]) Delete(pfx netip.Prefix) bool { pfx = pfx.Masked() // always canonicalize! is4 := pfx.Addr().Is4() @@ -180,7 +180,7 @@ func (t *Table) Delete(pfx netip.Prefix) bool { // UnionImmutable combines any two tables immutable and returns the combined table. // If there are duplicate entries, the value is taken from the other table. -func (t Table) UnionImmutable(other Table) *Table { +func (t Table[T]) UnionImmutable(other Table[T]) *Table[T] { t.root4 = t.root4.union(other.root4, true, true) t.root6 = t.root6.union(other.root6, true, true) return &t @@ -188,14 +188,14 @@ func (t Table) UnionImmutable(other Table) *Table { // Union combines two tables, changing the receiver table. // If there are duplicate entries, the value is taken from the other table. -func (t *Table) Union(other Table) { +func (t *Table[T]) Union(other Table[T]) { t.root4 = t.root4.union(other.root4, true, false) t.root6 = t.root6.union(other.root6, true, false) } // union two treaps. // flag overwrite isn't public but needed as input for rec-descent calls, see below when trepa are swapped. -func (n *node) union(b *node, overwrite bool, immutable bool) *node { +func (n *node[T]) union(b *node[T], overwrite bool, immutable bool) *node[T] { // recursion stop condition if n == nil { return b @@ -237,7 +237,7 @@ func (n *node) union(b *node, overwrite bool, immutable bool) *node { // Walk iterates the cidrtree in ascending order. // The callback function is called with the prefix and value of the respective node and the depth in the tree. // If callback returns `false`, the iteration is aborted. -func (t Table) Walk(cb func(pfx netip.Prefix, val any) bool) { +func (t Table[T]) Walk(cb func(pfx netip.Prefix, val T) bool) { if !t.root4.walk(cb) { return } @@ -246,7 +246,7 @@ func (t Table) Walk(cb func(pfx netip.Prefix, val any) bool) { } // walk tree in ascending prefix order. -func (n *node) walk(cb func(netip.Prefix, any) bool) bool { +func (n *node[T]) walk(cb func(netip.Prefix, T) bool) bool { if n == nil { return true } @@ -273,7 +273,7 @@ func (n *node) walk(cb func(netip.Prefix, any) bool) bool { // If the ip isn't covered by any CIDR, the zero value and false is returned. // // Lookup does not allocate memory. -func (t Table) Lookup(ip netip.Addr) (lpm netip.Prefix, value any, ok bool) { +func (t Table[T]) Lookup(ip netip.Addr) (lpm netip.Prefix, value T, ok bool) { if ip.Is4() { // don't return the depth lpm, value, ok, _ = t.root4.lpmIP(ip, 0) @@ -285,7 +285,7 @@ func (t Table) Lookup(ip netip.Addr) (lpm netip.Prefix, value any, ok bool) { } // lpmIP rec-descent -func (n *node) lpmIP(ip netip.Addr, depth int) (lpm netip.Prefix, value any, ok bool, atDepth int) { +func (n *node[T]) lpmIP(ip netip.Addr, depth int) (lpm netip.Prefix, value T, ok bool, atDepth int) { for { // recursion stop condition if n == nil { @@ -326,7 +326,7 @@ func (n *node) lpmIP(ip netip.Addr, depth int) (lpm netip.Prefix, value any, ok // If the prefix isn't equal or covered by any CIDR in the table, the zero value and false is returned. // // LookupPrefix does not allocate memory. -func (t Table) LookupPrefix(pfx netip.Prefix) (lpm netip.Prefix, value any, ok bool) { +func (t Table[T]) LookupPrefix(pfx netip.Prefix) (lpm netip.Prefix, value T, ok bool) { pfx = pfx.Masked() // always canonicalize! if pfx.Addr().Is4() { @@ -340,7 +340,7 @@ func (t Table) LookupPrefix(pfx netip.Prefix) (lpm netip.Prefix, value any, ok b } // lpmCIDR rec-descent -func (n *node) lpmCIDR(pfx netip.Prefix, depth int) (lpm netip.Prefix, value any, ok bool, atDepth int) { +func (n *node[T]) lpmCIDR(pfx netip.Prefix, depth int) (lpm netip.Prefix, value T, ok bool, atDepth int) { for { // recursion stop condition if n == nil { @@ -393,13 +393,13 @@ func (n *node) lpmCIDR(pfx netip.Prefix, depth int) (lpm netip.Prefix, value any } // Clone, deep cloning of the routing table. -func (t Table) Clone() *Table { +func (t Table[T]) Clone() *Table[T] { t.root4 = t.root4.clone() t.root6 = t.root6.clone() return &t } -func (n *node) clone() *node { +func (n *node[T]) clone() *node[T] { if n == nil { return n } @@ -421,7 +421,7 @@ func (n *node) clone() *node { // and greater-than the provided cidr (BST key). The resulting nodes are // properly formed treaps or nil. // If the split must be immutable, first copy concerned nodes. -func (n *node) split(cidr netip.Prefix, immutable bool) (left, mid, right *node) { +func (n *node[T]) split(cidr netip.Prefix, immutable bool) (left, mid, right *node[T]) { // recursion stop condition if n == nil { return nil, nil, nil @@ -472,7 +472,7 @@ func (n *node) split(cidr netip.Prefix, immutable bool) (left, mid, right *node) // join combines two disjunct treaps. All nodes in treap n have keys <= that of treap m // for this algorithm to work correctly. If the join must be immutable, first copy concerned nodes. -func (n *node) join(m *node, immutable bool) *node { +func (n *node[T]) join(m *node[T], immutable bool) *node[T] { // recursion stop condition if n == nil { return m @@ -511,8 +511,8 @@ func (n *node) join(m *node, immutable bool) *node { // ########################################################### // makeNode, create new node with cidr. -func makeNode(pfx netip.Prefix, val any) *node { - n := new(node) +func makeNode[T any](pfx netip.Prefix, val T) *node[T] { + n := new(node[T]) n.cidr = pfx.Masked() // always store the prefix in normalized form n.value = val n.prio = mrand.Uint64() @@ -521,7 +521,7 @@ func makeNode(pfx netip.Prefix, val any) *node { } // copyNode, make a shallow copy of the pointers and the cidr. -func (n *node) copyNode() *node { +func (n *node[T]) copyNode() *node[T] { c := *n return &c } @@ -529,7 +529,7 @@ func (n *node) copyNode() *node { // recalc the augmented fields in treap node after each creation/modification // with values in descendants. // Only one level deeper must be considered. The treap datastructure is very easy to augment. -func (n *node) recalc() { +func (n *node[T]) recalc() { if n == nil { return } diff --git a/treap_test.go b/treap_test.go index 4fca344..4f91246 100644 --- a/treap_test.go +++ b/treap_test.go @@ -92,7 +92,7 @@ func TestZeroValue(t *testing.T) { var zeroIP netip.Addr var zeroCIDR netip.Prefix - var zeroTable cidrtree.Table + var zeroTable cidrtree.Table[any] if zeroTable.String() != "" { t.Errorf("String() = %v, want \"\"", "") @@ -129,7 +129,7 @@ func TestZeroValue(t *testing.T) { func TestInsertImmutable(t *testing.T) { t.Parallel() - rtbl := new(cidrtree.Table) + rtbl := new(cidrtree.Table[any]) for _, route := range routes { rtbl = rtbl.InsertImmutable(route.cidr, route.nextHop) @@ -142,7 +142,7 @@ func TestInsertImmutable(t *testing.T) { func TestDupInsert(t *testing.T) { t.Parallel() - rtbl := new(cidrtree.Table) + rtbl := new(cidrtree.Table[any]) for _, route := range routes { rtbl.Insert(route.cidr, route.nextHop) @@ -183,7 +183,7 @@ func TestDupInsert(t *testing.T) { func TestInsert(t *testing.T) { t.Parallel() - rtbl := new(cidrtree.Table) + rtbl := new(cidrtree.Table[any]) for _, route := range routes { rtbl.Insert(route.cidr, route.nextHop) @@ -197,7 +197,7 @@ func TestInsert(t *testing.T) { func TestImmutable(t *testing.T) { t.Parallel() - rtbl1 := new(cidrtree.Table) + rtbl1 := new(cidrtree.Table[any]) for _, route := range routes { rtbl1.Insert(route.cidr, route.nextHop) } @@ -235,7 +235,7 @@ func TestImmutable(t *testing.T) { } func TestMutable(t *testing.T) { - rtbl1 := new(cidrtree.Table) + rtbl1 := new(cidrtree.Table[any]) for _, route := range routes { rtbl1.Insert(route.cidr, route.nextHop) } @@ -253,7 +253,7 @@ func TestMutable(t *testing.T) { } // reset table1, table2 - rtbl1 = new(cidrtree.Table) + rtbl1 = new(cidrtree.Table[any]) for _, route := range routes { rtbl1.Insert(route.cidr, route.nextHop) } @@ -274,7 +274,7 @@ func TestMutable(t *testing.T) { func TestDeleteImmutable(t *testing.T) { t.Parallel() - rtbl := new(cidrtree.Table) + rtbl := new(cidrtree.Table[any]) for _, route := range routes { rtbl.Insert(route.cidr, route.nextHop) } @@ -299,7 +299,7 @@ func TestDeleteImmutable(t *testing.T) { func TestDelete(t *testing.T) { t.Parallel() - rtbl := new(cidrtree.Table) + rtbl := new(cidrtree.Table[any]) for _, route := range routes { rtbl.Insert(route.cidr, route.nextHop) } @@ -322,7 +322,7 @@ func TestDelete(t *testing.T) { func TestLookupIP(t *testing.T) { t.Parallel() - rtbl := new(cidrtree.Table) + rtbl := new(cidrtree.Table[any]) for _, route := range routes { rtbl.Insert(route.cidr, route.nextHop) } @@ -406,7 +406,7 @@ func TestLookupIP(t *testing.T) { // ########################################## tc := shuffleFullTable(100_000) - rtbl2 := new(cidrtree.Table) + rtbl2 := new(cidrtree.Table[any]) for _, cidr := range tc { rtbl2.Insert(cidr, nil) } @@ -428,7 +428,7 @@ func TestLookupIP(t *testing.T) { func TestLookupCIDR(t *testing.T) { t.Parallel() - rtbl := new(cidrtree.Table) + rtbl := new(cidrtree.Table[any]) for _, route := range routes { rtbl.Insert(route.cidr, route.nextHop) } @@ -513,7 +513,7 @@ func TestLookupCIDR(t *testing.T) { tc := shuffleFullTable(100_000) - rtbl2 := new(cidrtree.Table) + rtbl2 := new(cidrtree.Table[any]) for _, cidr := range tc { rtbl2.Insert(cidr, nil) } @@ -526,8 +526,8 @@ func TestLookupCIDR(t *testing.T) { func TestUnion(t *testing.T) { t.Parallel() - rtbl := new(cidrtree.Table) - rtbl2 := new(cidrtree.Table) + rtbl := new(cidrtree.Table[any]) + rtbl2 := new(cidrtree.Table[any]) for _, route := range routes { rtbl.Insert(route.cidr, route.nextHop) rtbl2.Insert(route.cidr, route.nextHop) @@ -539,7 +539,7 @@ func TestUnion(t *testing.T) { } clone := rtbl.Clone() - rtbl.Union(cidrtree.Table{}) + rtbl.Union(cidrtree.Table[any]{}) if !reflect.DeepEqual(rtbl, clone) { t.Fatal("UnionMutable with zero value changed original") } @@ -552,8 +552,8 @@ func TestUnion(t *testing.T) { func TestUnionDupe(t *testing.T) { t.Parallel() - rtbl1 := new(cidrtree.Table) - rtbl2 := new(cidrtree.Table) + rtbl1 := new(cidrtree.Table[any]) + rtbl2 := new(cidrtree.Table[any]) for _, cidr := range shuffleFullTable(100_000) { rtbl1.Insert(cidr, 1) // dupe cidr with different value @@ -582,7 +582,7 @@ func TestUnionDupe(t *testing.T) { func TestFprint(t *testing.T) { t.Parallel() - rtbl := new(cidrtree.Table) + rtbl := new(cidrtree.Table[any]) for _, route := range routes { rtbl.Insert(route.cidr, route.nextHop) } @@ -599,7 +599,7 @@ func TestFprint(t *testing.T) { func TestWalk(t *testing.T) { t.Parallel() - rtbl := new(cidrtree.Table) + rtbl := new(cidrtree.Table[any]) for _, route := range routes { rtbl.Insert(route.cidr, route.nextHop) } @@ -618,7 +618,7 @@ func TestWalk(t *testing.T) { func TestWalkStartStop(t *testing.T) { t.Parallel() - rtbl := new(cidrtree.Table) + rtbl := new(cidrtree.Table[any]) for _, route := range routes { rtbl.Insert(route.cidr, route.nextHop) } diff --git a/whitebox_test.go b/whitebox_test.go index c2f0644..9ea4c06 100644 --- a/whitebox_test.go +++ b/whitebox_test.go @@ -13,7 +13,7 @@ import ( ) func TestFprintBST(t *testing.T) { - rtbl := new(Table) + rtbl := new(Table[any]) for i := 1; i <= 48; i++ { rtbl.Insert(randPfx4(), nil) rtbl.Insert(randPfx6(), nil) @@ -39,7 +39,7 @@ func TestFprintBST(t *testing.T) { func TestStatisticsRandom(t *testing.T) { for i := 10; i <= 100_000; i *= 10 { - rtbl := new(Table) + rtbl := new(Table[any]) for c := 0; c <= i; c++ { rtbl.Insert(randPfx(), nil) } @@ -57,7 +57,7 @@ func TestStatisticsRandom(t *testing.T) { } func TestStatisticsFullTable(t *testing.T) { - rtbl := new(Table) + rtbl := new(Table[any]) for _, cidr := range fullTable { rtbl.Insert(cidr, nil) } @@ -80,7 +80,7 @@ func TestLPMRandom(t *testing.T) { var lpm netip.Prefix for i := 10; i <= 100_000; i *= 10 { - rtbl := new(Table) + rtbl := new(Table[any]) for c := 0; c <= i; c++ { rtbl.Insert(randPfx(), nil) } @@ -106,7 +106,7 @@ func TestLPMFullTableWithDefaultRoutes(t *testing.T) { var addr netip.Addr var lpm netip.Prefix - rtbl := new(Table) + rtbl := new(Table[any]) for _, cidr := range fullTable { rtbl.Insert(cidr, nil) }