summaryrefslogtreecommitdiff
path: root/lib/table.c
diff options
context:
space:
mode:
authorAvneesh Sachdev <avneesh@opensourcerouting.org>2012-08-17 08:19:50 -0700
committerDavid Lamparter <equinox@opensourcerouting.org>2012-09-26 21:50:48 +0200
commit28971c8cb1138700e87dc7da673e59b5596bb51b (patch)
tree0e55c3f830681449cd96bb36eb04a6a1293d8b44 /lib/table.c
parent67174041d2d9d8908f8b2c915bc0d186d8442c68 (diff)
lib/table: add route_table_get_next() and iterator
* lib/table.[ch] - Add a function (route_table_get_next()) to get the route_node in a tree that succeeds a given prefix in iteration order. This allows one to reliably walk nodes in a tree while allowing modifications, and is useful for achieving scale and performance. Other approaches are also possible -- the main plus point of this one is that it does not require any state about the walk to be maintained in the table data structures. - Add an iterator for walking the nodes in a tree. This introduces a new structure (route_table_iter_t) and the following main functions. route_table_iter_init() route_table_iter_pause() route_table_iter_next() route_table_iter_cleanup() The iterator normally uses node pointers and the existing route_next() function to walk nodes efficiently. When an iteration is 'paused' with route_table_iter_pause(), it stores the last prefix processed. The next call to route_table_iter_next() transparently invokes route_table_get_next() with the prefix to resume iteration. * bgpd/bgp_table.[ch] Add wrappers for the new table features described above. * tests/table_test.c Add tests for the new table code. Signed-off-by: David Lamparter <equinox@opensourcerouting.org>
Diffstat (limited to 'lib/table.c')
-rw-r--r--lib/table.c282
1 files changed, 282 insertions, 0 deletions
diff --git a/lib/table.c b/lib/table.c
index 6bbc9c62..19b5d1b1 100644
--- a/lib/table.c
+++ b/lib/table.c
@@ -528,3 +528,285 @@ route_table_init (void)
{
return route_table_init_with_delegate (&default_delegate);
}
+
+/**
+ * route_table_prefix_iter_cmp
+ *
+ * Compare two prefixes according to the order in which they appear in
+ * an iteration over a tree.
+ *
+ * @return -1 if p1 occurs before p2 (p1 < p2)
+ * 0 if the prefixes are identical (p1 == p2)
+ * +1 if p1 occurs after p2 (p1 > p2)
+ */
+int
+route_table_prefix_iter_cmp (struct prefix *p1, struct prefix *p2)
+{
+ struct prefix common_space;
+ struct prefix *common = &common_space;
+
+ if (p1->prefixlen <= p2->prefixlen)
+ {
+ if (prefix_match (p1, p2))
+ {
+
+ /*
+ * p1 contains p2, or is equal to it.
+ */
+ return (p1->prefixlen == p2->prefixlen) ? 0 : -1;
+ }
+ }
+ else
+ {
+
+ /*
+ * Check if p2 contains p1.
+ */
+ if (prefix_match (p2, p1))
+ return 1;
+ }
+
+ route_common (p1, p2, common);
+ assert (common->prefixlen < p1->prefixlen);
+ assert (common->prefixlen < p2->prefixlen);
+
+ /*
+ * Both prefixes are longer than the common prefix.
+ *
+ * We need to check the bit after the common prefixlen to determine
+ * which one comes later.
+ */
+ if (prefix_bit (&p1->u.prefix, common->prefixlen))
+ {
+
+ /*
+ * We branch to the right to get to p1 from the common prefix.
+ */
+ assert (!prefix_bit (&p2->u.prefix, common->prefixlen));
+ return 1;
+ }
+
+ /*
+ * We branch to the right to get to p2 from the common prefix.
+ */
+ assert (prefix_bit (&p2->u.prefix, common->prefixlen));
+ return -1;
+}
+
+/*
+ * route_get_subtree_next
+ *
+ * Helper function that returns the first node that follows the nodes
+ * in the sub-tree under 'node' in iteration order.
+ */
+static struct route_node *
+route_get_subtree_next (struct route_node *node)
+{
+ while (node->parent)
+ {
+ if (node->parent->l_left == node && node->parent->l_right)
+ return node->parent->l_right;
+
+ node = node->parent;
+ }
+
+ return NULL;
+}
+
+/**
+ * route_table_get_next_internal
+ *
+ * Helper function to find the node that occurs after the given prefix in
+ * order of iteration.
+ *
+ * @see route_table_get_next
+ */
+static struct route_node *
+route_table_get_next_internal (const struct route_table *table,
+ struct prefix *p)
+{
+ struct route_node *node, *tmp_node;
+ u_char prefixlen;
+ int cmp;
+
+ prefixlen = p->prefixlen;
+
+ node = table->top;
+
+ while (node)
+ {
+ int match;
+
+ if (node->p.prefixlen < p->prefixlen)
+ match = prefix_match (&node->p, p);
+ else
+ match = prefix_match (p, &node->p);
+
+ if (match)
+ {
+ if (node->p.prefixlen == p->prefixlen)
+ {
+
+ /*
+ * The prefix p exists in the tree, just return the next
+ * node.
+ */
+ route_lock_node (node);
+ node = route_next (node);
+ if (node)
+ route_unlock_node (node);
+
+ return (node);
+ }
+
+ if (node->p.prefixlen > p->prefixlen)
+ {
+
+ /*
+ * Node is in the subtree of p, and hence greater than p.
+ */
+ return node;
+ }
+
+ /*
+ * p is in the sub-tree under node.
+ */
+ tmp_node = node->link[prefix_bit (&p->u.prefix, node->p.prefixlen)];
+
+ if (tmp_node)
+ {
+ node = tmp_node;
+ continue;
+ }
+
+ /*
+ * There are no nodes in the direction where p should be. If
+ * node has a right child, then it must be greater than p.
+ */
+ if (node->l_right)
+ return node->l_right;
+
+ /*
+ * No more children to follow, go upwards looking for the next
+ * node.
+ */
+ return route_get_subtree_next (node);
+ }
+
+ /*
+ * Neither node prefix nor 'p' contains the other.
+ */
+ cmp = route_table_prefix_iter_cmp (&node->p, p);
+ if (cmp > 0)
+ {
+
+ /*
+ * Node follows p in iteration order. Return it.
+ */
+ return node;
+ }
+
+ assert (cmp < 0);
+
+ /*
+ * Node and the subtree under it come before prefix p in
+ * iteration order. Prefix p and its sub-tree are not present in
+ * the tree. Go upwards and find the first node that follows the
+ * subtree. That node will also succeed p.
+ */
+ return route_get_subtree_next (node);
+ }
+
+ return NULL;
+}
+
+/**
+ * route_table_get_next
+ *
+ * Find the node that occurs after the given prefix in order of
+ * iteration.
+ */
+struct route_node *
+route_table_get_next (const struct route_table *table, struct prefix *p)
+{
+ struct route_node *node;
+
+ node = route_table_get_next_internal (table, p);
+ if (node)
+ {
+ assert (route_table_prefix_iter_cmp (&node->p, p) > 0);
+ route_lock_node (node);
+ }
+ return node;
+}
+
+/*
+ * route_table_iter_init
+ */
+void
+route_table_iter_init (route_table_iter_t * iter, struct route_table *table)
+{
+ memset (iter, 0, sizeof (*iter));
+ iter->state = RT_ITER_STATE_INIT;
+ iter->table = table;
+}
+
+/*
+ * route_table_iter_pause
+ *
+ * Pause an iteration over the table. This allows the iteration to be
+ * resumed point after arbitrary additions/deletions from the table.
+ * An iteration can be resumed by just calling route_table_iter_next()
+ * on the iterator.
+ */
+void
+route_table_iter_pause (route_table_iter_t * iter)
+{
+ switch (iter->state)
+ {
+
+ case RT_ITER_STATE_INIT:
+ case RT_ITER_STATE_PAUSED:
+ case RT_ITER_STATE_DONE:
+ return;
+
+ case RT_ITER_STATE_ITERATING:
+
+ /*
+ * Save the prefix that we are currently at. The next call to
+ * route_table_iter_next() will return the node after this prefix
+ * in the tree.
+ */
+ prefix_copy (&iter->pause_prefix, &iter->current->p);
+ route_unlock_node (iter->current);
+ iter->current = NULL;
+ iter->state = RT_ITER_STATE_PAUSED;
+ return;
+
+ default:
+ assert (0);
+ }
+
+}
+
+/*
+ * route_table_iter_cleanup
+ *
+ * Release any resources held by the iterator.
+ */
+void
+route_table_iter_cleanup (route_table_iter_t * iter)
+{
+ if (iter->state == RT_ITER_STATE_ITERATING)
+ {
+ route_unlock_node (iter->current);
+ iter->current = NULL;
+ }
+ assert (!iter->current);
+
+ /*
+ * Set the state to RT_ITER_STATE_DONE to make any
+ * route_table_iter_next() calls on this iterator return NULL.
+ */
+ iter->state = RT_ITER_STATE_DONE;
+}