2020-01-29 03:05:17 +00:00
|
|
|
#lang racket
|
|
|
|
|
2020-02-01 02:19:55 +00:00
|
|
|
(require "iputil.rkt")
|
|
|
|
|
2020-02-14 21:22:06 +00:00
|
|
|
(provide make-rt rt-add! rt-del! rt-lookup rt-dump rt-flatten)
|
2020-01-29 03:05:17 +00:00
|
|
|
|
2020-02-02 04:55:14 +00:00
|
|
|
;; Radix tree implementation for efficient routing lookups
|
|
|
|
;; This module provides a radix tree type indexed by bit-lists (lists of booleans representing IP
|
2020-02-14 21:22:06 +00:00
|
|
|
;; address bits), which supports arbitary node data at any tree depth. Lookup finds all the matches.
|
2020-02-02 04:55:14 +00:00
|
|
|
|
2020-02-14 21:22:06 +00:00
|
|
|
;; An RT is a (rt-node RTedge RTedge [Listof Any])
|
2020-02-02 04:55:14 +00:00
|
|
|
;; An RTkey is a [Listof Bool]
|
|
|
|
;; An RTedge is a (rt-edge RTkey RT)
|
2020-01-29 03:05:17 +00:00
|
|
|
(struct rt-node [edge0 edge1 data] #:transparent #:mutable)
|
2020-02-02 04:55:14 +00:00
|
|
|
;; Edge labels always contain all of their bits, even though the first bit is redundant
|
|
|
|
;; (represented by whether this was an edge0 or edge1 in the parent node)
|
2020-01-29 03:05:17 +00:00
|
|
|
(struct rt-edge [label target] #:transparent)
|
|
|
|
|
2020-02-02 04:55:14 +00:00
|
|
|
;; -> RT
|
|
|
|
;; Creates a new empty RT
|
2020-01-29 03:05:17 +00:00
|
|
|
(define (make-rt)
|
2020-02-14 21:22:06 +00:00
|
|
|
(rt-node #f #f '()))
|
2020-01-29 03:05:17 +00:00
|
|
|
|
|
|
|
;; Helper functions for rt-node struct access based on whether the edge is 1 or 0
|
|
|
|
(define (rt-getter bit) (if bit rt-node-edge1 rt-node-edge0))
|
|
|
|
(define (rt-setter bit) (if bit set-rt-node-edge1! set-rt-node-edge0!))
|
|
|
|
|
2020-02-02 04:55:14 +00:00
|
|
|
;; RTkey RTkey -> Nat
|
|
|
|
;; Computes the length of the longest common prefix in bl1 and bl2
|
2020-02-01 02:19:55 +00:00
|
|
|
(define (bl-common-len bl1 bl2)
|
|
|
|
(for/sum ([b1 (in-list bl1)]
|
|
|
|
[b2 (in-list bl2)])
|
|
|
|
#:break (not (equal? b1 b2))
|
|
|
|
1))
|
2020-01-29 03:05:17 +00:00
|
|
|
|
2020-02-02 04:55:14 +00:00
|
|
|
;; An RTresult is one of
|
|
|
|
;; (list 'exact RT) - An exact match for the given key that landed on the returned RT
|
|
|
|
;; (list 'partial RT RTedge Nat RTkey [Listof RT]) - A partial match stopping at the given
|
|
|
|
;; RT, next partially matched edge, length of partial match, tail of the key not exactly
|
|
|
|
;; matched, and a list of visited nodes ordered by most recent first
|
|
|
|
;; (list 'no-match RT RTkey [Listof RT]) - No match was found, but we stopped at the given
|
|
|
|
;; RT, with tail of the key that did not match, and list of visited nodes ordered by most
|
|
|
|
;; recent first
|
|
|
|
|
|
|
|
;; RT RTkey [[Listof RT]] -> RTresult
|
|
|
|
;; Helper function that performs tree iteration
|
2020-02-01 03:11:42 +00:00
|
|
|
(define (rt-partial-iterate node key [visited '()])
|
2020-01-29 03:05:17 +00:00
|
|
|
(cond
|
2020-02-15 00:52:37 +00:00
|
|
|
[(empty? key) (list 'exact node (cons node visited))]
|
2020-01-29 03:05:17 +00:00
|
|
|
[else
|
2020-02-01 02:19:55 +00:00
|
|
|
(let* ([bit (first key)]
|
2020-01-29 03:05:17 +00:00
|
|
|
[getter (rt-getter bit)]
|
|
|
|
[next-edge (getter node)]
|
|
|
|
[next-label (and next-edge (rt-edge-label next-edge))]
|
|
|
|
[next-target (and next-edge (rt-edge-target next-edge))]
|
2020-02-01 02:19:55 +00:00
|
|
|
[next-common-len (and next-label (bl-common-len next-label key))])
|
2020-01-29 03:05:17 +00:00
|
|
|
(cond
|
2020-02-01 02:19:55 +00:00
|
|
|
[(and next-edge (= next-common-len (length next-label)))
|
2020-02-01 03:11:42 +00:00
|
|
|
(rt-partial-iterate next-target (drop key next-common-len) (cons node visited))]
|
|
|
|
[next-edge (list 'partial node next-edge next-common-len key (cons node visited))]
|
|
|
|
[else (list 'no-match node key (cons node visited))]))]))
|
2020-01-29 03:05:17 +00:00
|
|
|
|
2020-02-14 21:22:06 +00:00
|
|
|
;; RT RTkey Any -> Void
|
|
|
|
;; Updates the radix tree for the given key, adding the elem to the data at that key.
|
|
|
|
(define (rt-add! node key elem)
|
2020-02-01 02:19:55 +00:00
|
|
|
(define (insert-node! node key)
|
|
|
|
(let* ([bit (first key)]
|
2020-01-29 03:05:17 +00:00
|
|
|
[setter! (rt-setter bit)])
|
2020-02-14 21:22:06 +00:00
|
|
|
(setter! node (rt-edge key (rt-node #f #f (list elem))))))
|
2020-02-01 02:19:55 +00:00
|
|
|
(define (split-node! node key orig-edge prefix-len)
|
|
|
|
(let* ([bit (first key)]
|
2020-01-29 03:05:17 +00:00
|
|
|
[setter! (rt-setter bit)]
|
|
|
|
[orig-label (rt-edge-label orig-edge)]
|
|
|
|
[orig-target (rt-edge-target orig-edge)]
|
2020-02-04 22:50:08 +00:00
|
|
|
[new-orig-edge (rt-edge (drop orig-label prefix-len) orig-target)])
|
|
|
|
(if (= prefix-len (length key))
|
|
|
|
(let* ([next-bit (list-ref orig-label prefix-len)]
|
|
|
|
[common-node (rt-node (if next-bit #f new-orig-edge)
|
|
|
|
(if next-bit new-orig-edge #f)
|
2020-02-14 21:22:06 +00:00
|
|
|
(list elem))]
|
2020-02-04 22:50:08 +00:00
|
|
|
[common-edge (rt-edge (take key prefix-len) common-node)])
|
|
|
|
(setter! node common-edge))
|
|
|
|
(let* ([new-insert-edge (rt-edge (drop key prefix-len)
|
2020-02-14 21:22:06 +00:00
|
|
|
(rt-node #f #f (list elem)))]
|
2020-02-04 22:50:08 +00:00
|
|
|
[diff-bit (list-ref key prefix-len)]
|
|
|
|
[common-node (rt-node (if diff-bit new-orig-edge new-insert-edge)
|
|
|
|
(if diff-bit new-insert-edge new-orig-edge)
|
2020-02-14 21:22:06 +00:00
|
|
|
'())]
|
2020-02-04 22:50:08 +00:00
|
|
|
[common-edge (rt-edge (take key prefix-len) common-node)])
|
|
|
|
(setter! node common-edge)))))
|
2020-01-29 03:05:17 +00:00
|
|
|
(match (rt-partial-iterate node key)
|
2020-02-15 00:52:37 +00:00
|
|
|
[(list 'exact node _)
|
2020-02-14 21:22:06 +00:00
|
|
|
(set-rt-node-data! node
|
|
|
|
(cons elem (rt-node-data node)))]
|
2020-02-01 03:11:42 +00:00
|
|
|
[(list 'partial node orig-edge prefix-len partial-key visited)
|
2020-02-01 02:19:55 +00:00
|
|
|
(split-node! node partial-key orig-edge prefix-len)]
|
2020-02-01 03:11:42 +00:00
|
|
|
[(list 'no-match node partial-key visited)
|
2020-02-01 02:19:55 +00:00
|
|
|
(insert-node! node partial-key)]))
|
|
|
|
|
2020-02-14 21:22:06 +00:00
|
|
|
;; RT RTKey (Any -> Boolean) -> Void
|
|
|
|
;; Delete all entries under the given key
|
|
|
|
(define (rt-del! node key del?)
|
|
|
|
(for ([node (in-list (match (rt-partial-iterate node key)
|
2020-02-15 00:52:37 +00:00
|
|
|
[(list 'exact node _) (list node)]
|
2020-02-14 21:22:06 +00:00
|
|
|
[(list _ ... visited) visited]))])
|
|
|
|
(set-rt-node-data! node
|
|
|
|
(filter (negate del?)
|
|
|
|
(rt-node-data node)))))
|
|
|
|
|
|
|
|
;; RT RTkey -> [Listof Any]
|
|
|
|
;; Looks up all the matches for the given key in the tree, ordered from most-specific to
|
|
|
|
;; least-specific.
|
|
|
|
(define (rt-lookup node key)
|
|
|
|
(match (rt-partial-iterate node key)
|
|
|
|
[(list _ ... visited) (append-map rt-node-data visited)]))
|
2020-01-29 03:05:17 +00:00
|
|
|
|
2020-02-02 04:55:14 +00:00
|
|
|
;; RT -> [Listof Any]
|
|
|
|
;; Converts the tree into a flat list of all contained node data
|
2020-02-01 04:39:42 +00:00
|
|
|
(define (rt-flatten node)
|
2020-02-14 21:22:06 +00:00
|
|
|
(define (flatten-edge e)
|
|
|
|
(if e (rt-flatten (rt-edge-target e)) '()))
|
|
|
|
(append (rt-node-data node)
|
|
|
|
(flatten-edge (rt-node-edge0 node))
|
|
|
|
(flatten-edge (rt-node-edge1 node))))
|
2020-02-01 04:39:42 +00:00
|
|
|
|
2020-02-02 04:55:14 +00:00
|
|
|
;; RT [Str] ->
|
|
|
|
;; Debug print function that dumps the tree to current-output-port in a vaguely human-readable
|
|
|
|
;; format
|
2020-02-01 02:19:55 +00:00
|
|
|
(define (rt-dump node [prefix ""])
|
2020-02-04 23:13:52 +00:00
|
|
|
(displayln (format "~a node ~s" prefix (rt-node-data node)))
|
2020-02-01 02:19:55 +00:00
|
|
|
(define edge0 (rt-node-edge0 node))
|
|
|
|
(define edge1 (rt-node-edge1 node))
|
|
|
|
(when edge0
|
|
|
|
(displayln (format "~a edge0 ~a" prefix (rt-edge-label edge0)))
|
|
|
|
(rt-dump (rt-edge-target edge0) (string-append prefix " ")))
|
|
|
|
(when edge1
|
|
|
|
(displayln (format "~a edge1 ~a" prefix (rt-edge-label edge1)))
|
|
|
|
(rt-dump (rt-edge-target edge1) (string-append prefix " "))))
|
2020-02-04 22:50:08 +00:00
|
|
|
|
2020-02-14 21:22:06 +00:00
|
|
|
|
|
|
|
(module+ test
|
|
|
|
|
|
|
|
(define (str->bl x)
|
2020-02-04 22:50:08 +00:00
|
|
|
(map (curry equal? #\1) (string->list x)))
|
2020-02-14 21:22:06 +00:00
|
|
|
|
|
|
|
(define test (make-rt))
|
|
|
|
|
|
|
|
(define (test-insert! x)
|
|
|
|
(rt-add! test (str->bl x) x))
|
|
|
|
|
|
|
|
(test-insert! "0001")
|
|
|
|
(test-insert! "")
|
|
|
|
(rt-dump test)
|
|
|
|
(test-insert! "0001")
|
|
|
|
(test-insert! "000")
|
|
|
|
(test-insert! "0")
|
|
|
|
(rt-dump test)
|
|
|
|
(rt-partial-iterate test (str->bl "0001"))
|
|
|
|
(rt-lookup test (str->bl "0000"))
|
|
|
|
|
|
|
|
; (test-insert! "0001")
|
|
|
|
; (test-insert! "1000")
|
|
|
|
; (test-insert! "1010")
|
|
|
|
; (test-insert! "0011")
|
|
|
|
; (test-insert! "0000")
|
|
|
|
)
|