aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt2
-rw-r--r--contrib/lc-btrie/CMakeLists.txt8
-rw-r--r--contrib/lc-btrie/btrie.c2622
-rw-r--r--contrib/lc-btrie/btrie.h83
-rw-r--r--test/CMakeLists.txt1
-rw-r--r--test/rspamd_radix_test.c112
6 files changed, 2804 insertions, 24 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index ae950659d..33dcf5cbf 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -471,6 +471,7 @@ INCLUDE_DIRECTORIES("${CMAKE_SOURCE_DIR}/src"
"${CMAKE_SOURCE_DIR}/contrib/snowball/include"
"${CMAKE_SOURCE_DIR}/contrib/librdns"
"${CMAKE_SOURCE_DIR}/contrib/aho-corasick"
+ "${CMAKE_SOURCE_DIR}/contrib/lc-btrie"
"${CMAKE_BINARY_DIR}/src" #Stored in the binary dir
"${CMAKE_BINARY_DIR}/src/libcryptobox")
@@ -1064,6 +1065,7 @@ ENDIF(GLIB_COMPAT)
ADD_SUBDIRECTORY(contrib/xxhash)
ADD_SUBDIRECTORY(contrib/cdb)
ADD_SUBDIRECTORY(contrib/http-parser)
+ADD_SUBDIRECTORY(contrib/lc-btrie)
ADD_SUBDIRECTORY(contrib/libottery)
IF(ENABLE_SNOWBALL MATCHES "ON")
ADD_SUBDIRECTORY(contrib/snowball)
diff --git a/contrib/lc-btrie/CMakeLists.txt b/contrib/lc-btrie/CMakeLists.txt
new file mode 100644
index 000000000..7ae9ce063
--- /dev/null
+++ b/contrib/lc-btrie/CMakeLists.txt
@@ -0,0 +1,8 @@
+SET(LCTRIESRC btrie.c)
+ADD_LIBRARY(lcbtrie STATIC ${LCTRIESRC})
+
+SET(LCTRIE_CFLAGS "-DBUILD_RSPAMD")
+if ("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" OR "${CMAKE_C_COMPILER_ID}" STREQUAL "GNU")
+ SET(LCTRIE_CFLAGS "${LCTRIE_CFLAGS} -O3")
+endif ()
+set_target_properties(lcbtrie PROPERTIES COMPILE_FLAGS "${LCTRIE_CFLAGS}") \ No newline at end of file
diff --git a/contrib/lc-btrie/btrie.c b/contrib/lc-btrie/btrie.c
new file mode 100644
index 000000000..899b05061
--- /dev/null
+++ b/contrib/lc-btrie/btrie.c
@@ -0,0 +1,2622 @@
+/* Level-Compressed Tree Bitmap (LC-TBM) Trie implementation
+ *
+ * Contributed by Geoffrey T. Dairiki <dairiki@dairiki.org>
+ *
+ * This file is released under a "Three-clause BSD License".
+ *
+ * Copyright (c) 2013, Geoffrey T. Dairiki
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * * Neither the name of Geoffrey T. Dairiki nor the names of other
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GEOFFREY
+ * T. DAIRIKI BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+
+/*****************************************************************
+ *
+ * This code implements a routing table conceptually based on a binary
+ * trie structure. Internally, the trie is represented by two types
+ * of compound nodes: "multibit nodes", which contain the top few
+ * levels of an entire binary subtree; and "level compression" (LC)
+ * nodes which represent a (potentially long) chain of out-degree one
+ * (single child) binary nodes (possibly ending at a terminal node).
+ *
+ * The multibit nodes are represented using a "Tree Bitmap" structure
+ * (more on this below), which is very efficient --- both in terms of
+ * memory usage and lookup speed --- at representing densely branching
+ * parts of the trie. The LC nodes can efficiently represent long
+ * non-branching chains of binary trie nodes. Using both node types
+ * together results in efficient representation of both the sparse and
+ * dense parts of a binary trie.
+ *
+ * Graphically, here's the rough idea:
+ *
+ * ........
+ * .LC o .
+ * . / . LC nodes can
+ * . o . <= represent long chains
+ * . \ . of (non-branching) binary
+ * . o . trie nodes
+ * . / .
+ * . o .
+ * ......../.....
+ * .TBM o .
+ * . / \ . TBM nodes can represent
+ * . o * . <= several levels of densely
+ * . / \ . branching binary trie nodes
+ * . o o .
+ * ......./.....\.......
+ * .TBM o .. o LC.
+ * . / \ .. \ .
+ * . o o .. o .
+ * . / / \ .. \ .
+ * . * o *.. o .
+ * ...../....... / .
+ * . o LC. . o .
+ * . \ . .....\......
+ * . * . . o TBM.
+ * ........ . / \ .
+ * . o o .
+ * . / \ \ .
+ * .* * *.
+ * ...........
+ *
+ * Terminology
+ * -----------
+ *
+ * node
+ * Usually, in the comments below, "node" will be used to refer to
+ * a compound node: either a multibit (TBM) node or an LC node.
+ *
+ * "internal node" or "prefix"
+ * The terms "prefix" or "internal node" are used to refer to
+ * a node in the binary trie which is internal to a multibit (TBM)
+ * node.
+ *
+ * ----------------------------------------------------------------
+ *
+ * Internal Representation of the Nodes
+ * ====================================
+ *
+ * Multibit (TBM) Nodes
+ * ~~~~~~~~~~~~~~~~~~~~
+ *
+ * The multibit nodes are represented using a "Tree Bitmap" (TBM)
+ * structure as described by Eatherton, Dittia and Varghese[1]. See
+ * the paper referenced below for basic details.
+ *
+ * A multibit node, represents several levels of a binary trie.
+ * For example, here is a multibit node of stride 2 (which represent
+ * two levels of a binary trie.
+ *
+ * +------- | ------+
+ * | multi o |
+ * | bit / \ |
+ * | node / \ |
+ * | o * |
+ * +--- / \ - / \ --+
+ * O
+ *
+ * Note that, for a multibit node of stride S, there are 2^S - 1 internal
+ * nodes, each of which may have data (or not) associated with them, and
+ * 2^S "external paths" leading to other (possibly compound nodes).
+ * (In the diagram above, one of three internal node (the one denoted by "*")
+ * has data, and one of four extending paths leads to an external node
+ * (denoted by the 'O').)
+ *
+ * The TBM structure can represent these bitmaps in a very memory-efficient
+ * manner.
+ *
+ * Each TBM node consists of two bitmaps --- the "internal bitmap" and the
+ * "extending paths bitmap" --- and a pointer which points to an array
+ * which contains both the extending path ("child") nodes and any
+ * internal prefix data for the TBM node.
+ *
+ * +--------+--------+
+ * TBM | ext bm | int bm |
+ * Node +--------+--------+
+ * | pointer |----+
+ * +-----------------+ |
+ * |
+ * |
+ * +-----------------+ |
+ * | extending path | |
+ * | node[N-1] | |
+ * +-----------------+ |
+ * / ... / |
+ * / ... / |
+ * +-----------------+ |
+ * | extending path | |
+ * | node[0] | |
+ * +-----------------+<---+
+ * | int. data[M-1] |
+ * +-----------------+
+ * / ... /
+ * +-----------------+
+ * | int. data[0] |
+ * +-----------------+
+ *
+ * The extending paths bitmap (or "ext bitmap") has one bit for each
+ * possible "extending path" from the bottom of the multibit node. To
+ * check if a particular extending path is present, one checks to see if
+ * the corresponding bit is set in the ext bitmap. The index into the
+ * array of children for that path can be found by counting the number
+ * of set bits to the left of that bit.
+ *
+ * Similary, the internal bitmap has one bit for each binary node
+ * which is internal to the multibit node. To determine whether there
+ * is data stored for an internal prefix, one checks the corresponding
+ * bit in the internal bitmap. As for extending paths, the index into
+ * the array of internal data is found by counting the number of set
+ * bits to the left of that bit.
+ *
+ * To save space in the node structure, the node data array is stored
+ * contiguously with the node extending path array. The single
+ * ("children") pointer in the TBM structure points to the beginning
+ * of the array of extending path nodes and to (one past) the end of
+ * the the internal data array.
+ *
+ * The multibit stride is chosen so that the entire TBM node structure fits
+ * in the space of two pointers. On 32 bit machines this means the stride
+ * is four (each of the two bitmaps is 16 bits); on 32 bit machines the
+ * stride is five.
+ *
+ * Note that there are only 2^stride - 1 internal prefixes in a TBM
+ * node. That means there is one unused bit in the internal bitmap.
+ * We require that that bit must always be clear for a TBM node. (If
+ * set, it indicates that the structure represents, instead, an LC
+ * node. See below.)
+ *
+ * ----------------------------------------------------------------
+ *
+ * Level Compression (LC) Nodes
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * LC nodes are used to represent a chain of out-degree-one (single
+ * child) prefixes in the binary trie. The are represented by a bit
+ * string (the "relative prefix") along with its length and a pointer
+ * to the extending path (the next node past the LC node.)
+ *
+ *
+ * Non-Terminal LC Node:
+ *
+ * +------------------+-------+
+ * | relative prefix |1|0|len|
+ * +------------------+-------+
+ * | ptr.child |--+
+ * +--------------------------+ |
+ * |
+ * |
+ * +--------------------------+ |
+ * | Next node - | |
+ * | either LC or TBM | |
+ * | | |
+ * +--------------------------+<-+
+ *
+ * The Relative Prefix
+ * -------------------
+ *
+ * The maximum relative prefix per LC node is selected so that (again)
+ * the entire node structure fits in the space of two pointers. On 32 bit
+ * machines, the maximum relative prefix is 24 bits; on 62 bit machines
+ * the limit is 56 bits.
+ *
+ * In the LC node structure, the relative prefix is stored as an array
+ * of bytes. To avoid some bit-shifting during tree searches, these
+ * bytes are byte-aligned with the global prefix. In other words, in
+ * general there are (pos % 8) "pad" bits at the beginning of the
+ * relative prefix --- where pos "starting bit" (or depth in the
+ * binary tree) of the LC node --- which really belong to the parent
+ * node(s) of the LC node. For efficiency (so that we don't have to
+ * mask them out when matching) we require that these pad bits be
+ * correct --- they must match the path which leads to the LC node.
+ *
+ * The relative prefix length stored in the LC node structure does not
+ * count the pad bits.
+ *
+ * Terminal Node Compression
+ * -------------------------
+ *
+ * For memory efficiency, we also support "terminal LC" nodes. When
+ * the extension path from an LC node consists a single terminal node,
+ * we store that terminal nodes data directly in the parent LC node.
+ *
+ * Instead of this:
+ *
+ * +------------------+-------+
+ * | relative prefix |1|0|len|
+ * +------------------+-------+
+ * | ptr.child |--+
+ * +--------------------------+ |
+ * |
+ * +--------------------------+ |
+ * | Terminal Node (TBM node, | |
+ * | empty except for the | |
+ * +--| root internal node.) | |
+ * | +--------------------------+<-+
+ * |
+ * +->+--------------------------+
+ * | terminal node data |
+ * +--------------------------+
+ *
+ * We can do this:
+ *
+ * +------------------+-------+
+ * | relative prefix |1|1|len|
+ * +------------------+-------+
+ * | terminal node data |
+ * +--------------------------+
+ *
+ * Terminal LC nodes are differentiated from non-terminal LC nodes
+ * by the setting of the is_terminal flag.
+ *
+ * Node Structure Packing Details
+ * ------------------------------
+ *
+ * The LC and TBM node structures are carefully packed so that the
+ * "is_lc" flag (which indicates that a node is an LC node)
+ * corresponds to the one unused bit in the internal bitmap of the TBM
+ * node structure (which we require to be zero for TBM nodes).
+ *
+ * ----------------------------------------------------------------
+ *
+ * References
+ * ==========
+ *
+ * [1] Will Eatherton, George Varghese, and Zubin Dittia. 2004. Tree
+ * bitmap: hardware/software IP lookups with incremental
+ * updates. SIGCOMM Comput. Commun. Rev. 34, 2 (April 2004),
+ * 97-122. DOI=10.1145/997150.997160
+ * http://doi.acm.org/10.1145/997150.997160
+ * http://comnet.kaist.ac.kr/yhlee/CN_2008_Spring/readings/Eath-04-tree_bitmap.pdf
+ *
+ ****************************************************************/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <setjmp.h>
+#if defined(TEST) && defined(NDEBUG)
+# warning undefining NDEBUG for TEST build
+# undef NDEBUG
+#endif
+#include <assert.h>
+
+#include "btrie.h"
+#include "libutil/mem_pool.h"
+
+#if __SIZEOF_POINTER__ == 4
+# define TBM_STRIDE 4
+#elif __SIZEOF_POINTER__ == 8
+# define TBM_STRIDE 5
+#else
+# error "Unsupported word size"
+#endif
+
+#ifndef NO_STDINT_H
+# if TBM_STRIDE == 4
+typedef uint16_t tbm_bitmap_t;
+# else
+typedef uint32_t tbm_bitmap_t;
+# endif
+#else /* NO_STDINT_H */
+# if TBM_STRIDE == 4
+# if SIZEOF_SHORT == 2
+typedef short unsigned tbm_bitmap_t;
+# else
+# error "can not determine type for 16 bit unsigned int"
+# endif
+# else /* TBM_STRIDE == 5 */
+# if SIZEOF_INT == 4
+typedef unsigned tbm_bitmap_t;
+# elif SIZEOF_LONG == 4
+typedef long unsigned tbm_bitmap_t;
+# else
+# error "can not determine type for 32 bit unsigned int"
+# endif
+# endif
+#endif
+
+#define TBM_FANOUT (1U << TBM_STRIDE)
+#define LC_BYTES_PER_NODE (__SIZEOF_POINTER__ - 1)
+
+typedef union node_u node_t;
+
+/* The tbm_node and lc_node structs must be packed so that the the
+ * high bit (LC_FLAGS_IS_LC) of lc_flags in the the lc_node struct
+ * coincides with bit zero (the most significant bit) of tbm_node's
+ * int_bm. (This bit is how we differentiate between the two node
+ * types. It is always clear for a tbm_node and always set for an
+ * lc_node.)
+ */
+
+struct tbm_node
+{
+#ifdef WORDS_BIGENDIAN
+ tbm_bitmap_t int_bm; /* the internal bitmap */
+ tbm_bitmap_t ext_bm; /* extending path ("external") bitmap */
+#else
+ tbm_bitmap_t ext_bm; /* extending path ("external") bitmap */
+ tbm_bitmap_t int_bm; /* the internal bitmap */
+#endif
+ union
+ {
+ node_t *children; /* pointer to array of children */
+ const void **data_end; /* one past end of internal prefix data array */
+ } ptr;
+};
+
+struct lc_node
+{
+ /* lc_flags contains the LC prefix length and a couple of bit flags
+ * (apparently char-sized bit fields are a gcc extension)
+ */
+# define LC_FLAGS_IS_LC 0x80
+# define LC_FLAGS_IS_TERMINAL 0x40
+# define LC_FLAGS_LEN_MASK 0x3f
+#ifdef WORDS_BIGENDIAN
+ btrie_oct_t lc_flags;
+ btrie_oct_t prefix[LC_BYTES_PER_NODE];
+#else
+ btrie_oct_t prefix[LC_BYTES_PER_NODE];
+ btrie_oct_t lc_flags;
+#endif
+ union
+ {
+ node_t *child; /* pointer to child (if !is_terminal) */
+ const void *data; /* the prefix data (if is_terminal) */
+ } ptr;
+};
+
+union node_u
+{
+ struct tbm_node tbm_node;
+ struct lc_node lc_node;
+};
+
+struct free_hunk
+{
+ struct free_hunk *next;
+};
+
+#define MAX_CHILD_ARRAY_LEN (TBM_FANOUT + TBM_FANOUT / 2)
+
+struct btrie
+{
+ node_t root;
+
+ rspamd_mempool_t *mp;
+ struct free_hunk *free_list[MAX_CHILD_ARRAY_LEN];
+ jmp_buf exception;
+ /* mem mgmt stats */
+ size_t alloc_total; /* total bytes allocated from mempool */
+ size_t alloc_data; /* bytes allocated for TBM node int. prefix data */
+ size_t alloc_waste; /* bytes wasted by rounding of data array size */
+#ifdef BTRIE_DEBUG_ALLOC
+ size_t alloc_hist[MAX_CHILD_ARRAY_LEN * 2]; /* histogram of alloc sizes */
+#endif
+
+ /* trie stats */
+ size_t n_entries; /* number of entries */
+ size_t n_tbm_nodes; /* total number of TBM nodes in tree */
+ size_t n_lc_nodes; /* total number of LC nodes in tree */
+};
+
+/****************************************************************
+ *
+ * Memory management
+ *
+ * We will need to frequently resize child/data arrays. The current
+ * mempool implementation does not support resizing/freeing, so here
+ * we roll our own.
+ */
+
+static inline void _free_hunk(struct btrie *btrie, void *buf, unsigned n_nodes)
+{
+ struct free_hunk *hunk = buf;
+
+ hunk->next = btrie->free_list[n_nodes - 1];
+ btrie->free_list[n_nodes - 1] = hunk;
+}
+
+static inline void *
+_get_hunk(struct btrie *btrie, unsigned n_nodes)
+{
+ struct free_hunk *hunk = btrie->free_list[n_nodes - 1];
+
+ if (hunk != NULL)
+ btrie->free_list[n_nodes - 1] = hunk->next;
+ return hunk;
+}
+
+/* Get pointer to uninitialized child/data array.
+ *
+ * Allocates memory for an array of NDATA (void *)s followed by an
+ * array of NCHILDREN (node_t)s. The returned pointer points to to
+ * beginning of the children array (i.e. it points to (one past) the
+ * end of the data array.)
+ */
+static node_t *
+alloc_nodes(struct btrie *btrie, unsigned nchildren, unsigned ndata)
+{
+ size_t n_nodes = nchildren + (ndata + 1) / 2;
+ node_t *hunk;
+
+ assert(n_nodes > 0 && n_nodes <= MAX_CHILD_ARRAY_LEN);
+
+ hunk = _get_hunk (btrie, n_nodes);
+ if (hunk == NULL) {
+ /* Do not have free hunk of exactly the requested size, look for a
+ * larger hunk. (The funny order in which we scan the buckets is
+ * heuristically selected in an attempt to minimize unnecessary
+ * creation of small fragments)
+ */
+ size_t n, skip = n_nodes > 4 ? 4 : n_nodes;
+ for (n = n_nodes + skip; n <= MAX_CHILD_ARRAY_LEN; n++) {
+ if ((hunk = _get_hunk (btrie, n)) != NULL) {
+ _free_hunk (btrie, hunk + n_nodes, n - n_nodes);
+ goto DONE;
+ }
+ }
+ for (n = n_nodes + 1; n < n_nodes + skip && n <= MAX_CHILD_ARRAY_LEN;
+ n++) {
+ if ((hunk = _get_hunk (btrie, n)) != NULL) {
+ _free_hunk (btrie, hunk + n_nodes, n - n_nodes);
+ goto DONE;
+ }
+ }
+
+ /* failed to find free hunk, allocate a fresh one */
+ hunk = rspamd_mempool_alloc (btrie->mp, n_nodes * sizeof(node_t));
+ if (hunk == NULL)
+ longjmp (btrie->exception, BTRIE_ALLOC_FAILED);
+ btrie->alloc_total += n_nodes * sizeof(node_t);
+ }
+
+ DONE: btrie->alloc_data += ndata * sizeof(void *);
+ btrie->alloc_waste += (ndata % 2) * sizeof(void *);
+#ifdef BTRIE_DEBUG_ALLOC
+ btrie->alloc_hist[2 * nchildren + ndata]++;
+#endif
+
+ /* adjust pointer to allow room for data array before child array */
+ return hunk + (ndata + 1) / 2;
+}
+
+/* Free memory allocated by alloc_nodes */
+static void free_nodes(struct btrie *btrie, node_t *buf, unsigned nchildren,
+ unsigned ndata)
+{
+ size_t n_nodes = nchildren + (ndata + 1) / 2;
+
+ assert(n_nodes > 0 && n_nodes <= MAX_CHILD_ARRAY_LEN);
+
+ _free_hunk (btrie, buf - (ndata + 1) / 2, n_nodes);
+
+ btrie->alloc_data -= ndata * sizeof(void *);
+ btrie->alloc_waste -= (ndata % 2) * sizeof(void *);
+#ifdef BTRIE_DEBUG_ALLOC
+ btrie->alloc_hist[2 * nchildren + ndata]--;
+#endif
+}
+
+/* Debugging/development only: */
+#ifdef BTRIE_DEBUG_ALLOC
+static void
+dump_alloc_hist(const struct btrie *btrie)
+{
+ unsigned bin;
+ size_t total_alloc = 0;
+ size_t total_free = 0;
+ size_t total_bytes = 0;
+ size_t total_waste = 0;
+ size_t total_free_bytes = 0;
+
+ puts("hunk alloc free alloc wasted free");
+ puts("size hunks hunks bytes bytes bytes");
+ puts("==== ====== ====== ======== ======== ========");
+
+ for (bin = 1; bin < 2 * MAX_CHILD_ARRAY_LEN; bin++) {
+ size_t n_alloc = btrie->alloc_hist[bin];
+ size_t bytes = n_alloc * bin * sizeof(void *);
+ size_t waste_bytes = (bin % 2) * n_alloc * sizeof(void *);
+ size_t n_free = 0, free_bytes;
+ if (bin % 2 == 0) {
+ const struct free_hunk *hunk;
+ for (hunk = btrie->free_list[bin / 2 - 1]; hunk; hunk = hunk->next)
+ n_free++;
+ }
+ free_bytes = n_free * bin * sizeof(void *);
+
+ printf("%3zu: %6zu %6zu %8zu %8zu %8zu\n", bin * sizeof(void *),
+ n_alloc, n_free, bytes, waste_bytes, free_bytes);
+
+ total_alloc += n_alloc;
+ total_free += n_free;
+ total_bytes += bytes;
+ total_waste += waste_bytes;
+ total_free_bytes += free_bytes;
+ }
+ puts("---- ------ ------ -------- -------- --------");
+ printf("SUM: %6zu %6zu %8zu %8zu %8zu\n",
+ total_alloc, total_free, total_bytes, total_waste, total_free_bytes);
+}
+#endif
+
+/****************************************************************
+ *
+ * Bit twiddling
+ *
+ */
+
+static inline tbm_bitmap_t bit(unsigned b)
+{
+ return 1U << ((1 << TBM_STRIDE) - 1 - b);
+}
+
+/* count the number of set bits in bitmap
+ *
+ * algorithm from
+ * http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetParallel
+ */
+static inline unsigned count_bits(tbm_bitmap_t v)
+{
+ /* Count set bits in parallel. */
+ /* v = (v & 0x5555...) + ((v >> 1) & 0x5555...); */
+ v -= (v >> 1) & (tbm_bitmap_t) ~0UL / 3;
+ /* v = (v & 0x3333...) + ((v >> 2) & 0x3333...); */
+ v = (v & (tbm_bitmap_t) ~0UL / 5) + ((v >> 2) & (tbm_bitmap_t) ~0UL / 5);
+ /* v = (v & 0x0f0f...) + ((v >> 4) & 0x0f0f...); */
+ v = (v + (v >> 4)) & (tbm_bitmap_t) ~0UL / 17;
+ /* v = v % 255; */
+#if TBM_STRIDE == 4
+ /* tbm_bitmap_t is uint16_t, avoid the multiply */
+ return (v + (v >> 8)) & 0x0ff;
+#else
+ return (v * (tbm_bitmap_t) (~0UL / 255)) >> ((sizeof(tbm_bitmap_t) - 1) * 8);
+#endif
+}
+
+static inline unsigned count_bits_before(tbm_bitmap_t bm, int b)
+{
+ return b ? count_bits (bm >> ((1 << TBM_STRIDE) - b)) : 0;
+}
+
+static inline unsigned count_bits_from(tbm_bitmap_t bm, int b)
+{
+ return count_bits (bm << b);
+}
+
+/* extracts a few bits from bitstring, returning them as an integer */
+static inline btrie_oct_t extract_bits(const btrie_oct_t *prefix, unsigned pos,
+ unsigned nbits)
+{
+ if (nbits == 0)
+ return 0;
+ else {
+ unsigned v = (prefix[pos / 8] << 8) + prefix[pos / 8 + 1];
+ return (v >> (16 - nbits - pos % 8)) & ((1U << nbits) - 1);
+ }
+}
+
+static inline unsigned extract_bit(const btrie_oct_t *prefix, int pos)
+{
+ return (prefix[pos / 8] >> (7 - pos % 8)) & 0x01;
+}
+
+/* get mask for high n bits of a byte */
+static inline btrie_oct_t high_bits(unsigned n)
+{
+ return (btrie_oct_t) -(1U << (8 - n));
+}
+
+/* determine whether two prefixes are equal */
+static inline int prefixes_equal(const btrie_oct_t *pfx1,
+ const btrie_oct_t *pfx2, unsigned len)
+{
+ return (memcmp (pfx1, pfx2, len / 8) == 0
+ && ((pfx1[len / 8] ^ pfx2[len / 8]) & high_bits (len % 8)) == 0);
+}
+
+/* determine length of longest common subprefix */
+static inline unsigned common_prefix(const btrie_oct_t *pfx1,
+ const btrie_oct_t *pfx2, unsigned len)
+{
+ /* algorithm adapted from
+ * http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogLookup
+ */
+ static btrie_oct_t leading_zeros[] =
+ { 8, 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, };
+ unsigned nb;
+
+ for (nb = 0; nb < len / 8; nb++) {
+ unsigned diff = *pfx1++ ^ *pfx2++;
+ if (diff != 0)
+ return 8 * nb + leading_zeros[diff];
+ }
+ if (len % 8) {
+ unsigned n = leading_zeros[*pfx1 ^ *pfx2];
+ if (n < len % 8)
+ return 8 * nb + n;
+ }
+ return len;
+}
+
+/****************************************************************
+ */
+
+static inline int is_empty_node(const node_t *node)
+{
+ return node->tbm_node.ext_bm == 0 && node->tbm_node.int_bm == 0;
+}
+
+static inline int is_lc_node(const node_t *node)
+{
+ return (node->lc_node.lc_flags & LC_FLAGS_IS_LC) != 0;
+}
+
+static inline int is_tbm_node(const node_t *node)
+{
+ return !is_lc_node (node);
+}
+
+/* is node a TBM node with internal data? */
+static inline int has_data(const node_t *node)
+{
+ return is_tbm_node (node) && node->tbm_node.int_bm != 0;
+}
+
+static inline unsigned base_index(unsigned pfx, unsigned plen)
+{
+ assert(plen < TBM_STRIDE);
+ assert(pfx < (1U << plen));
+ return pfx | (1U << plen);
+}
+
+/* initialize node to an empty TBM node */
+static inline void init_empty_node(struct btrie *btrie, node_t *node)
+{
+ memset(node, 0, sizeof(*node));
+ btrie->n_tbm_nodes++;
+}
+
+/* get pointer to TBM internal prefix data */
+static inline const void **
+tbm_data_p(const struct tbm_node *node, unsigned pfx, unsigned plen)
+{
+ unsigned bi = base_index (pfx, plen);
+
+ if ((node->int_bm & bit (bi)) == 0)
+ return NULL; /* no data */
+ else {
+ return &node->ptr.data_end[-(int) count_bits_from (node->int_bm, bi)];
+ }
+}
+
+/* add an element to the internal data array */
+static void tbm_insert_data(struct btrie *btrie, struct tbm_node *node,
+ unsigned pfx, unsigned plen, const void *data)
+{
+ /* XXX: don't realloc if already big enough? */
+ unsigned bi = base_index (pfx, plen);
+ unsigned nchildren = count_bits (node->ext_bm);
+ int ndata = count_bits (node->int_bm);
+ unsigned di = count_bits_before (node->int_bm, bi);
+ node_t *old_children = node->ptr.children;
+ const void **old_data_beg = node->ptr.data_end - ndata;
+ const void **data_beg;
+
+ assert((node->int_bm & bit (bi)) == 0);
+
+ node->ptr.children = alloc_nodes (btrie, nchildren, ndata + 1);
+ data_beg = node->ptr.data_end - (ndata + 1);
+ data_beg[di] = data;
+ node->int_bm |= bit (bi);
+
+ if (nchildren != 0 || ndata != 0) {
+ memcpy(data_beg, old_data_beg, di * sizeof(data_beg[0]));
+ memcpy(&data_beg[di + 1], &old_data_beg[di],
+ (ndata - di) * sizeof(data_beg[0])
+ + nchildren * sizeof(node_t));
+ free_nodes (btrie, old_children, nchildren, ndata);
+ }
+}
+
+/* determine whether TBM has internal prefix data for pfx/plen or ancestors */
+static inline int has_internal_data(const struct tbm_node *node, unsigned pfx,
+ unsigned plen)
+{
+# define BIT(n) (1U << ((1 << TBM_STRIDE) - 1 - (n)))
+# define B0() BIT(1) /* the bit for 0/0 */
+# define B1(n) (BIT((n) + 2) | B0()) /* the bits for n/1 and its ancestors */
+# define B2(n) (BIT((n) + 4) | B1(n >> 1)) /* the bits for n/2 and ancestors */
+# define B3(n) (BIT((n) + 8) | B2(n >> 1)) /* the bits for n/3 and ancestors */
+# define B4(n) (BIT((n) + 16) | B3(n >> 1)) /* the bits for n/4 and ancestors */
+
+ static tbm_bitmap_t ancestors[] =
+ { 0, B0(), B1(0), B1(1), B2(0), B2(1), B2(2), B2(3), B3(0), B3(1), B3(2),
+ B3(3), B3(4), B3(5), B3(6), B3(7),
+# if TBM_STRIDE == 5
+ B4(0), B4(1), B4(2), B4(3), B4(4), B4(5), B4(6), B4(7), B4(8), B4(
+ 9), B4(10), B4(11), B4(12), B4(13), B4(14), B4(15),
+# elif TBM_STRIDE != 4
+# error "unsupported TBM_STRIDE"
+# endif
+ };
+# undef B4
+# undef B3
+# undef B2
+# undef B1
+# undef B0
+# undef BIT
+
+ return (node->int_bm & ancestors[base_index (pfx, plen)]) != 0;
+}
+
+/* get pointer to TBM extending path */
+static inline node_t *
+tbm_ext_path(const struct tbm_node *node, unsigned pfx)
+{
+ if ((node->ext_bm & bit (pfx)) == 0)
+ return NULL;
+ else
+ return &node->ptr.children[count_bits_before (node->ext_bm, pfx)];
+}
+
+/* resize TBM node child array to make space for new child node */
+static node_t *
+tbm_insert_ext_path(struct btrie *btrie, struct tbm_node *node, unsigned pfx)
+{
+ unsigned nchildren = count_bits (node->ext_bm);
+ unsigned ci = count_bits_before (node->ext_bm, pfx);
+ int ndata = count_bits (node->int_bm);
+ node_t *old_children = node->ptr.children;
+ const void **old_data_beg = node->ptr.data_end - ndata;
+
+ assert((node->ext_bm & bit (pfx)) == 0);
+
+ node->ptr.children = alloc_nodes (btrie, nchildren + 1, ndata);
+ init_empty_node (btrie, &node->ptr.children[ci]);
+ node->ext_bm |= bit (pfx);
+
+ if (nchildren != 0 || ndata != 0) {
+ const void **data_beg = node->ptr.data_end - ndata;
+ memcpy(data_beg, old_data_beg,
+ ndata * sizeof(data_beg[0]) + ci * sizeof(node_t));
+ memcpy(&node->ptr.children[ci + 1], &old_children[ci],
+ (nchildren - ci) * sizeof(old_children[0]));
+ free_nodes (btrie, old_children, nchildren, ndata);
+ }
+
+ return &node->ptr.children[ci];
+}
+
+static inline int lc_is_terminal(const struct lc_node *node)
+{
+ return (node->lc_flags & LC_FLAGS_IS_TERMINAL) != 0;
+}
+
+static inline unsigned lc_len(const struct lc_node *node)
+{
+ return node->lc_flags & LC_FLAGS_LEN_MASK;
+}
+
+static inline void lc_init_flags(struct lc_node *node, int is_terminal,
+ unsigned len)
+{
+ assert((len & ~LC_FLAGS_LEN_MASK) == 0);
+ node->lc_flags = LC_FLAGS_IS_LC | len;
+ if (is_terminal)
+ node->lc_flags |= LC_FLAGS_IS_TERMINAL;
+}
+
+static inline void lc_add_to_len(struct lc_node *node, int increment)
+{
+ unsigned new_len = lc_len (node) + increment;
+ assert((new_len & ~LC_FLAGS_LEN_MASK) == 0);
+ node->lc_flags = (node->lc_flags & ~LC_FLAGS_LEN_MASK) | new_len;
+}
+
+static inline unsigned lc_shift(unsigned pos)
+{
+ return pos / 8;
+}
+
+static inline unsigned lc_base(unsigned pos)
+{
+ return 8 * lc_shift (pos);
+}
+
+static inline unsigned lc_bits(const struct lc_node *node, unsigned pos)
+{
+ return pos % 8 + lc_len (node);
+}
+
+static inline unsigned lc_bytes(const struct lc_node *node, unsigned pos)
+{
+ return (lc_bits (node, pos) + 7) / 8;
+}
+
+static inline unsigned lc_leading_bits(const struct lc_node *node, unsigned pos,
+ unsigned nbits)
+{
+ return extract_bits (node->prefix, pos % 8, nbits);
+}
+
+/* Initialize a new terminal LC node
+ *
+ * If prefix is too long to fit in a single LC node, then a chain
+ * of LC nodes will be created.
+ */
+static void init_terminal_node(struct btrie *btrie, node_t *dst, unsigned pos,
+ const btrie_oct_t *prefix, unsigned len, const void *data)
+{
+ struct lc_node *node = &dst->lc_node;
+ unsigned nbytes = (len + 7) / 8;
+
+ while (nbytes - lc_shift (pos) > LC_BYTES_PER_NODE) {
+ memcpy(node->prefix, prefix + lc_shift (pos), LC_BYTES_PER_NODE);
+ lc_init_flags (node, 0, 8 * LC_BYTES_PER_NODE - pos % 8);
+ node->ptr.child = alloc_nodes (btrie, 1, 0);
+ pos += lc_len (node);
+ node = &node->ptr.child->lc_node;
+ btrie->n_lc_nodes++;
+ }
+
+ memcpy(node->prefix, prefix + lc_shift (pos), nbytes - lc_shift (pos));
+ lc_init_flags (node, 1, len - pos);
+ node->ptr.data = data;
+ btrie->n_lc_nodes++;
+}
+
+/* merge chains of multiple LC nodes into a single LC node, if possible.
+ *
+ * also ensure that the leading nodes in the LC chain have maximum length.
+ */
+static void coalesce_lc_node(struct btrie *btrie, struct lc_node *node,
+ unsigned pos)
+{
+ while (!lc_is_terminal (node) && lc_bits (node, pos) < 8 * LC_BYTES_PER_NODE
+ && is_lc_node (node->ptr.child)) {
+ struct lc_node *child = &node->ptr.child->lc_node;
+ unsigned spare_bits = 8 * LC_BYTES_PER_NODE - lc_bits (node, pos);
+ unsigned end = pos + lc_len (node);
+ unsigned shift = lc_shift (end) - lc_shift (pos);
+ if (lc_len (child) <= spare_bits) {
+ /* node plus child will fit in single node - merge */
+ memcpy(node->prefix + shift, child->prefix, lc_bytes (child, end));
+ lc_init_flags (node, lc_is_terminal (child),
+ lc_len (node) + lc_len (child));
+ node->ptr = child->ptr;
+ free_nodes (btrie, (node_t *) child, 1, 0);
+ btrie->n_lc_nodes--;
+ }
+ else {
+ /* can't merge, but can take some of childs bits */
+ unsigned cshift = lc_shift (end + spare_bits) - lc_shift (end);
+
+ memcpy(node->prefix + shift, child->prefix,
+ LC_BYTES_PER_NODE - shift);
+ lc_add_to_len (node, spare_bits);
+ if (cshift)
+ memmove(child->prefix, child->prefix + cshift,
+ lc_bytes (child, end) - cshift);
+ assert(lc_len (child) > spare_bits);
+ lc_add_to_len (child, -spare_bits);
+
+ pos += lc_len (node);
+ node = child;
+ }
+ }
+}
+
+static void init_tbm_node(struct btrie *btrie, node_t *node, unsigned pos,
+ const btrie_oct_t pbyte, const void **root_data_p, node_t *left,
+ node_t *right);
+
+/* given an LC node at orig_pos, create a new (shorter) node at pos */
+static void shorten_lc_node(struct btrie *btrie, node_t *dst, unsigned pos,
+ struct lc_node *src, unsigned orig_pos)
+{
+ assert(orig_pos < pos);
+ assert(lc_len (src) >= pos - orig_pos);
+ assert(dst != (node_t * )src);
+
+ if (lc_len (src) == pos - orig_pos && !lc_is_terminal (src)) {
+ /* just steal the child */
+ node_t *child = src->ptr.child;
+ *dst = *child;
+ free_nodes (btrie, child, 1, 0);
+ btrie->n_lc_nodes--;
+ }
+ else {
+ struct lc_node *node = &dst->lc_node;
+ unsigned shift = lc_shift (pos) - lc_shift (orig_pos);
+ if (shift) {
+ memmove(node->prefix, src->prefix + shift,
+ lc_bytes (src, orig_pos) - shift);
+ node->lc_flags = src->lc_flags;
+ node->ptr = src->ptr;
+ }
+ else {
+ *node = *src;
+ }
+ lc_add_to_len (node, -(pos - orig_pos));
+ coalesce_lc_node (btrie, node, pos);
+ }
+}
+
+/* convert LC node to non-terminal LC node of length len *in place*
+ *
+ * on entry, node must have length at least len
+ */
+static void split_lc_node(struct btrie *btrie, struct lc_node *node,
+ unsigned pos, unsigned len)
+{
+ node_t *child = alloc_nodes (btrie, 1, 0);
+
+ assert(lc_len (node) >= len);
+ shorten_lc_node (btrie, child, pos + len, node, pos);
+
+ lc_init_flags (node, 0, len);
+ node->ptr.child = child;
+ btrie->n_lc_nodes++;
+}
+
+/* convert non-terminal LC node of length one to a TBM node *in place* */
+static void convert_lc_node_1(struct btrie *btrie, struct lc_node *node,
+ unsigned pos)
+{
+ btrie_oct_t pbyte = node->prefix[0];
+ node_t *child = node->ptr.child;
+ node_t *left, *right;
+
+ assert(lc_len (node) == 1);
+ assert(!lc_is_terminal (node));
+
+ if (extract_bit (node->prefix, pos % 8))
+ left = NULL, right = child;
+ else
+ left = child, right = NULL;
+ init_tbm_node (btrie, (node_t *) node, pos, pbyte, NULL, left, right);
+ free_nodes (btrie, child, 1, 0);
+ btrie->n_lc_nodes--;
+}
+
+/* convert an LC node to TBM node *in place* */
+static void convert_lc_node(struct btrie *btrie, struct lc_node *node,
+ unsigned pos)
+{
+ unsigned len = lc_len (node);
+
+ if (len >= TBM_STRIDE) {
+ unsigned pfx = lc_leading_bits (node, pos, TBM_STRIDE);
+ struct tbm_node *result = (struct tbm_node *) node;
+
+ /* split to LC of len TBM_STRIDE followed by child (extending path) */
+ split_lc_node (btrie, node, pos, TBM_STRIDE);
+ /* then convert leading LC node to TBM node */
+ result->int_bm = 0;
+ result->ext_bm = bit (pfx);
+ btrie->n_lc_nodes--;
+ btrie->n_tbm_nodes++;
+ }
+ else if (lc_is_terminal (node)) {
+ /* convert short terminal LC to TBM (with internal data) */
+ unsigned pfx = lc_leading_bits (node, pos, len);
+ const void *data = node->ptr.data;
+ node_t *result = (node_t *) node;
+
+ init_empty_node (btrie, result);
+ tbm_insert_data (btrie, &result->tbm_node, pfx, len, data);
+
+ btrie->n_lc_nodes--;
+ }
+ else {
+ assert(len > 0);
+ for (; len > 1; len--) {
+ split_lc_node (btrie, node, pos, len - 1);
+ convert_lc_node_1 (btrie, &node->ptr.child->lc_node, pos + len - 1);
+ }
+ convert_lc_node_1 (btrie, node, pos);
+ }
+}
+
+static void insert_lc_node(struct btrie *btrie, node_t *dst, unsigned pos,
+ btrie_oct_t pbyte, unsigned last_bit, node_t *tail)
+{
+ struct lc_node *node = &dst->lc_node;
+ btrie_oct_t mask = 1 << (7 - (pos % 8));
+ btrie_oct_t bit = last_bit ? mask : 0;
+
+ if (mask != 0x01 && is_lc_node (tail)) {
+ /* optimization: LC tail has room for the extra bit (without shifting) */
+ assert((tail->lc_node.prefix[0] & mask) == bit);
+ *node = tail->lc_node;
+ lc_add_to_len (node, 1);
+ return;
+ }
+
+ /* add new leading LC node of len 1 */
+ node->prefix[0] = pbyte | bit;
+ lc_init_flags (node, 0, 1);
+ node->ptr.child = alloc_nodes (btrie, 1, 0);
+ node->ptr.child[0] = *tail;
+ btrie->n_lc_nodes++;
+
+ if (is_lc_node (tail))
+ coalesce_lc_node (btrie, node, pos);
+}
+
+/* given:
+ * pbyte: the bits in the prefix between lc_base(pos) and pos
+ * pfx: the next TBM_STRIDE bits in the prefix starting at pos
+ * returns:
+ * the bits in the prefix between lc_base(pos + plen) and pos + plen
+ */
+static inline btrie_oct_t next_pbyte(btrie_oct_t pbyte, unsigned pos,
+ unsigned pfx)
+{
+ unsigned end = pos + TBM_STRIDE;
+
+ if (end % 8 != 0) {
+ btrie_oct_t nbyte = (btrie_oct_t) pfx << (8 - end % 8);
+ if (end % 8 > TBM_STRIDE)
+ nbyte |= pbyte & high_bits (pos % 8);
+ return nbyte;
+ }
+ return 0;
+}
+
+/* construct a new TBM node, given the data and children of the
+ * root prefix of the new node.
+ */
+static void init_tbm_node(struct btrie *btrie, node_t *dst, unsigned pos,
+ const btrie_oct_t pbyte, const void **root_data_p, node_t *left,
+ node_t *right)
+{
+ struct tbm_node *node = &dst->tbm_node;
+ unsigned nchildren = 0;
+ unsigned ndata = 0;
+ node_t children[TBM_FANOUT];
+ const void *data[TBM_FANOUT - 1];
+ tbm_bitmap_t ext_bm = 0;
+ tbm_bitmap_t int_bm = 0;
+ unsigned i, d, pfx_base;
+
+ if (left && is_lc_node (left) && lc_len (&left->lc_node) < TBM_STRIDE)
+ convert_lc_node (btrie, &left->lc_node, pos + 1);
+ if (right && is_lc_node (right) && lc_len (&right->lc_node) < TBM_STRIDE)
+ convert_lc_node (btrie, &right->lc_node, pos + 1);
+
+ /* set internal data for root prefix */
+ if (root_data_p) {
+ data[ndata++] = *root_data_p;
+ int_bm |= bit (base_index (0, 0));
+ }
+ /* copy internal data from children */
+ for (d = 0; d < TBM_STRIDE - 1; d++) {
+ if (left && has_data (left)) {
+ for (i = 0; i < 1U << d; i++) {
+ const void **data_p = tbm_data_p (&left->tbm_node, i, d);
+ if (data_p) {
+ data[ndata++] = *data_p;
+ int_bm |= bit (base_index (i, d + 1));
+ }
+ }
+ }
+ if (right && has_data (right)) {
+ for (i = 0; i < 1U << d; i++) {
+ const void **data_p = tbm_data_p (&right->tbm_node, i, d);
+ if (data_p) {
+ data[ndata++] = *data_p;
+ int_bm |= bit (base_index (i + (1 << d), d + 1));
+ }
+ }
+ }
+ }
+
+ /* copy extending paths */
+ for (pfx_base = 0; pfx_base < TBM_FANOUT; pfx_base += TBM_FANOUT / 2) {
+ node_t *child = pfx_base ? right : left;
+ if (child == NULL) {
+ continue;
+ }
+ else if (is_lc_node (child)) {
+ unsigned pfx = pfx_base + lc_leading_bits (&child->lc_node, pos + 1,
+ TBM_STRIDE - 1);
+ /* child is LC node, just shorten it by TBM_STRIDE - 1 */
+ shorten_lc_node (btrie, &children[nchildren++], pos + TBM_STRIDE,
+ &child->lc_node, pos + 1);
+ ext_bm |= bit (pfx);
+ }
+ else if (!is_empty_node (child)) {
+ /* convert deepest internal prefixes of child to extending paths
+ * of the new node
+ */
+ for (i = 0; i < TBM_FANOUT / 2; i++) {
+ const void **data_p = tbm_data_p (&child->tbm_node, i,
+ TBM_STRIDE - 1);
+ node_t *left_ext = tbm_ext_path (&child->tbm_node, 2 * i);
+ node_t *right_ext = tbm_ext_path (&child->tbm_node, 2 * i + 1);
+ if (data_p || left_ext || right_ext) {
+ node_t *ext_path = &children[nchildren++];
+ unsigned pfx = pfx_base + i;
+ btrie_oct_t npbyte = next_pbyte (pbyte, pos, pfx);
+
+ ext_bm |= bit (pfx);
+ if (left_ext == NULL && right_ext == NULL) {
+ /* only have data - set ext_path to zero-length terminal LC node */
+ lc_init_flags (&ext_path->lc_node, 1, 0);
+ ext_path->lc_node.prefix[0] = npbyte;
+ ext_path->lc_node.ptr.data = *data_p;
+ btrie->n_lc_nodes++;
+ }
+ else if (data_p || (left_ext && right_ext)) {
+ /* have at least two of data, left_ext, right_ext
+ * ext_path must be a full TBM node */
+ init_tbm_node (btrie, ext_path, pos + TBM_STRIDE,
+ npbyte, data_p, left_ext, right_ext);
+ }
+ else if (left_ext) {
+ /* have only left_ext, insert length-one LC node */
+ insert_lc_node (btrie, ext_path, pos + TBM_STRIDE,
+ npbyte, 0, left_ext);
+ }
+ else {
+ /* have only right_ext, insert length-one LC node */
+ insert_lc_node (btrie, ext_path, pos + TBM_STRIDE,
+ npbyte, 1, right_ext);
+ }
+ }
+ }
+ btrie->n_tbm_nodes--;
+ free_nodes (btrie, child->tbm_node.ptr.children,
+ count_bits (child->tbm_node.ext_bm),
+ count_bits (child->tbm_node.int_bm));
+ }
+ }
+
+ assert(count_bits (int_bm) == ndata);
+ assert(count_bits (ext_bm) == nchildren);
+
+ node->ptr.children = alloc_nodes (btrie, nchildren, ndata);
+ memcpy(node->ptr.data_end - (int )ndata, data, ndata * sizeof(data[0]));
+ memcpy(node->ptr.children, children, nchildren * sizeof(children[0]));
+ node->ext_bm = ext_bm;
+ node->int_bm = int_bm;
+ btrie->n_tbm_nodes++;
+}
+
+static enum btrie_result add_to_trie(struct btrie *btrie, node_t *node,
+ unsigned pos, const btrie_oct_t *prefix, unsigned len, const void *data)
+{
+ for (;;) {
+ if (is_lc_node (node)) {
+ struct lc_node *lc_node = &node->lc_node;
+ unsigned end = pos + lc_len (lc_node);
+ unsigned cbits = common_prefix (prefix + lc_shift (pos),
+ lc_node->prefix, (len < end ? len : end) - lc_base (pos));
+ unsigned clen = lc_base (pos) + cbits; /* position of first mismatch */
+
+ if (clen == end && !lc_is_terminal (lc_node)) {
+ /* matched entire prefix of LC node, proceed to child */
+ assert(lc_len (lc_node) > 0);
+ node = lc_node->ptr.child;
+ pos = end;
+ }
+ else if (clen == end && len == end && lc_is_terminal (lc_node)) {
+ /* exact match for terminal node - already have data for prefix */
+ return BTRIE_DUPLICATE_PREFIX;
+ }
+ else {
+ assert(clen < end || (lc_is_terminal (lc_node) && len > end));
+ /* Need to insert new TBM node at clen */
+ if (clen > pos) {
+ split_lc_node (btrie, lc_node, pos, clen - pos);
+ node = lc_node->ptr.child;
+ assert(is_lc_node (node));
+ pos = clen;
+ }
+ convert_lc_node (btrie, &node->lc_node, pos);
+ }
+ }
+ else if (is_empty_node (node)) {
+ /* at empty TBM node - just replace with terminal LC node */
+ init_terminal_node (btrie, node, pos, prefix, len, data);
+ btrie->n_entries++;
+ btrie->n_tbm_nodes--;
+ return BTRIE_OKAY;
+ }
+ else {
+ struct tbm_node *tbm_node = &node->tbm_node;
+ unsigned end = pos + TBM_STRIDE;
+
+ if (len < end) {
+ unsigned plen = len - pos;
+ unsigned pfx = extract_bits (prefix, pos, plen);
+
+ if (tbm_data_p (tbm_node, pfx, plen) != NULL)
+ return BTRIE_DUPLICATE_PREFIX; /* prefix already has data */
+ else {
+ tbm_insert_data (btrie, tbm_node, pfx, plen, data);
+ btrie->n_entries++;
+ return BTRIE_OKAY;
+ }
+ }
+ else {
+ unsigned pfx = extract_bits (prefix, pos, TBM_STRIDE);
+
+ /* follow extending path */
+ node = tbm_ext_path (tbm_node, pfx);
+ if (node == NULL)
+ node = tbm_insert_ext_path (btrie, tbm_node, pfx);
+ pos = end;
+ }
+ }
+ }
+}
+
+static const void *
+search_trie(const node_t *node, unsigned pos, const btrie_oct_t *prefix,
+ unsigned len)
+{
+ /* remember last TBM node seen with internal data */
+ const struct tbm_node *int_node = 0;
+ unsigned int_pfx = 0, int_plen = 0;
+
+ while (node) {
+ if (is_lc_node (node)) {
+ const struct lc_node *lc_node = &node->lc_node;
+ unsigned end = pos + lc_len (lc_node);
+ if (len < end)
+ break;
+ if (!prefixes_equal (prefix + lc_shift (pos), lc_node->prefix,
+ end - lc_base (pos)))
+ break;
+
+ if (lc_is_terminal (lc_node))
+ return lc_node->ptr.data; /* found terminal node */
+
+ pos = end;
+ node = lc_node->ptr.child;
+ }
+ else {
+ const struct tbm_node *tbm_node = &node->tbm_node;
+ unsigned end = pos + TBM_STRIDE;
+ if (len < end) {
+ unsigned plen = len - pos;
+ unsigned pfx = extract_bits (prefix, pos, plen);
+ if (has_internal_data (tbm_node, pfx, plen)) {
+ int_node = tbm_node;
+ int_pfx = pfx;
+ int_plen = plen;
+ }
+ break;
+ }
+ else {
+ unsigned pfx = extract_bits (prefix, pos, TBM_STRIDE);
+ if (has_internal_data (tbm_node, pfx >> 1, TBM_STRIDE - 1)) {
+ int_node = tbm_node;
+ int_pfx = pfx >> 1;
+ int_plen = TBM_STRIDE - 1;
+ }
+ pos = end;
+ node = tbm_ext_path (tbm_node, pfx);
+ }
+ }
+ }
+
+ if (int_node) {
+ const void **data_p = tbm_data_p (int_node, int_pfx, int_plen);
+ while (data_p == NULL) {
+ assert(int_plen > 0);
+ int_pfx >>= 1;
+ int_plen--;
+ data_p = tbm_data_p (int_node, int_pfx, int_plen);
+ }
+ return *data_p;
+ }
+
+ return NULL;
+}
+
+struct btrie *
+btrie_init(rspamd_mempool_t *mp)
+{
+ struct btrie *btrie;
+
+ if (!(btrie = rspamd_mempool_alloc (mp, sizeof(*btrie))))
+ return NULL;
+ memset(btrie, 0, sizeof(*btrie));
+ btrie->mp = mp;
+ btrie->alloc_total = sizeof(*btrie);
+
+ /* count the empty root node */
+ btrie->n_tbm_nodes = 1;
+
+ return btrie;
+}
+
+enum btrie_result btrie_add_prefix(struct btrie *btrie,
+ const btrie_oct_t *prefix, unsigned len, const void *data)
+{
+ enum btrie_result rv;
+ if ((rv = setjmp (btrie->exception)) != 0)
+ return rv; /* out of memory */
+
+ return add_to_trie (btrie, &btrie->root, 0, prefix, len, data);
+}
+
+const void *
+btrie_lookup(const struct btrie *btrie, const btrie_oct_t *prefix, unsigned len)
+{
+ return search_trie (&btrie->root, 0, prefix, len);
+}
+
+/****************************************************************
+ *
+ * btrie_stats() - statistics reporting
+ */
+
+#ifdef BTRIE_EXTENDED_STATS
+
+/* Define BTRIE_EXTENDED_STATS to get extra statistics (including
+ * trie depth). This statistics require a traversal of the entire trie
+ * to compute, and so are disabled by default.
+ */
+
+struct stats {
+ size_t max_depth;
+ size_t total_depth;
+#ifndef NDEBUG
+ size_t n_lc_nodes;
+ size_t n_tbm_nodes;
+ size_t n_entries;
+ size_t alloc_data;
+ size_t alloc_waste;
+#endif
+};
+
+static void
+node_stats(const node_t *node, size_t depth, struct stats *stats)
+{
+ if (depth > stats->max_depth)
+ stats->max_depth = depth;
+ stats->total_depth += depth;
+
+ if (is_lc_node(node)) {
+#ifndef NDEBUG
+ stats->n_lc_nodes++;
+#endif
+ if (!lc_is_terminal(&node->lc_node))
+ node_stats(node->lc_node.ptr.child, depth + 1, stats);
+#ifndef NDEBUG
+ else
+ stats->n_entries++;
+#endif
+ }
+ else {
+ unsigned i;
+ unsigned nchildren = count_bits(node->tbm_node.ext_bm);
+#ifndef NDEBUG
+ unsigned ndata = count_bits(node->tbm_node.int_bm);
+
+ stats->n_tbm_nodes++;
+ stats->n_entries += ndata;
+ stats->alloc_data += ndata * sizeof(void *);
+ stats->alloc_waste += (ndata % 2) * sizeof(void *);
+#endif
+ for (i = 0; i < nchildren; i++)
+ node_stats(&node->tbm_node.ptr.children[i], depth + 1, stats);
+ }
+}
+#endif /* BTRIE_EXTENDED_STATS */
+
+#ifndef NDEBUG
+static size_t count_free(const struct btrie *btrie)
+{
+ size_t total = 0;
+ unsigned sz;
+ for (sz = 1; sz <= MAX_CHILD_ARRAY_LEN; sz++) {
+ const struct free_hunk *free = btrie->free_list[sz - 1];
+ size_t n;
+ for (n = 0; free; n++)
+ free = free->next;
+ total += sz * n;
+ }
+ return total * sizeof(node_t);
+}
+#endif /* not NDEBUG */
+
+const char *
+btrie_stats(const struct btrie *btrie)
+{
+ static char buf[128];
+ size_t n_nodes = btrie->n_lc_nodes + btrie->n_tbm_nodes;
+ size_t alloc_free = (btrie->alloc_total + sizeof(node_t) /* do not double-count the root node */
+ - n_nodes * sizeof(node_t) - btrie->alloc_data - btrie->alloc_waste
+ - sizeof(*btrie));
+#ifdef BTRIE_EXTENDED_STATS
+ struct stats stats;
+ double average_depth;
+
+ memset(&stats, 0, sizeof(stats));
+ node_stats(&btrie->root, 0, &stats);
+ average_depth = (double)stats.total_depth / n_nodes;
+
+#ifndef NDEBUG
+ /* check the node counts */
+ assert(stats.n_lc_nodes == btrie->n_lc_nodes);
+ assert(stats.n_tbm_nodes == btrie->n_tbm_nodes);
+ assert(stats.n_entries == btrie->n_entries);
+ assert(stats.alloc_data == btrie->alloc_data);
+ assert(stats.alloc_waste == btrie->alloc_waste);
+#endif /* not NDEBUG */
+#endif /* BTRIE_EXTENDED_STATS */
+
+#ifndef NDEBUG
+ /* check that we haven't lost any memory */
+ assert(alloc_free == count_free (btrie));
+#endif
+
+#ifdef BTRIE_DEBUG_ALLOC
+ dump_alloc_hist(btrie);
+#endif
+
+
+#ifdef BTRIE_EXTENDED_STATS
+ snprintf(buf, sizeof(buf),
+ "ents=%lu tbm=%lu lc=%lu mem=%.0fk free=%lu waste=%lu"
+ " depth=%.1f/%lu"
+ ,(long unsigned)btrie->n_entries, (long unsigned)btrie->n_tbm_nodes,
+ (long unsigned)btrie->n_lc_nodes, (double)btrie->alloc_total / 1024,
+ (long unsigned)alloc_free, (long unsigned)btrie->alloc_waste
+ , average_depth, (long unsigned)stats.max_depth);
+#else
+ snprintf(buf, sizeof(buf),
+ "ents=%lu tbm=%lu lc=%lu mem=%.0fk free=%lu waste=%lu"
+ ,(long unsigned)btrie->n_entries, (long unsigned)btrie->n_tbm_nodes,
+ (long unsigned)btrie->n_lc_nodes, (double)btrie->alloc_total / 1024,
+ (long unsigned)alloc_free, (long unsigned)btrie->alloc_waste
+ );
+#endif
+ buf[sizeof(buf) - 1] = '\0';
+ return buf;
+}
+
+/****************************************************************/
+
+#ifndef NO_MASTER_DUMP
+
+struct walk_context
+{
+ btrie_walk_cb_t *callback;
+ void *user_data;
+
+ btrie_oct_t prefix[(BTRIE_MAX_PREFIX + 7) / 8];
+};
+
+static void
+walk_node(const node_t *node, unsigned pos, struct walk_context *ctx);
+
+static void walk_tbm_node(const struct tbm_node *node, unsigned pos,
+ unsigned pfx, unsigned plen, struct walk_context *ctx)
+{
+ btrie_oct_t *prefix = ctx->prefix;
+ int pbyte = pos / 8;
+ btrie_oct_t pbit = 0x80 >> (pos % 8);
+ const void **data_p = tbm_data_p (node, pfx, plen);
+
+ if (pos >= BTRIE_MAX_PREFIX) {
+ /* This can/should not happen, but don't overwrite buffers if it does. */
+ return;
+ }
+
+ if (data_p)
+ ctx->callback (prefix, pos, *data_p, 0, ctx->user_data);
+
+ /* walk children */
+ if (plen < TBM_STRIDE - 1) {
+ /* children are internal prefixes in same node */
+ walk_tbm_node (node, pos + 1, pfx << 1, plen + 1, ctx);
+ prefix[pbyte] |= pbit;
+ walk_tbm_node (node, pos + 1, (pfx << 1) + 1, plen + 1, ctx);
+ prefix[pbyte] &= ~pbit;
+ }
+ else {
+ /* children are extending paths */
+ const node_t *ext_path;
+ if ((ext_path = tbm_ext_path (node, pfx << 1)) != NULL)
+ walk_node (ext_path, pos + 1, ctx);
+ if ((ext_path = tbm_ext_path (node, (pfx << 1) + 1)) != NULL) {
+ prefix[pbyte] |= pbit;
+ walk_node (ext_path, pos + 1, ctx);
+ prefix[pbyte] &= ~pbit;
+ }
+ }
+
+ if (data_p)
+ ctx->callback (prefix, pos, *data_p, 1, ctx->user_data);
+}
+
+static void walk_lc_node(const struct lc_node *node, unsigned pos,
+ struct walk_context *ctx)
+{
+ btrie_oct_t *prefix = ctx->prefix;
+ unsigned end = pos + lc_len (node);
+ btrie_oct_t save_prefix = prefix[lc_shift (pos)];
+
+ if (end > BTRIE_MAX_PREFIX) {
+ /* This can/should not happen, but don't overwrite buffers if it does. */
+ return;
+ }
+
+ /* construct full prefix to node */
+ memcpy(&prefix[lc_shift (pos)], node->prefix, lc_bytes (node, pos));
+ if (end % 8)
+ prefix[end / 8] &= high_bits (end % 8);
+
+ if (lc_is_terminal (node)) {
+ ctx->callback (prefix, end, node->ptr.data, 0, ctx->user_data);
+ ctx->callback (prefix, end, node->ptr.data, 1, ctx->user_data);
+ }
+ else
+ walk_node (node->ptr.child, end, ctx);
+
+ prefix[lc_shift (pos)] = save_prefix; /* restore parents prefix */
+ if (lc_bytes (node, pos) > 1)
+ memset(&prefix[lc_shift (pos) + 1], 0, lc_bytes (node, pos) - 1);
+}
+
+static void walk_node(const node_t *node, unsigned pos,
+ struct walk_context *ctx)
+{
+ if (is_lc_node (node))
+ walk_lc_node (&node->lc_node, pos, ctx);
+ else
+ walk_tbm_node (&node->tbm_node, pos, 0, 0, ctx);
+}
+
+/* walk trie in lexicographical order
+ *
+ * calls callback twice (once preorder, once postorder) at each prefix
+ */
+void btrie_walk(const struct btrie *btrie, btrie_walk_cb_t *callback,
+ void *user_data)
+{
+ struct walk_context ctx;
+
+ memset(&ctx, 0, sizeof(ctx));
+ ctx.callback = callback;
+ ctx.user_data = user_data;
+
+ walk_node (&btrie->root, 0, &ctx);
+}
+
+#endif /* not NO_MASTER_DUMP */
+
+
+#ifdef TEST
+/*****************************************************************
+ *
+ * Unit tests
+ *
+ */
+#include <stdio.h>
+
+#ifndef UNUSED
+# define UNUSED __attribute__((unused))
+#endif
+
+/* bogus replacements mp_alloc for running self-tests */
+void *
+mp_alloc(UNUSED struct mempool *mp, unsigned sz, UNUSED int align)
+{
+ return malloc(sz);
+}
+
+#if 0
+# define PASS(name) puts("OK " name)
+#else
+# define PASS(name) fputs(".", stdout); fflush(stdout)
+#endif
+
+const char * pgm_name = "???";
+
+static void
+test_struct_node_packing()
+{
+ node_t node;
+
+ assert(sizeof(struct tbm_node) == 2 * sizeof(void *));
+ assert(sizeof(struct lc_node) == 2 * sizeof(void *));
+ assert(sizeof(node_t) == 2 * sizeof(void *));
+
+ /* The lc_node bit must be an alias for bit zero of int_bm, since
+ * that is the only unused bit in the TBM node structure.
+ */
+ memset(&node, 0, sizeof(node));
+ assert(node.tbm_node.int_bm == 0);
+ lc_init_flags(&node.lc_node, 0, 0);
+ assert(node.tbm_node.int_bm == bit(0));
+
+ PASS("test_struct_node_packing");
+}
+
+static void
+test_bit()
+{
+ tbm_bitmap_t ones = ~(tbm_bitmap_t)0;
+ tbm_bitmap_t high_bit = ones ^ (ones >> 1);
+
+ assert(bit(0) == high_bit);
+ assert(bit(1) == high_bit >> 1);
+ assert(bit(8 * sizeof(tbm_bitmap_t) - 1) == 1);
+ PASS("test_bit");
+}
+
+static void
+test_count_bits()
+{
+ unsigned max_bits = sizeof(tbm_bitmap_t) * 8;
+ tbm_bitmap_t ones = ~(tbm_bitmap_t)0;
+
+ assert(count_bits(0) == 0);
+ assert(count_bits(1) == 1);
+ assert(count_bits(2) == 1);
+ assert(count_bits(3) == 2);
+ assert(count_bits(ones) == max_bits);
+ assert(count_bits(~1) == max_bits - 1);
+
+ /* count_bits(0x5555....) */
+ assert(count_bits(ones / 3) == max_bits / 2);
+ /* count_bits(0x3333...) */
+ assert(count_bits(ones / 5) == max_bits / 2);
+ /* count_bits(0x0f0f...) */
+ assert(count_bits(ones / 17) == max_bits / 2);
+ /* count_bits(0x1010...) */
+ assert(count_bits(ones / 255) == max_bits / 8);
+
+ PASS("test_count_bits");
+}
+
+static void
+test_count_bits_before()
+{
+ unsigned max_bits = sizeof(tbm_bitmap_t) * 8;
+ tbm_bitmap_t ones = ~(tbm_bitmap_t)0;
+ unsigned i;
+
+ for (i = 0; i < max_bits; i++) {
+ assert(count_bits_before(0, i) == 0);
+ assert(count_bits_before(ones, i) == i);
+ }
+
+ PASS("test_count_bits_before");
+}
+
+static void
+test_count_bits_from()
+{
+ unsigned max_bits = sizeof(tbm_bitmap_t) * 8;
+ tbm_bitmap_t ones = ~(tbm_bitmap_t)0;
+ unsigned i;
+
+ for (i = 0; i < max_bits; i++) {
+ assert(count_bits_from(0, i) == 0);
+ assert(count_bits_from(ones, i) == max_bits - i);
+ }
+
+ PASS("test_count_bits_from");
+}
+
+static void
+test_extract_bits()
+{
+ static btrie_oct_t prefix[] = {0xff, 0x55, 0xaa, 0x00};
+ unsigned i;
+
+ for (i = 0; i < 32; i++)
+ assert(extract_bits(prefix, i, 0) == 0);
+
+ for (i = 0; i < 8; i++)
+ assert(extract_bits(prefix, i, 1) == 1);
+ for (i = 8; i < 16; i++)
+ assert(extract_bits(prefix, i, 1) == i % 2);
+ for (i = 16; i < 24; i++)
+ assert(extract_bits(prefix, i, 1) == (i + 1) % 2);
+ for (i = 24; i < 32; i++)
+ assert(extract_bits(prefix, i, 1) == 0);
+
+ assert(extract_bits(prefix, 2, 6) == 0x3f);
+ assert(extract_bits(prefix, 3, 6) == 0x3e);
+ assert(extract_bits(prefix, 4, 6) == 0x3d);
+ assert(extract_bits(prefix, 5, 6) == 0x3a);
+ assert(extract_bits(prefix, 6, 6) == 0x35);
+ assert(extract_bits(prefix, 7, 6) == 0x2a);
+ assert(extract_bits(prefix, 8, 6) == 0x15);
+
+ PASS("test_extract_bits");
+}
+
+static void
+test_high_bits()
+{
+ assert(high_bits(0) == 0x00);
+ assert(high_bits(1) == 0x80);
+ assert(high_bits(2) == 0xc0);
+ assert(high_bits(3) == 0xe0);
+ assert(high_bits(4) == 0xf0);
+ assert(high_bits(5) == 0xf8);
+ assert(high_bits(6) == 0xfc);
+ assert(high_bits(7) == 0xfe);
+ assert(high_bits(8) == 0xff);
+ PASS("test_high_bits");
+}
+
+static void
+test_prefixes_equal()
+{
+ btrie_oct_t prefix1[LC_BYTES_PER_NODE];
+ btrie_oct_t prefix2[LC_BYTES_PER_NODE];
+ unsigned i;
+ memset(prefix1, 0xaa, LC_BYTES_PER_NODE);
+ memset(prefix2, 0xaa, LC_BYTES_PER_NODE);
+
+ for (i = 0; i < 8 * LC_BYTES_PER_NODE; i++) {
+ assert(prefixes_equal(prefix1, prefix2, i));
+ prefix1[i / 8] ^= 1 << (7 - i % 8);
+ assert(!prefixes_equal(prefix1, prefix2, 8 * LC_BYTES_PER_NODE));
+ assert(prefixes_equal(prefix1, prefix2, i));
+ if (i + 1 < 8 * LC_BYTES_PER_NODE)
+ assert(!prefixes_equal(prefix1, prefix2, i + 1));
+ prefix1[i / 8] ^= 1 << (7 - i % 8);
+ }
+ PASS("test_prefixes_equal");
+}
+
+static void
+test_common_prefix()
+{
+ btrie_oct_t prefix1[LC_BYTES_PER_NODE];
+ btrie_oct_t prefix2[LC_BYTES_PER_NODE];
+ unsigned i;
+ memset(prefix1, 0x55, LC_BYTES_PER_NODE);
+ memset(prefix2, 0x55, LC_BYTES_PER_NODE);
+
+ for (i = 0; i < 8 * LC_BYTES_PER_NODE; i++) {
+ assert(common_prefix(prefix1, prefix2, i) == i);
+ prefix1[i / 8] ^= 1 << (7 - i % 8);
+ assert(common_prefix(prefix1, prefix2, 8 * LC_BYTES_PER_NODE) == i);
+ if (i + 1 < 8 * LC_BYTES_PER_NODE)
+ assert(common_prefix(prefix1, prefix2, i+1) == i);
+ prefix1[i / 8] ^= 1 << (7 - i % 8);
+ }
+ PASS("test_common_prefix");
+}
+
+static void
+test_base_index()
+{
+ assert(base_index(0,0) == 1);
+ assert(base_index(0,1) == 2);
+ assert(base_index(1,1) == 3);
+ assert(base_index(0,2) == 4);
+ assert(base_index(1,2) == 5);
+ assert(base_index(2,2) == 6);
+ assert(base_index(3,2) == 7);
+ PASS("test_base_index");
+}
+
+static void
+test_has_internal_data()
+{
+ struct tbm_node node;
+ unsigned plen, pfx, bi;
+ for (plen = 0; plen < TBM_STRIDE; plen++) {
+ for (pfx = 0; pfx < 1U << plen; pfx++) {
+ tbm_bitmap_t ancestor_mask = 0;
+ for (bi = base_index(pfx, plen); bi; bi >>= 1) {
+ node.int_bm = bit(bi);
+ ancestor_mask |= bit(bi);
+ assert(has_internal_data(&node, pfx, plen));
+ }
+ node.int_bm = ~ancestor_mask;
+ assert(!has_internal_data(&node, pfx, plen));
+ }
+ }
+ PASS("test_has_internal_data");
+}
+
+/****************************************************************/
+static const btrie_oct_t numbered_bytes[] = {
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+ 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
+ 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
+ 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
+ 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
+ 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
+};
+
+static void
+check_non_terminal_lc_node(struct lc_node *node, unsigned len)
+{
+ assert(is_lc_node((node_t *)node));
+ assert(!lc_is_terminal(node));
+ assert(lc_len(node) == len);
+}
+
+static void
+check_terminal_lc_node(struct lc_node *node, unsigned len, const void *data)
+{
+ assert(is_lc_node((node_t *)node));
+ assert(lc_is_terminal(node));
+ assert(lc_len(node) == len);
+ assert(node->ptr.data == data);
+}
+
+static void
+test_init_terminal_node()
+{
+ struct btrie *btrie = btrie_init(NULL);
+ const void *data = (void *)0xdeadbeef;
+ node_t node;
+ struct lc_node *head = &node.lc_node;
+
+ init_terminal_node(btrie, &node, 0,
+ numbered_bytes, 8 * LC_BYTES_PER_NODE, data);
+ check_terminal_lc_node(head, 8 * LC_BYTES_PER_NODE, data);
+ assert(memcmp(head->prefix, numbered_bytes, LC_BYTES_PER_NODE) == 0);
+
+ init_terminal_node(btrie, &node, 7,
+ numbered_bytes, 8 * LC_BYTES_PER_NODE, data);
+ check_terminal_lc_node(head, 8 * LC_BYTES_PER_NODE - 7, data);
+ assert(memcmp(head->prefix, numbered_bytes, LC_BYTES_PER_NODE) == 0);
+
+ init_terminal_node(btrie, &node, 0,
+ numbered_bytes, 2 * 8 * LC_BYTES_PER_NODE, data);
+ check_non_terminal_lc_node(head, 8 * LC_BYTES_PER_NODE);
+ assert(memcmp(head->prefix, numbered_bytes, LC_BYTES_PER_NODE) == 0);
+ {
+ struct lc_node *child = &head->ptr.child->lc_node;
+ check_terminal_lc_node(child, 8 * LC_BYTES_PER_NODE, data);
+ assert(memcmp(child->prefix, &numbered_bytes[LC_BYTES_PER_NODE],
+ LC_BYTES_PER_NODE) == 0);
+ }
+
+ init_terminal_node(btrie, &node, 15,
+ numbered_bytes, 8 * LC_BYTES_PER_NODE + 15, data);
+ check_non_terminal_lc_node(head, 8 * LC_BYTES_PER_NODE - 7);
+ assert(memcmp(head->prefix, &numbered_bytes[1], LC_BYTES_PER_NODE) == 0);
+ {
+ struct lc_node *child = &head->ptr.child->lc_node;
+ check_terminal_lc_node(child, 7, data);
+ assert(child->prefix[0] == numbered_bytes[LC_BYTES_PER_NODE + 1]);
+ }
+
+ PASS("test_init_terminal_node");
+}
+
+static void
+test_coalesce_lc_node()
+{
+ struct btrie *btrie = btrie_init(NULL);
+ const void *data = (void *)0xdeadbeef;
+ node_t node;
+ struct lc_node *head = &node.lc_node;
+
+ /* test merging */
+ init_terminal_node(btrie, &node, 0,
+ numbered_bytes, 8 * (LC_BYTES_PER_NODE + 1), data);
+ check_non_terminal_lc_node(head, LC_BYTES_PER_NODE * 8);
+ lc_add_to_len(head, -8);
+ coalesce_lc_node(btrie, head, 8);
+ check_terminal_lc_node(head, LC_BYTES_PER_NODE * 8, data);
+ assert(head->prefix[LC_BYTES_PER_NODE - 1]
+ == numbered_bytes[LC_BYTES_PER_NODE]);
+
+ /* test bit stealing */
+ init_terminal_node(btrie, &node, 0,
+ numbered_bytes, 8 * (2 * LC_BYTES_PER_NODE), data);
+ check_non_terminal_lc_node(head, LC_BYTES_PER_NODE * 8);
+ lc_add_to_len(head, -15);
+ coalesce_lc_node(btrie, head, 15);
+ check_non_terminal_lc_node(head, LC_BYTES_PER_NODE * 8 - 7);
+ assert(memcmp(head->prefix, numbered_bytes, LC_BYTES_PER_NODE - 1) == 0);
+ assert(head->prefix[LC_BYTES_PER_NODE - 1]
+ == numbered_bytes[LC_BYTES_PER_NODE]);
+ {
+ struct lc_node *child = &head->ptr.child->lc_node;
+ check_terminal_lc_node(child, 8 * (LC_BYTES_PER_NODE - 1), data);
+ assert(memcmp(child->prefix, &numbered_bytes[LC_BYTES_PER_NODE + 1],
+ LC_BYTES_PER_NODE - 1) == 0);
+ }
+
+ PASS("test_coalesce_lc_node");
+}
+
+static void
+test_shorten_lc_node()
+{
+ struct btrie *btrie = btrie_init(NULL);
+ const void *data = (void *)0xdeadbeef;
+ node_t node, shorter;
+
+ /* test shorten without shift */
+ init_terminal_node(btrie, &node, 0,
+ numbered_bytes, 8 * LC_BYTES_PER_NODE, data);
+ memset(shorter.lc_node.prefix, 0xff, LC_BYTES_PER_NODE);
+ shorten_lc_node(btrie, &shorter, 7, &node.lc_node, 0);
+ check_terminal_lc_node(&shorter.lc_node, LC_BYTES_PER_NODE * 8 - 7, data);
+ assert(memcmp(shorter.lc_node.prefix, numbered_bytes, LC_BYTES_PER_NODE)
+ == 0);
+
+ /* test shorten with shift */
+ init_terminal_node(btrie, &node, 7,
+ numbered_bytes, 8 * LC_BYTES_PER_NODE, data);
+ memset(shorter.lc_node.prefix, 0xff, LC_BYTES_PER_NODE);
+ shorten_lc_node(btrie, &shorter, 9, &node.lc_node, 7);
+ check_terminal_lc_node(&shorter.lc_node, LC_BYTES_PER_NODE * 8 - 9, data);
+ assert(memcmp(shorter.lc_node.prefix, &numbered_bytes[1],
+ LC_BYTES_PER_NODE - 1) == 0);
+
+ {
+ /* test child stealing */
+ struct lc_node head;
+ node_t tail, shorter;
+
+ lc_init_flags(&head, 0, 7);
+ head.ptr.child = &tail;
+ init_empty_node(btrie, &tail);
+
+ shorten_lc_node(btrie, &shorter, 7, &head, 0);
+ assert(is_empty_node(&shorter));
+ }
+
+ PASS("test_shorten_lc_node");
+}
+
+static void
+test_split_lc_node()
+{
+ struct btrie *btrie = btrie_init(NULL);
+ const void *data = (void *)0xdeadbeef;
+ struct lc_node node;
+
+ init_terminal_node(btrie, (node_t *)&node, 1, numbered_bytes, 25, data);
+ split_lc_node(btrie, &node, 1, 8);
+ check_non_terminal_lc_node(&node, 8);
+ check_terminal_lc_node(&node.ptr.child->lc_node, 16, data);
+
+ /* test conversion of terminal to non-terminal */
+ init_terminal_node(btrie, (node_t *)&node, 7, numbered_bytes, 10, data);
+ split_lc_node(btrie, &node, 7, 3);
+ check_non_terminal_lc_node(&node, 3);
+ check_terminal_lc_node(&node.ptr.child->lc_node, 0, data);
+
+ PASS("test_split_lc_node");
+}
+
+static void
+test_convert_lc_node_1()
+{
+ struct btrie *btrie = btrie_init(NULL);
+ const void *data = (void *)0xdeadbeef;
+ struct lc_node head;
+
+ /* test tail is left */
+ lc_init_flags(&head, 0, 1);
+ head.prefix[0] = 0;
+ head.ptr.child = alloc_nodes(btrie, 1, 0);
+ init_terminal_node(btrie, head.ptr.child, 1, numbered_bytes, 1, data);
+ convert_lc_node_1(btrie, &head, 0);
+ {
+ node_t *result = (node_t *)&head;
+ assert(is_tbm_node(result));
+ assert(result->tbm_node.ext_bm == 0);
+ assert(result->tbm_node.int_bm == bit(base_index(0, 1)));
+ assert(*tbm_data_p(&result->tbm_node, 0, 1) == data);
+ }
+
+ /* test tail is right */
+ lc_init_flags(&head, 0, 1);
+ head.prefix[0] = 1;
+ head.ptr.child = alloc_nodes(btrie, 1, 0);
+ init_terminal_node(btrie, head.ptr.child, 8, numbered_bytes, 10, data);
+ convert_lc_node_1(btrie, &head, 7);
+ {
+ node_t *result = (node_t *)&head;
+ assert(is_tbm_node(result));
+ assert(result->tbm_node.ext_bm == 0);
+ assert(result->tbm_node.int_bm == bit(base_index(4, 3)));
+ assert(*tbm_data_p(&result->tbm_node, 4, 3) == data);
+ }
+
+ PASS("test_convert_lc_node_1");
+}
+
+static void
+test_convert_lc_node()
+{
+ struct btrie *btrie = btrie_init(NULL);
+ const void *data = (void *)0xdeadbeef;
+ node_t node;
+
+ /* if (len >= TBM_STRIDE) */
+ init_terminal_node(btrie, &node, 7, numbered_bytes, TBM_STRIDE + 7, data);
+ convert_lc_node(btrie, &node.lc_node, 7);
+ assert(is_tbm_node(&node));
+ assert(node.tbm_node.ext_bm == bit(0));
+ assert(node.tbm_node.int_bm == 0);
+ check_terminal_lc_node(&tbm_ext_path(&node.tbm_node, 0)->lc_node, 0, data);
+
+ /* if (lc_is_terminal(node)) */
+ init_terminal_node(btrie, &node, 0, numbered_bytes, 0, data);
+ convert_lc_node(btrie, &node.lc_node, 0);
+ assert(is_tbm_node(&node));
+ assert(node.tbm_node.ext_bm == 0);
+ assert(node.tbm_node.int_bm == bit(base_index(0, 0)));
+ assert(*tbm_data_p(&node.tbm_node, 0, 0) == data);
+
+ /* else */
+ lc_init_flags(&node.lc_node, 0, TBM_STRIDE - 1);
+ node.lc_node.prefix[0] = 0;
+ node.lc_node.ptr.child = alloc_nodes(btrie, 1, 0);
+ init_empty_node(btrie, node.lc_node.ptr.child);
+ tbm_insert_data(btrie, &node.lc_node.ptr.child->tbm_node, 0, 0, data);
+
+ convert_lc_node(btrie, &node.lc_node, 0);
+ assert(is_tbm_node(&node));
+ assert(node.tbm_node.ext_bm == 0);
+ assert(node.tbm_node.int_bm == bit(base_index(0, TBM_STRIDE - 1)));
+ assert(*tbm_data_p(&node.tbm_node, 0, TBM_STRIDE - 1) == data);
+
+ PASS("test_convert_lc_node");
+}
+
+static void
+test_insert_lc_node()
+{
+ struct btrie *btrie = btrie_init(NULL);
+ const void *data = (void *)0xdeadbeef;
+ node_t node, tail;
+
+ /* test optimized case, last_bit == 0 */
+ init_terminal_node(btrie, &tail, 9, numbered_bytes, 17, data);
+ insert_lc_node(btrie, &node, 8, 0, 0, &tail);
+ check_terminal_lc_node(&node.lc_node, 9, data);
+ assert(memcmp(node.lc_node.prefix, &numbered_bytes[1], 2) == 0);
+
+ /* test optimized case, last_bit == 1 */
+ init_terminal_node(btrie, &tail, 7, &numbered_bytes[0x12], 15, data);
+ insert_lc_node(btrie, &node, 6, 0x10, 1, &tail);
+ check_terminal_lc_node(&node.lc_node, 9, data);
+ assert(node.lc_node.prefix[0] == 0x12);
+ assert(node.lc_node.prefix[1] == 0x13);
+
+ /* test with shift */
+ init_terminal_node(btrie, &tail, 0, numbered_bytes, 8, data);
+ insert_lc_node(btrie, &node, 7, 0x40, 1, &tail);
+ check_terminal_lc_node(&node.lc_node, 9, data);
+ assert(node.lc_node.prefix[0] == 0x41);
+ assert(node.lc_node.prefix[1] == numbered_bytes[0]);
+
+ /* test with TBM node */
+ init_empty_node(btrie, &tail);
+ insert_lc_node(btrie, &node, 6, 0x40, 0, &tail);
+ check_non_terminal_lc_node(&node.lc_node, 1);
+ assert(is_tbm_node(node.lc_node.ptr.child));
+
+ PASS("test_insert_lc_node");
+}
+
+static void
+test_next_pbyte()
+{
+ assert(next_pbyte(0xff, 0, 1) == 0x80 >> (TBM_STRIDE - 1));
+ assert(next_pbyte(0xff, 1, 1) == (0x80 | (0x80 >> TBM_STRIDE)));
+ assert(next_pbyte(0xff, 2, 1) == (0xc0 | (0x80 >> (TBM_STRIDE + 1))));
+ assert(next_pbyte(0xff, 8 - TBM_STRIDE, 1) == 0);
+ assert(next_pbyte(0xff, 9 - TBM_STRIDE, 1) == 0x80);
+
+ PASS("test_next_pbyte");
+}
+
+static void
+test_init_tbm_node()
+{
+ struct btrie *btrie = btrie_init(NULL);
+ const void *data = (void *)0xdeadbeef;
+ unsigned lr;
+ node_t node;
+
+ /* test root data */
+ init_tbm_node(btrie, &node, 0, 0, &data, NULL, NULL);
+ assert(is_tbm_node(&node));
+ assert(node.tbm_node.ext_bm == 0);
+ assert(node.tbm_node.int_bm == bit(base_index(0, 0)));
+ assert(*tbm_data_p(&node.tbm_node, 0, 0) == data);
+
+ for (lr = 0; lr < 2; lr++) {
+ node_t child;
+ node_t *left = lr ? NULL : &child;
+ node_t *right = lr ? &child : NULL;
+ unsigned base = lr ? (1U << (TBM_STRIDE - 1)) : 0;
+ unsigned pfx;
+
+ /* test with long LC node child */
+ init_terminal_node(btrie, &child, 1, numbered_bytes, TBM_STRIDE + 1, data);
+ init_tbm_node(btrie, &node, 0, 0, NULL, left, right);
+ assert(is_tbm_node(&node));
+ assert(node.tbm_node.ext_bm == bit(base));
+ assert(node.tbm_node.int_bm == 0);
+ check_terminal_lc_node(&tbm_ext_path(&node.tbm_node, base)->lc_node,
+ 1, data);
+
+ /* test with short LC node children */
+ init_terminal_node(btrie, &child, 1, numbered_bytes, TBM_STRIDE - 1, data);
+ init_tbm_node(btrie, &node, 0, 0, NULL, left, right);
+ assert(is_tbm_node(&node));
+ assert(node.tbm_node.ext_bm == 0);
+ assert(node.tbm_node.int_bm == bit(base_index(base >> 1, TBM_STRIDE-1)));
+ assert(*tbm_data_p(&node.tbm_node, base >> 1, TBM_STRIDE-1) == data);
+
+ /* construct TBM node with all eight combinations of having data,
+ * left_ext and/or right_ext in its extending paths */
+ init_empty_node(btrie, &child);
+ for (pfx = 0; pfx < 8; pfx++) {
+ if (pfx & 1)
+ tbm_insert_data(btrie, &child.tbm_node, pfx, TBM_STRIDE - 1, data);
+ if (pfx & 2) {
+ btrie_oct_t prefix0 = 0;
+ init_terminal_node(btrie,
+ tbm_insert_ext_path(btrie, &child.tbm_node, 2*pfx),
+ TBM_STRIDE + 1,
+ &prefix0, TBM_STRIDE + 2, data);
+ }
+ if (pfx & 4) {
+ btrie_oct_t prefix0 = 0x80 >> TBM_STRIDE;
+ init_terminal_node(btrie,
+ tbm_insert_ext_path(btrie, &child.tbm_node, 2*pfx+1),
+ TBM_STRIDE + 1,
+ &prefix0, TBM_STRIDE + 3, data);
+ }
+ }
+ init_tbm_node(btrie, &node, 0, 0, NULL, left, right);
+ for (pfx = 0; pfx < 8; pfx++) {
+ unsigned base = lr ? (1U << (TBM_STRIDE - 1)) : 0;
+ node_t *ext_path = tbm_ext_path(&node.tbm_node, base + pfx);
+ if (pfx == 0)
+ assert(ext_path == NULL);
+ else if (pfx == 1)
+ check_terminal_lc_node(&ext_path->lc_node, 0, data);
+ else if (pfx == 2) {
+ check_terminal_lc_node(&ext_path->lc_node, 2, data);
+ assert(ext_path->lc_node.prefix[0] == 0);
+ }
+ else if (pfx == 4) {
+ check_terminal_lc_node(&ext_path->lc_node, 3, data);
+ assert(ext_path->lc_node.prefix[0] == (0x80 >> TBM_STRIDE));
+ }
+ else {
+ tbm_bitmap_t int_bm = 0;
+ assert(is_tbm_node(ext_path));
+ if (pfx & 1) {
+ int_bm |= bit(base_index(0, 0));
+ assert(*tbm_data_p(&ext_path->tbm_node, 0, 0) == data);
+ }
+ if (pfx & 2) {
+ int_bm |= bit(base_index(0, 2));
+ assert(*tbm_data_p(&ext_path->tbm_node, 0, 2) == data);
+ }
+ if (pfx & 4) {
+ int_bm |= bit(base_index(4, 3));
+ assert(*tbm_data_p(&ext_path->tbm_node, 4, 3) == data);
+ }
+ assert(ext_path->tbm_node.int_bm == int_bm);
+ }
+ }
+ }
+
+ PASS("test_init_tbm_node");
+}
+
+static void
+test_add_to_trie()
+{
+ struct btrie *btrie = btrie_init(NULL);
+ const void *data = (void *)0xdeadbeef;
+ enum btrie_result result;
+ unsigned pfx, plen;
+ node_t root;
+
+ /* test initial insertion */
+ init_empty_node(btrie, &root);
+ result = add_to_trie(btrie, &root, 0,
+ numbered_bytes, 8 * 2 * LC_BYTES_PER_NODE, data);
+ assert(result == BTRIE_OKAY);
+ check_non_terminal_lc_node(&root.lc_node, 8 * LC_BYTES_PER_NODE);
+ check_terminal_lc_node(&root.lc_node.ptr.child->lc_node,
+ 8 * LC_BYTES_PER_NODE, data);
+
+ /* test can follow LC node to tail, and then detect duplicate prefix */
+ result = add_to_trie(btrie, &root, 0,
+ numbered_bytes, 8 * 2 * LC_BYTES_PER_NODE, data);
+ assert(result == BTRIE_DUPLICATE_PREFIX);
+
+ /* test can insert new TBM node within existing LC node */
+ result = add_to_trie(btrie, &root, 0,
+ &numbered_bytes[1], 16, data);
+ assert(result == BTRIE_OKAY);
+ check_non_terminal_lc_node(&root.lc_node, 7);
+ assert(is_tbm_node(root.lc_node.ptr.child));
+
+ /* test can convert terminal LC node to TBM node */
+ init_terminal_node(btrie, &root, 0, numbered_bytes, 12, data);
+ result = add_to_trie(btrie, &root, 0, numbered_bytes, 24, data);
+ assert(result == BTRIE_OKAY);
+ check_non_terminal_lc_node(&root.lc_node, 12);
+ assert(is_tbm_node(root.lc_node.ptr.child));
+
+ /* test can insert internal prefix data in TBM node */
+ for (plen = 0; plen < TBM_STRIDE; plen++) {
+ for (pfx = 0; pfx < (1U << plen); pfx++) {
+ btrie_oct_t prefix0 = plen ? pfx << (8 - plen) : 0;
+ init_empty_node(btrie, &root);
+ init_terminal_node(btrie, tbm_insert_ext_path(btrie, &root.tbm_node, 0),
+ TBM_STRIDE,
+ numbered_bytes, 8, data);
+ result = add_to_trie(btrie, &root, 0, &prefix0, plen, data);
+ assert(result == BTRIE_OKAY);
+ assert(is_tbm_node(&root));
+ assert(root.tbm_node.ext_bm == bit(0));
+ assert(root.tbm_node.int_bm == bit(base_index(pfx, plen)));
+ assert(*tbm_data_p(&root.tbm_node, pfx, plen) == data);
+
+ result = add_to_trie(btrie, &root, 0, &prefix0, plen, data);
+ assert(result == BTRIE_DUPLICATE_PREFIX);
+ }
+ }
+
+ /* test can add extending paths to TBM node */
+ for (pfx = 0; pfx < (1U << TBM_STRIDE); pfx++) {
+ btrie_oct_t prefix0 = pfx << (8 - TBM_STRIDE);
+ init_empty_node(btrie, &root);
+ tbm_insert_data(btrie, &root.tbm_node, 0, 0, data);
+ result = add_to_trie(btrie, &root, 0, &prefix0, 8, data);
+ assert(result == BTRIE_OKAY);
+ assert(is_tbm_node(&root));
+ assert(root.tbm_node.ext_bm == bit(pfx));
+ assert(root.tbm_node.int_bm == bit(base_index(0, 0)));
+ check_terminal_lc_node(&tbm_ext_path(&root.tbm_node, pfx)->lc_node,
+ 8 - TBM_STRIDE, data);
+
+ result = add_to_trie(btrie, &root, 0, &prefix0, 8, data);
+ assert(result == BTRIE_DUPLICATE_PREFIX);
+ }
+
+ /* test can follow extending path */
+ init_empty_node(btrie, &root);
+ init_terminal_node(btrie,
+ tbm_insert_ext_path(btrie, &root.tbm_node, 0), TBM_STRIDE,
+ numbered_bytes, 8, data);
+ result = add_to_trie(btrie, &root, 0, numbered_bytes, 7, data);
+ assert(result == BTRIE_OKAY);
+ assert(root.tbm_node.ext_bm == bit(0));
+ assert(root.tbm_node.int_bm == 0);
+ check_non_terminal_lc_node(&root.tbm_node.ptr.children[0].lc_node,
+ 7 - TBM_STRIDE);
+
+ PASS("test_add_to_trie");
+}
+
+static void
+test_search_trie()
+{
+ struct btrie *btrie = btrie_init(NULL);
+ const void *data01 = (void *)0xdead0001;
+ const void *data11 = (void *)0xdead0101;
+ const void *data = (void *)0xdeadbeef;
+ unsigned plen, pfx;
+ node_t root;
+
+ /* test can follow chain of LC nodes to an exact match */
+ init_empty_node(btrie, &root);
+ add_to_trie(btrie, &root, 0,
+ numbered_bytes, 8 * 2 * LC_BYTES_PER_NODE, data);
+
+ assert(search_trie(&root, 0, numbered_bytes, 8 * 2 * LC_BYTES_PER_NODE)
+ == data);
+ assert(search_trie(&root, 0, numbered_bytes, 8 * 2 * LC_BYTES_PER_NODE + 1)
+ == data);
+ assert(search_trie(&root, 0, numbered_bytes, 8 * 2 * LC_BYTES_PER_NODE - 1)
+ == NULL);
+ assert(search_trie(&root, 0, &numbered_bytes[1], 8 * 2 * LC_BYTES_PER_NODE)
+ == NULL);
+
+ /* test can follow extending path to an exact match */
+ for (pfx = 0; pfx < (1U << TBM_STRIDE); pfx++) {
+ btrie_oct_t prefix0 = pfx << (8 - TBM_STRIDE);
+ init_empty_node(btrie, &root);
+ tbm_insert_data(btrie, &root.tbm_node, 0, 1, data01);
+ tbm_insert_data(btrie, &root.tbm_node, 1, 1, data11);
+ add_to_trie(btrie, &root, 0, &prefix0, 8, data);
+ assert(search_trie(&root, 0, &prefix0, 8) == data);
+ /* test that last matching TBM internal prefix gets picked up */
+ if (prefix0 & 0x80)
+ assert(search_trie(&root, 0, &prefix0, 7) == data11);
+ else
+ assert(search_trie(&root, 0, &prefix0, 7) == data01);
+ prefix0 ^= 1 << (8 - TBM_STRIDE);
+ if (prefix0 & 0x80)
+ assert(search_trie(&root, 0, &prefix0, 8) == data11);
+ else
+ assert(search_trie(&root, 0, &prefix0, 8) == data01);
+ }
+
+ /* test finding of TBM internal prefixes */
+ init_empty_node(btrie, &root);
+ tbm_insert_data(btrie, &root.tbm_node, 0, 1, data01);
+ tbm_insert_data(btrie, &root.tbm_node, 1, 1, data11);
+
+ assert(search_trie(&root, 0, numbered_bytes, 0) == NULL);
+ for (plen = 1; plen < TBM_STRIDE; plen++) {
+ for (pfx = 0; pfx < (1U << TBM_STRIDE); pfx++) {
+ btrie_oct_t prefix0 = pfx << (8 - plen);
+ if (prefix0 & 0x80)
+ assert(search_trie(&root, 0, &prefix0, plen) == data11);
+ else
+ assert(search_trie(&root, 0, &prefix0, plen) == data01);
+ }
+ }
+
+ PASS("test_search_trie");
+}
+
+static int
+unit_tests()
+{
+ test_struct_node_packing();
+ test_bit();
+ test_count_bits();
+ test_count_bits_before();
+ test_count_bits_from();
+ test_extract_bits();
+ test_high_bits();
+ test_prefixes_equal();
+ test_common_prefix();
+ test_base_index();
+ test_has_internal_data();
+
+ test_init_terminal_node();
+ test_coalesce_lc_node();
+ test_shorten_lc_node();
+ test_split_lc_node();
+ test_convert_lc_node_1();
+ test_convert_lc_node();
+ test_insert_lc_node();
+ test_next_pbyte();
+ test_init_tbm_node();
+ test_add_to_trie();
+ test_search_trie();
+
+ puts("\nOK");
+ return 0;
+}
+
+/*****************************************************************
+ *
+ * btrie_dump: print out the trie structure (for testing)
+ *
+ */
+#define INDENT_FILL "....:....|....:....|....:....|....:....|"
+
+static void dump_node(const node_t *node, unsigned pos, btrie_oct_t *prefix,
+ int indent);
+
+static void
+dump_prefix(btrie_oct_t *prefix, unsigned len, int indent, const char *tail)
+{
+ unsigned i;
+
+ printf("%*.*s0x", indent, indent, INDENT_FILL);
+ for (i = 0; i < len / 8; i++)
+ printf("%02x", prefix[i]);
+ if (len % 8)
+ printf("%02x", prefix[len / 8] & high_bits(len % 8));
+ printf("/%u%s", len, tail);
+}
+
+/* the opposite of extract_bits, sets a short string of bits from integer */
+static void
+insert_bits(btrie_oct_t *prefix, unsigned pos, btrie_oct_t pfx, unsigned nbits)
+{
+ if (nbits != 0) {
+ unsigned v = (prefix[pos / 8] << 8) + prefix[pos / 8 + 1];
+ unsigned mask = (1U << nbits) - 1;
+ unsigned shift = 16 - (pos % 8) - nbits;
+ v = (v & ~(mask << shift)) | (pfx << shift);
+ prefix[pos / 8] = v >> 8;
+ prefix[pos / 8 + 1] = (btrie_oct_t)v;
+ }
+}
+
+static void
+dump_tbm_node(const struct tbm_node *node, unsigned pos,
+ btrie_oct_t *prefix, int indent)
+{
+ unsigned pfx = 0, plen = 0;
+
+ dump_prefix(prefix, pos, indent, " [tbm]\n");
+
+ for (;;) {
+ if (plen < TBM_STRIDE) {
+ const void **data_p = tbm_data_p(node, pfx, plen);
+ if (data_p) {
+ insert_bits(prefix, pos, pfx, plen);
+ dump_prefix(prefix, pos + plen, indent, "");
+ printf(" [%u/%u] (%s)\n", pfx, plen, (const char *)*data_p);
+ }
+ plen++;
+ pfx <<= 1;
+ }
+ else {
+ const node_t *ext_path = tbm_ext_path(node, pfx);
+ if (ext_path) {
+ insert_bits(prefix, pos, pfx, TBM_STRIDE);
+ dump_node(ext_path, pos + TBM_STRIDE, prefix, indent + 1);
+ }
+ while (pfx & 1) {
+ if (--plen == 0)
+ return;
+ pfx >>= 1;
+ }
+ pfx++;
+ }
+ }
+}
+
+static void
+dump_lc_node(const struct lc_node *node, unsigned pos,
+ btrie_oct_t *prefix, int indent)
+{
+ unsigned end = pos + lc_len(node);
+ btrie_oct_t save_prefix = prefix[lc_shift(pos)];
+
+ memcpy(&prefix[lc_shift(pos)], node->prefix, lc_bytes(node, pos));
+
+ if (lc_is_terminal(node)) {
+ dump_prefix(prefix, end, indent, "");
+ printf(" (%s)\n", (const char *)node->ptr.data);
+ }
+ else {
+ dump_prefix(prefix, end, indent, "\n");
+ dump_node(node->ptr.child, end, prefix, indent + 1);
+ }
+
+ prefix[lc_shift(pos)] = save_prefix;
+ if (lc_bytes(node, pos) > 1)
+ memset(&prefix[lc_shift(pos) + 1], 0, lc_bytes(node, pos) - 1);
+}
+
+static void
+dump_node(const node_t *node, unsigned pos, btrie_oct_t *prefix, int indent)
+{
+ if (is_lc_node(node))
+ dump_lc_node(&node->lc_node, pos, prefix, indent);
+ else
+ dump_tbm_node(&node->tbm_node, pos, prefix, indent);
+}
+
+static void
+btrie_dump(struct btrie *btrie)
+{
+ btrie_oct_t prefix[(BTRIE_MAX_PREFIX + 7) / 8];
+
+ memset(prefix, 0, sizeof(prefix));
+ dump_node(&btrie->root, 0, prefix, 0);
+ puts(btrie_stats(btrie));
+}
+
+/****************************************************************
+ *
+ * test program - just enough to construct a trie and preform a lookup
+ *
+ */
+
+#include <arpa/inet.h>
+
+static int
+parse_prefix(const char *arg, btrie_oct_t prefix[16], unsigned *len)
+{
+ char addrbuf[128];
+ return sscanf(arg, "%127[0-9a-fA-F:]/%u", addrbuf, len) == 2
+ && inet_pton(AF_INET6, addrbuf, prefix) == 1;
+}
+
+static int
+test_btrie(int argc, char *argv[])
+{
+ struct btrie *btrie = btrie_init(NULL);
+ int i;
+ btrie_oct_t prefix[16];
+ unsigned len;
+
+ for (i = 1; i < argc-1; i++) {
+ if (!parse_prefix(argv[i], prefix, &len)) {
+ fprintf(stderr, "Can not parse arg '%s'\n", argv[i]);
+ return 1;
+ }
+ btrie_add_prefix(btrie, prefix, len, argv[i]);
+ }
+
+ btrie_dump(btrie);
+
+ if (argc > 1) {
+ const void *data;
+
+ if (!parse_prefix(argv[argc-1], prefix, &len)) {
+ fprintf(stderr, "Can not parse arg '%s'\n", argv[argc-1]);
+ return 1;
+ }
+ data = btrie_lookup(btrie, prefix, 128);
+ printf("lookup(%s) => %s\n", argv[argc-1], (const char *)data);
+ }
+
+ return 0;
+}
+
+int
+main(int argc, char *argv[])
+{
+ if ((pgm_name = strrchr(argv[0], '/')) != NULL)
+ pgm_name++;
+ else
+ pgm_name = argv[0];
+
+ if (argc > 1)
+ return test_btrie(argc, argv);
+ else
+ return unit_tests();
+}
+
+#endif /* TEST */
diff --git a/contrib/lc-btrie/btrie.h b/contrib/lc-btrie/btrie.h
new file mode 100644
index 000000000..2cc662961
--- /dev/null
+++ b/contrib/lc-btrie/btrie.h
@@ -0,0 +1,83 @@
+/* Level-Compressed Tree Bitmap (LC-TBM) Trie implementation
+ *
+ * Contributed by Geoffrey T. Dairiki <dairiki@dairiki.org>
+ *
+ * This file is released under a "Three-clause BSD License".
+ *
+ * Copyright (c) 2013, Geoffrey T. Dairiki
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * * Neither the name of Geoffrey T. Dairiki nor the names of other
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GEOFFREY
+ * T. DAIRIKI BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+#ifndef _BTRIE_H_INCLUDED
+#define _BTRIE_H_INCLUDED
+
+#include "config.h"
+
+#include <stdint.h>
+typedef uint8_t btrie_oct_t;
+
+/* maximum length of bit string btrie_walk() can handle
+ *
+ * note: this limit is necessitated by the use of fixed length buffers
+ * in btrie_walk() --- btrie_add_prefix() and btrie_lookup() impose no
+ * limit on the length of bitstrings
+ */
+#define BTRIE_MAX_PREFIX 128
+
+struct btrie;
+struct memory_pool_s;
+
+struct btrie * btrie_init(struct memory_pool_s *mp);
+
+enum btrie_result
+{
+ BTRIE_OKAY = 0,
+ BTRIE_ALLOC_FAILED = -1,
+ BTRIE_DUPLICATE_PREFIX = 1
+};
+
+enum btrie_result btrie_add_prefix(struct btrie *btrie,
+ const btrie_oct_t *prefix, unsigned len, const void *data);
+
+const void *btrie_lookup(const struct btrie *btrie, const btrie_oct_t *pfx,
+ unsigned len);
+
+const char *btrie_stats(const struct btrie *btrie);
+
+#ifndef NO_MASTER_DUMP
+typedef void btrie_walk_cb_t(const btrie_oct_t *prefix, unsigned len,
+ const void *data, int post, void *user_data);
+
+void btrie_walk(const struct btrie *btrie, btrie_walk_cb_t *callback,
+ void *user_data);
+#endif /* not NO_MASTER_DUMP */
+
+#endif /* _BTRIE_H_INCLUDED */
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index f21e604f7..fccdef5e8 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -23,6 +23,7 @@ ELSE(NOT CMAKE_SYSTEM_NAME STREQUAL "Darwin")
TARGET_LINK_LIBRARIES(rspamd-test "-Wl,-force_load ${CMAKE_BINARY_DIR}/src/librspamd-server.a")
ENDIF(NOT CMAKE_SYSTEM_NAME STREQUAL "Darwin")
TARGET_LINK_LIBRARIES(rspamd-test rspamd-cdb)
+TARGET_LINK_LIBRARIES(rspamd-test lcbtrie)
TARGET_LINK_LIBRARIES(rspamd-test rspamd-http-parser)
TARGET_LINK_LIBRARIES(rspamd-test ${RSPAMD_REQUIRED_LIBRARIES})
IF (ENABLE_SNOWBALL MATCHES "ON")
diff --git a/test/rspamd_radix_test.c b/test/rspamd_radix_test.c
index 367fbfd35..9bf421cdb 100644
--- a/test/rspamd_radix_test.c
+++ b/test/rspamd_radix_test.c
@@ -17,6 +17,7 @@
#include "rspamd.h"
#include "radix.h"
#include "ottery.h"
+#include "btrie.h"
const gsize max_elts = 50 * 1024;
const gint lookup_cycles = 1 * 1024;
@@ -74,7 +75,7 @@ struct _tv {
};
static void
-rspamd_radix_text_vec (void)
+rspamd_radix_test_vec (void)
{
radix_compressed_t *tree = radix_create_compressed ();
struct _tv *t = &test_vec[0];
@@ -124,7 +125,7 @@ rspamd_radix_text_vec (void)
while (t->ip != NULL) {
val = radix_find_compressed (tree, t->addr, t->len);
g_assert (val == ++i);
- //g_assert (val != RADIX_NO_VALUE);
+ /* g_assert (val != RADIX_NO_VALUE); */
if (t->nip != NULL) {
val = radix_find_compressed (tree, t->naddr, t->len);
g_assert (val != i);
@@ -135,12 +136,78 @@ rspamd_radix_text_vec (void)
radix_destroy_compressed (tree);
}
+static void
+rspamd_btrie_test_vec (void)
+{
+ rspamd_mempool_t *pool;
+ struct btrie *tree;
+ struct _tv *t = &test_vec[0];
+ struct in_addr ina;
+ struct in6_addr in6a;
+ gsize i;
+ gpointer val;
+
+ pool = rspamd_mempool_new (rspamd_mempool_suggest_size (), "btrie");
+ tree = btrie_init (pool);
+
+ while (t->ip != NULL) {
+ t->addr = g_malloc (sizeof (in6a));
+ t->naddr = g_malloc (sizeof (in6a));
+ if (inet_pton (AF_INET, t->ip, &ina) == 1) {
+ memcpy (t->addr, &ina, sizeof (ina));
+ t->len = sizeof (ina);
+ }
+ else if (inet_pton (AF_INET6, t->ip, &in6a) == 1) {
+ memcpy (t->addr, &in6a, sizeof (in6a));
+ t->len = sizeof (in6a);
+ }
+ else {
+ g_assert (0);
+ }
+ if (t->nip) {
+ if (inet_pton (AF_INET, t->nip, &ina) == 1) {
+ memcpy (t->naddr, &ina, sizeof (ina));
+ }
+ else if (inet_pton (AF_INET6, t->nip, &in6a) == 1) {
+ memcpy (t->naddr, &in6a, sizeof (in6a));
+ }
+ else {
+ g_assert (0);
+ }
+ }
+
+ t->mask = strtoul (t->m, NULL, 10);
+ t ++;
+ }
+ t = &test_vec[0];
+
+ i = 0;
+ while (t->ip != NULL) {
+ g_assert (btrie_add_prefix (tree, t->addr, t->mask,
+ GSIZE_TO_POINTER (++i)) == BTRIE_OKAY);
+ t ++;
+ }
+
+ i = 0;
+ t = &test_vec[0];
+ while (t->ip != NULL) {
+ val = btrie_lookup (tree, t->addr, t->len * NBBY);
+ i ++;
+
+ g_assert (GPOINTER_TO_SIZE (val) == i);
+ if (t->nip != NULL) {
+ val = btrie_lookup (tree, t->naddr, t->len * NBBY);
+ g_assert (GPOINTER_TO_SIZE (val) != i);
+ }
+ t ++;
+ }
+}
+
void
rspamd_radix_test_func (void)
{
-#if 0
- radix_tree_t *tree = radix_tree_create ();
-#endif
+ struct btrie *btrie;
+ rspamd_mempool_t *pool;
radix_compressed_t *comp_tree = radix_create_compressed ();
struct {
guint32 addr;
@@ -155,7 +222,8 @@ rspamd_radix_test_func (void)
double diff;
/* Test suite for the compressed trie */
- rspamd_radix_text_vec ();
+ rspamd_radix_test_vec ();
+ rspamd_btrie_test_vec ();
nelts = max_elts;
/* First of all we generate many elements and push them to the array */
@@ -167,12 +235,15 @@ rspamd_radix_test_func (void)
ottery_rand_bytes (addrs[i].addr6, sizeof(addrs[i].addr6));
addrs[i].mask6 = ottery_rand_range(128);
}
-#if 0
- msg_info ("old radix performance (%z elts)", nelts);
+
+ pool = rspamd_mempool_new (65536, "btrie");
+ btrie = btrie_init (pool);
+ msg_info ("btrie performance (%z elts)", nelts);
+
ts1 = rspamd_get_ticks ();
for (i = 0; i < nelts; i ++) {
- guint32 mask = G_MAXUINT32 << (32 - addrs[i].mask);
- radix32tree_insert (tree, addrs[i].addr, mask, 1);
+ btrie_add_prefix (btrie, addrs[i].addr6,
+ addrs[i].mask6, GSIZE_TO_POINTER (i));
}
ts2 = rspamd_get_ticks ();
diff = (ts2 - ts1) * 1000.0;
@@ -182,25 +253,18 @@ rspamd_radix_test_func (void)
ts1 = rspamd_get_ticks ();
for (lc = 0; lc < lookup_cycles; lc ++) {
for (i = 0; i < nelts; i ++) {
- g_assert (radix32tree_find (tree, addrs[i].addr) != RADIX_NO_VALUE);
+ if (btrie_lookup (btrie, addrs[i].addr6, sizeof (addrs[i].addr6))
+ == NULL) {
+ all_good = FALSE;
+ }
}
}
+ g_assert (all_good);
ts2 = rspamd_get_ticks ();
diff = (ts2 - ts1) * 1000.0;
- msg_info ("Checked %z elements in %.6f ms", nelts, diff);
-
- ts1 = rspamd_get_ticks ();
- for (i = 0; i < nelts; i ++) {
- radix32tree_delete (tree, addrs[i].addr, addrs[i].mask);
- }
- ts2 = rspamd_get_ticks ();
- diff = (ts2 - ts1) * 1000.;
-
- msg_info ("Deleted %z elements in %.6f ms", nelts, diff);
+ msg_info ("Checked %z elements in %.6f ms", nelts * lookup_cycles, diff);
- radix_tree_free (tree);
-#endif
msg_info ("new radix performance (%z elts)", nelts);
ts1 = rspamd_get_ticks ();
for (i = 0; i < nelts; i ++) {
@@ -238,7 +302,7 @@ rspamd_radix_test_func (void)
ts2 = rspamd_get_ticks ();
diff = (ts2 - ts1) * 1000.0;
- msg_info ("Checked %z elements in %.6f ms", nelts, diff);
+ msg_info ("Checked %z elements in %.6f ms", nelts * lookup_cycles, diff);
radix_destroy_compressed (comp_tree);
g_free (addrs);