diff options
Diffstat (limited to 'rtrlib/pfx/trie/trie.c')
-rw-r--r-- | rtrlib/pfx/trie/trie.c | 235 |
1 files changed, 235 insertions, 0 deletions
diff --git a/rtrlib/pfx/trie/trie.c b/rtrlib/pfx/trie/trie.c new file mode 100644 index 0000000..b359892 --- /dev/null +++ b/rtrlib/pfx/trie/trie.c @@ -0,0 +1,235 @@ +/* + * This file is part of RTRlib. + * + * This file is subject to the terms and conditions of the MIT license. + * See the file LICENSE in the top level directory for more details. + * + * Website: http://rtrlib.realmv6.org/ + */ + +#include "trie_private.h" + +#include "rtrlib/lib/alloc_utils_private.h" +#include "rtrlib/lib/ip_private.h" + +#include <assert.h> +#include <stdlib.h> + +static void swap_nodes(struct trie_node *a, struct trie_node *b) +{ + struct trie_node tmp; + + tmp.prefix = a->prefix; + tmp.len = a->len; + tmp.data = a->data; + + a->prefix = b->prefix; + a->len = b->len; + a->data = b->data; + + b->prefix = tmp.prefix; + b->len = tmp.len; + b->data = tmp.data; +} + +enum child_node_rel { LEFT, RIGHT }; + +static void add_child_node(struct trie_node *parent, struct trie_node *child, enum child_node_rel rel) +{ + assert(rel == LEFT || rel == RIGHT); + + if (rel == LEFT) + parent->lchild = child; + else + parent->rchild = child; + + child->parent = parent; +} + +static inline bool is_left_child(const struct lrtr_ip_addr *addr, unsigned int lvl) +{ + /* A node must be inserted as left child if bit <lvl> of the IP address + * is 0 otherwise as right child + */ + return lrtr_ip_addr_is_zero(lrtr_ip_addr_get_bits(addr, lvl, 1)); +} + +void trie_insert(struct trie_node *root, struct trie_node *new, const unsigned int lvl) +{ + if (new->len < root->len) + swap_nodes(root, new); + + if (is_left_child(&new->prefix, lvl)) { + if (!root->lchild) + return add_child_node(root, new, LEFT); + + return trie_insert(root->lchild, new, lvl + 1); + } + + if (!root->rchild) + return add_child_node(root, new, RIGHT); + + trie_insert(root->rchild, new, lvl + 1); +} + +struct trie_node *trie_lookup(const struct trie_node *root, const struct lrtr_ip_addr *prefix, const uint8_t mask_len, + unsigned int *lvl) +{ + while (root) { + if (root->len <= mask_len && lrtr_ip_addr_equal(lrtr_ip_addr_get_bits(&root->prefix, 0, root->len), + lrtr_ip_addr_get_bits(prefix, 0, root->len))) + return (struct trie_node *)root; + + if (is_left_child(prefix, *lvl)) + root = root->lchild; + else + root = root->rchild; + + (*lvl)++; + } + return NULL; +} + +struct trie_node *trie_lookup_exact(struct trie_node *root_node, const struct lrtr_ip_addr *prefix, + const uint8_t mask_len, unsigned int *lvl, bool *found) +{ + *found = false; + + while (root_node) { + if (*lvl > 0 && root_node->len > mask_len) { + (*lvl)--; + return root_node->parent; + } + + if (root_node->len == mask_len && lrtr_ip_addr_equal(root_node->prefix, *prefix)) { + *found = true; + return root_node; + } + + if (is_left_child(prefix, *lvl)) { + if (!root_node->lchild) + return root_node; + root_node = root_node->lchild; + } else { + if (!root_node->rchild) + return root_node; + root_node = root_node->rchild; + } + + (*lvl)++; + } + return NULL; +} + +static void deref_node(struct trie_node *n) +{ + if (!n->parent) + return; + + if (n->parent->lchild == n) { + n->parent->lchild = NULL; + return; + } + + n->parent->rchild = NULL; +} + +static inline bool prefix_is_same(const struct trie_node *n, const struct lrtr_ip_addr *p, uint8_t mask_len) +{ + return n->len == mask_len && lrtr_ip_addr_equal(n->prefix, *p); +} + +static void replace_node_data(struct trie_node *a, struct trie_node *b) +{ + a->prefix = b->prefix; + a->len = b->len; + a->data = b->data; +} + +struct trie_node *trie_remove(struct trie_node *root, const struct lrtr_ip_addr *prefix, const uint8_t mask_len, + const unsigned int lvl) +{ + /* If the node has no children we can simply remove it + * If the node has children, we swap the node with the child that + * has the smaller prefix length and drop the child. + */ + if (prefix_is_same(root, prefix, mask_len)) { + void *tmp; + + if (trie_is_leaf(root)) { + deref_node(root); + return root; + } + + /* swap with the left child and drop the child */ + if (root->lchild && (!root->rchild || root->lchild->len < root->rchild->len)) { + tmp = root->data; + replace_node_data(root, root->lchild); + root->lchild->data = tmp; + + return trie_remove(root->lchild, &root->lchild->prefix, root->lchild->len, lvl + 1); + } + + /* swap with the right child and drop the child */ + tmp = root->data; + replace_node_data(root, root->rchild); + root->rchild->data = tmp; + + return trie_remove(root->rchild, &root->rchild->prefix, root->rchild->len, lvl + 1); + } + + if (is_left_child(prefix, lvl)) { + if (!root->lchild) + return NULL; + return trie_remove(root->lchild, prefix, mask_len, lvl + 1); + } + + if (!root->rchild) + return NULL; + return trie_remove(root->rchild, prefix, mask_len, lvl + 1); +} + +static int append_node_to_array(struct trie_node ***ary, unsigned int *len, struct trie_node *n) +{ + struct trie_node **new; + + new = lrtr_realloc(*ary, *len * sizeof(n)); + if (!new) + return -1; + + *ary = new; + (*ary)[*len - 1] = n; + return 0; +} + +int trie_get_children(const struct trie_node *root_node, struct trie_node ***array, unsigned int *len) +{ + if (root_node->lchild) { + *len += 1; + if (append_node_to_array(array, len, root_node->lchild)) + goto err; + + if (trie_get_children(root_node->lchild, array, len) == -1) + goto err; + } + + if (root_node->rchild) { + *len += 1; + if (append_node_to_array(array, len, root_node->rchild)) + goto err; + + if (trie_get_children(root_node->rchild, array, len) == -1) + goto err; + } + + return 0; + +err: + lrtr_free(*array); + return -1; +} + +inline bool trie_is_leaf(const struct trie_node *node) +{ + return !node->lchild && !node->rchild; +} |