#lang racket (require "iputil.rkt") (provide make-rt rt-update! rt-lookup rt-dump rt-flatten) ;; Radix tree implementation for efficient routing lookups ;; This module provides a radix tree type indexed by bit-lists (lists of booleans representing IP ;; address bits), which supports arbitary node data at any tree depth. Lookup always find the most ;; specific match. ;; An RT is a (rt-node RTedge RTedge Any) ;; An RTkey is a [Listof Bool] ;; An RTedge is a (rt-edge RTkey RT) (struct rt-node [edge0 edge1 data] #:transparent #:mutable) ;; 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) (struct rt-edge [label target] #:transparent) ;; A unique symbol representing no data associated with a certain node (define empty-node-data (gensym 'MEOW)) ;; -> RT ;; Creates a new empty RT (define (make-rt) (rt-node #f #f empty-node-data)) ;; 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!)) ;; RTkey RTkey -> Nat ;; Computes the length of the longest common prefix in bl1 and bl2 (define (bl-common-len bl1 bl2) (for/sum ([b1 (in-list bl1)] [b2 (in-list bl2)]) #:break (not (equal? b1 b2)) 1)) ;; 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 (define (rt-partial-iterate node key [visited '()]) (cond [(empty? key) (list 'exact node)] [else (let* ([bit (first key)] [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))] [next-common-len (and next-label (bl-common-len next-label key))]) (cond [(and next-edge (= next-common-len (length next-label))) (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))]))])) ;; RT RTkey (Any -> Any) (-> Any) ;; Updates the radix tree for the given key, using updater and failure-result like the standard ;; racket -update! abstraction (define (rt-update! node key updater failure-result) (define (insert-node! node key) (let* ([bit (first key)] [setter! (rt-setter bit)]) (setter! node (rt-edge key (rt-node #f #f (updater (failure-result))))))) (define (split-node! node key orig-edge prefix-len) (let* ([bit (first key)] [setter! (rt-setter bit)] [orig-label (rt-edge-label orig-edge)] [orig-target (rt-edge-target orig-edge)] [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) (updater (failure-result)))] [common-edge (rt-edge (take key prefix-len) common-node)]) (setter! node common-edge)) (let* ([new-insert-edge (rt-edge (drop key prefix-len) (rt-node #f #f (updater (failure-result))))] [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) empty-node-data)] [common-edge (rt-edge (take key prefix-len) common-node)]) (setter! node common-edge))))) (match (rt-partial-iterate node key) [(list 'exact node) (set-rt-node-data! node (let ([d (rt-node-data node)]) (updater (if (eq? d empty-node-data) (failure-result) d))))] [(list 'partial node orig-edge prefix-len partial-key visited) (split-node! node partial-key orig-edge prefix-len)] [(list 'no-match node partial-key visited) (insert-node! node partial-key)])) ;; RT RTkey (-> Any) -> Any ;; Looks up the most specific match for the given key in the tree, using failure-result if nothing ;; was found (define (rt-lookup node key failure-result) (define (find-first-with-data nodes) (or (for/first ([node (in-list nodes)] #:when (not (eq? empty-node-data (rt-node-data node)))) (rt-node-data node)) (failure-result))) (find-first-with-data (match (rt-partial-iterate node key) [(list 'exact node) (list node)] [(list _ ... visited) visited]))) ;; RT -> [Listof Any] ;; Converts the tree into a flat list of all contained node data (define (rt-flatten node) (define (flatten-edge e) (if e (rt-flatten (rt-edge-target e)) '())) (let* ([e1 (rt-node-edge0 node)] [e2 (rt-node-edge1 node)] [data (rt-node-data node)] [rst (apply append (map flatten-edge (list e1 e2)))]) (if (equal? data empty-node-data) rst (cons data rst)))) ;; RT [Str] -> ;; Debug print function that dumps the tree to current-output-port in a vaguely human-readable ;; format (define (rt-dump node [prefix ""]) (displayln (format "~a node ~a" prefix (rt-node-data node))) (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 " ")))) (define test (make-rt)) (define (str->bl x) (map (curry equal? #\1) (string->list x))) (define (test-insert! x) (rt-update! test (str->bl x) (lambda (_) x) (lambda () x))) ; (test-insert! "0001") ; (test-insert! "000") ; (test-insert! "0") ; (rt-dump test) ; (rt-partial-iterate test (str->bl "0001")) ; (rt-lookup test (str->bl "0001") (lambda () (error "bad"))) ; (test-insert! "0001") ; (test-insert! "1000") ; (test-insert! "1010") ; (test-insert! "0011") ; (test-insert! "0000")