Add nibble-tree iterator

The new struct tree_iterator and associated start/get/next/free
functions replace the recursive walk() function, removing the need for a
callback when iterating over all nodes in the tree, and allowing
iteration to be suspended while other pseudo-threads are run.  This
allows an HTTP REST request to keep a tree_iterator in its state struct
and potentially simplifies other areas of the code.

The iterator free()s any empty internal tree nodes that it encounters,
as did the original tree_walk() function.  To support the existence of
multiple iterators at once, a reference count has been added to the
tree_node struct, to prevent any iterator from free()ing a node while
any other iterators point to it; only the last iterator to pop out of an
empty node will free() it.

The tree_walk() and tree_walk_prefix() functions have been
re-implemented to use an iterator state object internally.  This
resolves an outstanding TODO to perform tree-node freeing during a
prefix walk, and simplifies the code considerably.

Renamed some function parameters and struct members to make the
nibble-tree API a little more self-explanatory.

Added a nibble-tree test to the 'serval-tests' utility.
This commit is contained in:
Andrew Bettison 2017-10-20 17:05:14 +10:30
parent 2ef315b692
commit c7a2fb4573
6 changed files with 490 additions and 97 deletions

View File

@ -409,7 +409,9 @@ servaldwrap: $(OBJSDIR_SERVALD)/servalwrap.o
@echo LINK $@ @echo LINK $@
@$(CC) -Wall -o $@ $^ $(LDFLAGS) @$(CC) -Wall -o $@ $^ $(LDFLAGS)
serval-tests: $(OBJSDIR_SERVALD)/test_features.o libservaldaemon.a serval-tests: $(OBJSDIR_SERVALD)/test_features.o \
$(OBJSDIR_SERVALD)/log_output_console.o \
libservaldaemon.a
@echo LINK $@ @echo LINK $@
@$(CC) -Wall -o $@ $^ $(LDFLAGS) @$(CC) -Wall -o $@ $^ $(LDFLAGS)

View File

@ -640,7 +640,7 @@ int meshmb_open(keyring_identity *id, struct meshmb_feeds **feeds)
*feeds = emalloc_zero(sizeof(struct meshmb_feeds)); *feeds = emalloc_zero(sizeof(struct meshmb_feeds));
if (*feeds){ if (*feeds){
(*feeds)->root.binary_length = sizeof(rhizome_bid_t); (*feeds)->root.index_size_bytes = sizeof(rhizome_bid_t);
(*feeds)->id = id; (*feeds)->id = id;
rhizome_manifest *m = rhizome_new_manifest(); rhizome_manifest *m = rhizome_new_manifest();
if (m){ if (m){

View File

@ -1,7 +1,7 @@
/* /*
Serval DNA Serval DNA
Copyright (C) 2012-2015 Serval Project Inc. Copyright (C) 2012-2015 Serval Project Inc.
Copyright (C) 2016 Flinders University Copyright (C) 2016-2018 Flinders University
This program is free software; you can redistribute it and/or This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License modify it under the terms of the GNU General Public License
@ -22,21 +22,22 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include <unistd.h> #include <unistd.h>
#include <assert.h> #include <assert.h>
#include <string.h> #include <string.h>
#include "lang.h" // for bool_t
#include "mem.h" #include "mem.h"
#include "nibble_tree.h" #include "nibble_tree.h"
static uint8_t get_nibble(const uint8_t *binary, int pos) static unsigned get_nibble(const uint8_t *binary, int pos)
{ {
uint8_t byte = binary[pos>>1]; unsigned byte = binary[pos>>1];
if (!(pos&1)) if (!(pos&1))
byte=byte>>4; byte=byte>>4;
return byte&0xF; return byte&0xF;
} }
enum tree_error_reason tree_find(struct tree_root *root, void **result, const uint8_t *binary, size_t bin_length, enum tree_error_reason tree_find(struct tree_root *root, void **result, const uint8_t *binary, size_t binary_size_bytes,
tree_create_callback create_node, void *context) tree_create_callback create_node, void *context)
{ {
assert(bin_length <= root->binary_length); assert(binary_size_bytes <= root->index_size_bytes);
struct tree_node *ptr = &root->_root_node; struct tree_node *ptr = &root->_root_node;
if (result) if (result)
@ -44,11 +45,11 @@ enum tree_error_reason tree_find(struct tree_root *root, void **result, const ui
unsigned pos=0; unsigned pos=0;
while(1) { while(1) {
if (pos>>1 >= bin_length) if (pos >= binary_size_bytes * 2)
return TREE_NOT_UNIQUE; return TREE_NOT_UNIQUE;
uint8_t nibble = get_nibble(binary, pos++); unsigned nibble = get_nibble(binary, pos++);
void *node_ptr = ptr->tree_nodes[nibble]; void *node_ptr = ptr->slot[nibble];
if (ptr->is_tree & (1<<nibble)){ if (ptr->is_tree & (1<<nibble)){
// search the next level of the tree // search the next level of the tree
@ -56,16 +57,16 @@ enum tree_error_reason tree_find(struct tree_root *root, void **result, const ui
}else if(!node_ptr){ }else if(!node_ptr){
// allow caller to provide a node constructor // allow caller to provide a node constructor
if (create_node && bin_length == root->binary_length){ if (create_node && binary_size_bytes == root->index_size_bytes){
node_ptr = create_node(context, binary, bin_length); node_ptr = create_node(context, binary, binary_size_bytes);
if (!node_ptr) if (!node_ptr)
return TREE_ERROR; return TREE_ERROR;
struct tree_record *tree_record = (struct tree_record *)node_ptr; struct tree_record *tree_record = (struct tree_record *)node_ptr;
assert(memcmp(tree_record->binary, binary, bin_length) == 0); assert(memcmp(tree_record->binary, binary, binary_size_bytes) == 0);
tree_record ->tree_depth = pos*4; tree_record->binary_size_bits = pos*4;
if (result) if (result)
*result = node_ptr; *result = node_ptr;
ptr->tree_nodes[nibble] = node_ptr; ptr->slot[nibble] = node_ptr;
return TREE_FOUND; return TREE_FOUND;
} }
return TREE_NOT_FOUND; return TREE_NOT_FOUND;
@ -74,7 +75,7 @@ enum tree_error_reason tree_find(struct tree_root *root, void **result, const ui
struct tree_record *tree_record = (struct tree_record *)node_ptr; struct tree_record *tree_record = (struct tree_record *)node_ptr;
// check that the remaining bytes of the value are the same // check that the remaining bytes of the value are the same
if (memcmp(tree_record->binary, binary, bin_length) == 0){ if (memcmp(tree_record->binary, binary, binary_size_bytes) == 0){
if (result) if (result)
*result = node_ptr; *result = node_ptr;
return TREE_FOUND; return TREE_FOUND;
@ -88,88 +89,190 @@ enum tree_error_reason tree_find(struct tree_root *root, void **result, const ui
if (!new_node) if (!new_node)
return TREE_ERROR; return TREE_ERROR;
ptr->tree_nodes[nibble] = new_node; ptr->slot[nibble] = new_node;
ptr->is_tree |= (1<<nibble); ptr->is_tree |= (1<<nibble);
ptr = new_node; ptr = new_node;
// get the nibble of the existing node // get the nibble of the existing node
nibble = get_nibble(tree_record->binary, pos); nibble = get_nibble(tree_record->binary, pos);
tree_record->tree_depth = (pos+1)*4; tree_record->binary_size_bits = (pos+1)*4;
ptr->tree_nodes[nibble] = node_ptr; ptr->slot[nibble] = node_ptr;
} }
} }
} }
static int walk(struct tree_node *node, unsigned pos, void tree_iterator_start(tree_iterator *it, struct tree_root *root)
uint8_t *empty, const uint8_t *binary, size_t bin_length, {
walk_callback callback, void *context){ it->stack = &it->bottom;
unsigned i=0, e=16; it->bottom.down = NULL;
int ret=0; it->bottom.node = &root->_root_node;
*empty=1; it->bottom.slotnum = 0;
root->_root_node.ref_count++;
}
if (binary){ static bool_t push(tree_iterator *it)
assert(pos*2 < bin_length); {
uint8_t n = get_nibble(binary, pos); assert(it->stack->node->is_tree & (1 << it->stack->slotnum));
for(;i<n;i++){ struct tree_node *child = it->stack->node->slot[it->stack->slotnum];
if (node->tree_nodes[i]){ assert(child);
*empty=0; tree_node_iterator *nit = (tree_node_iterator *) emalloc_zero(sizeof(tree_node_iterator));
break; if (!nit)
return 0;
nit->down = it->stack;
nit->node = child;
nit->slotnum = 0;
it->stack = nit;
child->ref_count++;
return 1;
}
static inline bool_t is_empty(struct tree_node *node)
{
unsigned i;
for (i = 0; i < 16; ++i)
if (node->slot[i])
return 0;
return 1;
}
static void pop(tree_iterator *it)
{
assert(it->stack);
assert(it->stack->node->ref_count != 0);
tree_node_iterator *popped = it->stack;
it->stack = it->stack->down;
if (--popped->node->ref_count == 0 && it->stack && is_empty(popped->node)) {
assert(it->stack->slotnum < 16);
assert(it->stack->node->is_tree & (1 << it->stack->slotnum));
assert(it->stack->node->slot[it->stack->slotnum] == popped->node);
if (it->stack) {
assert(popped != &it->bottom);
assert(popped->node != it->bottom.node);
free(popped->node);
}
else {
assert(popped == &it->bottom);
assert(popped->node == it->bottom.node);
}
popped->node = NULL;
it->stack->node->slot[it->stack->slotnum] = NULL;
it->stack->node->is_tree &= ~(1 << it->stack->slotnum);
}
if (it->stack) {
free(popped);
it->stack->slotnum++;
}
else
assert(popped == &it->bottom);
}
void tree_iterator_advance_to(tree_iterator *it, const uint8_t *binary, size_t binary_size_bytes)
{
// can only call this function once on an iterator, straight after tree_iterator_start()
assert(it->stack == &it->bottom);
assert(it->stack->slotnum == 0);
assert(it->stack->node);
unsigned n;
for (n = 0; n < binary_size_bytes * 2; ++n) {
it->stack->slotnum = get_nibble(binary, n);
if (!((it->stack->node->is_tree & (1 << it->stack->slotnum)) && push(it)))
break;
}
}
void **tree_iterator_get_node(tree_iterator *it)
{
while (it->stack) {
if (it->stack->slotnum < 16) {
if (it->stack->node->is_tree & (1 << it->stack->slotnum)) {
if (!push(it))
return NULL;
}
else {
void **childp = &it->stack->node->slot[it->stack->slotnum];
if (*childp)
return childp;
else
it->stack->slotnum++;
} }
} }
} else {
assert(it->stack->slotnum == 16);
for (;i<e;i++){ pop(it);
if (node->is_tree & (1<<i)){
uint8_t child_empty=1;
ret = walk((struct tree_node *)node->tree_nodes[i], pos+1, &child_empty, binary, bin_length, callback, context);
if (child_empty){
free(node->tree_nodes[i]);
node->tree_nodes[i]=NULL;
node->is_tree&=~(1<<i);
}
}else if(node->tree_nodes[i]){
ret = callback(&node->tree_nodes[i], context);
} }
if (ret)
return ret;
if (node->tree_nodes[i])
*empty=0;
// stop comparing the start binary after looking at the first branch of the tree
binary=NULL;
} }
return NULL;
}
return ret; void tree_iterator_advance(tree_iterator *it)
{
if (tree_iterator_get_node(it)) {
assert(it->stack);
assert(it->stack->slotnum < 16);
it->stack->slotnum++;
}
}
void tree_iterator_free(tree_iterator *it)
{
while (it->stack)
pop(it);
} }
// start enumerating the tree from binary, and continue until the end // start enumerating the tree from binary, and continue until the end
// callback is allowed to free any nodes while the walk is in progress // callback is allowed to free any nodes while the walk is in progress
int tree_walk(struct tree_root *root, const uint8_t *binary, size_t bin_length, walk_callback callback, void *context) int tree_walk(struct tree_root *root, const uint8_t *binary, size_t binary_size_bytes, walk_callback callback, void *context)
{ {
assert(!binary || bin_length <= root->binary_length); int ret = 0;
uint8_t ignore; tree_iterator it;
return walk(&root->_root_node, 0, &ignore, binary, bin_length, callback, context); tree_iterator_start(&it, root);
if (binary) {
assert(binary_size_bytes <= root->index_size_bytes);
tree_iterator_advance_to(&it, binary, binary_size_bytes);
}
void **node;
while ((node = tree_iterator_get_node(&it)) && (ret = callback(node, context)) == 0)
tree_iterator_advance(&it);
tree_iterator_free(&it);
return ret;
} }
int tree_walk_prefix(struct tree_root *root, const uint8_t *binary, size_t bin_length, walk_callback callback, void *context) int tree_walk_prefix(struct tree_root *root, const uint8_t *binary, size_t binary_size_bytes, walk_callback callback, void *context)
{ {
assert(bin_length <= root->binary_length); assert(binary);
//TODO if callback free's nodes, collapse parent tree nodes too without needing to walk again? assert(binary_size_bytes <= root->index_size_bytes);
struct tree_node *node = &root->_root_node; int ret = 0;
unsigned pos=0; tree_iterator it;
// look for a branch of the tree with a partial match tree_iterator_start(&it, root);
for (; node && pos<bin_length*2; pos++){ tree_iterator_advance_to(&it, binary, binary_size_bytes);
uint8_t i=get_nibble(binary, pos); void **node;
if ((node->is_tree & (1<<i))==0){ while ( (node = tree_iterator_get_node(&it))
struct tree_record *tree_record = (struct tree_record *)node->tree_nodes[i]; && memcmp(((struct tree_record *)*node)->binary, binary, binary_size_bytes) == 0
// only one match? && (ret = callback(node, context)) == 0)
if (tree_record && memcmp(tree_record->binary, binary, bin_length)==0){ tree_iterator_advance(&it);
return callback(&node->tree_nodes[i], context); tree_iterator_free(&it);
} return ret;
return 0; }
}
node = node->tree_nodes[i]; static void walk_statistics(struct tree_node *node, unsigned depth, struct tree_statistics *stats)
} {
// walk the whole branch stats->node_count++;
uint8_t ignore; if (depth > stats->maximum_depth)
return walk(node, pos+1, &ignore, NULL, 0, callback, context); stats->maximum_depth = depth;
if (is_empty(node))
stats->empty_node_count++;
unsigned i;
for (i = 0; i < 16; ++i)
if (node->is_tree & (1 << i))
walk_statistics(node->slot[i], depth + 1, stats);
else if (node->slot[i])
stats->record_count++;
}
struct tree_statistics tree_compute_statistics(struct tree_root *root)
{
struct tree_statistics stats;
bzero(&stats, sizeof stats);
walk_statistics(&root->_root_node, 0, &stats);
return stats;
} }

View File

@ -1,7 +1,7 @@
/* /*
Serval DNA Serval DNA
Copyright (C) 2012-2015 Serval Project Inc. Copyright (C) 2012-2015 Serval Project Inc.
Copyright (C) 2016 Flinders University Copyright (C) 2016-2018 Flinders University
This program is free software; you can redistribute it and/or This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License modify it under the terms of the GNU General Public License
@ -23,22 +23,38 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include <stdint.h> // for uint8_t, size_t #include <stdint.h> // for uint8_t, size_t
struct tree_record{ // Every record in a nibble tree has the following structure:
// number of bits of the binary value, to uniquely identify this record within the tree's current contents // - a count of the number of bits in the binary index
size_t tree_depth; // - the binary index itself, consisting of the number of bytes as specified by
// root.binary_size_bytes
// - the rest of the record
struct tree_record {
size_t binary_size_bits;
uint8_t binary[0]; uint8_t binary[0];
}; };
// each node has 16 slots based on the next 4 bits of the binary value // Each node in the nibble tree has 16 slots based on the next 4 bits of the
// each slot either points to another tree node or a data record // binary value.
struct tree_node{ struct tree_node {
// bit flags for the type of object each element points to // A reference count that is incremented by an iterator while it has a
// pointer to the node, and decremented when it discards the pointer. The
// iterator free()s the node if its count decrements to zero and all of its
// slots are NULL. This prevents nodes being free()d while in-use.
unsigned ref_count;
// A bitmask that has tbe bit (1 << slot_number) set if the corresponding
// slot points to a sub-tree.
uint16_t is_tree; uint16_t is_tree;
void *tree_nodes[16];
// Each slot either points to another tree node or a data record, depending
// on its corresponding bit in 'is_tree'.
void *slot[16];
}; };
struct tree_root{ // The root of a nibble tree specifies the binary index size, in bytes, and
size_t binary_length; // contains the root node.
struct tree_root {
size_t index_size_bytes;
struct tree_node _root_node; struct tree_node _root_node;
}; };
@ -49,28 +65,94 @@ enum tree_error_reason {
TREE_FOUND = 0 TREE_FOUND = 0
}; };
// allocate a new record and return it // allocate a new record and return it
// the returned memory buffer *must* begin with the same memory layout as struct tree_record // the returned memory buffer *must* begin with the same memory layout as struct tree_record
typedef void* (*tree_create_callback) (void *context, const uint8_t *binary, size_t bin_length); typedef void* (*tree_create_callback) (void *context, const uint8_t *binary, size_t binary_size_bytes);
// find the record related to the given binary value // find the record related to the given binary value
// if not found, the supplied not_found function will be called // if not found, the supplied create_node function will be called
// if the callback returns a non-null value it will be inserted into the tree // if the callback returns a non-null value it will be inserted into the tree
// returns either the current depth in the tree or a tree_error_reason // returns either the current depth in the tree or a tree_error_reason
enum tree_error_reason tree_find(struct tree_root *root, void **result, const uint8_t *binary, size_t bin_length, enum tree_error_reason tree_find(struct tree_root *root, void **result, const uint8_t *binary, size_t binary_size_bytes,
tree_create_callback create_node, void *context); tree_create_callback create_node, void *context);
// Iteration:
//
// tree_iterator it;
// tree_iterator_advance_to(&it, index, sizeof index); // optional
// node_type **node;
// for (tree_iterator_start(&it, root); (node = tree_iterator_get_node(&it)); tree_iterator_advance(&it)) {
// ..
// }
// tree_iterator_free(&it);
//
// An iterator advances through nodes in order of ascending binary index.
//
// The tree_iterator_get_node() function returns the same pointer on all
// successive invocations until tree_iterator_advance() is called, and returns
// NULL once the iterator has been advanced past the last node.
//
// The tree_iterator_advance_to() function rapidly positions the iterator at
// the first node whose binary index is >= the given binary index. This
// function can only be called once, straight after tree_iterator_start().
//
// Deletion:
//
// tree_iterator it;
// tree_iterator_start(&it, root);
// ...
// node_type **node = tree_iterator_get_node(&it));
// *node = NULL;
// node = tree_iterator_get_node(&it)); // returns the next node
// ...
// tree_iterator_free(&it);
//
// The tree_iterator_get_node(), tree_iterator_advance() and
// tree_iterator_free() functions all free() empty nodes as long as no other
// iterator is currently traversing the node. If there are several iterators
// positioned within an empty node, then only the last one to advance out of it
// will free() the node.
typedef struct tree_node_iterator {
struct tree_node_iterator *down;
struct tree_node *node;
unsigned slotnum;
} tree_node_iterator;
typedef struct tree_iterator {
struct tree_node_iterator bottom;
struct tree_node_iterator *stack;
} tree_iterator;
void tree_iterator_start(tree_iterator *it, struct tree_root *root);
void tree_iterator_advance_to(tree_iterator *it, const uint8_t *binary, size_t binary_size_bytes);
void **tree_iterator_get_node(tree_iterator *it);
void tree_iterator_advance(tree_iterator *it);
void tree_iterator_free(tree_iterator *it);
// The following legacy API functions are now implemented using iterators.
// callback function for walking the tree // callback function for walking the tree
// return 0 to continue enumeration, anything else to stop // return 0 to continue enumeration, anything else to stop
// set (*record) to null to indicate that memory has been released and the node should be removed from the tree // set (*record) to null to indicate that memory has been released and the node should be removed from the tree
typedef int (*walk_callback) (void **record, void *context); typedef int (*walk_callback) (void **record, void *context);
// walk the tree, calling walk_callback for each node. // walk the tree, calling walk_callback for each node.
// if binary & bin_length have been supplied, skip all records <= this binary value // if binary & binary_size_bytes have been supplied, skip all records < this binary value
int tree_walk(struct tree_root *root, const uint8_t *binary, size_t bin_length, walk_callback callback, void *context); int tree_walk(struct tree_root *root, const uint8_t *binary, size_t binary_size_bytes, walk_callback callback, void *context);
// walk the tree where nodes match the prefix binary / bin_length // walk the tree where nodes match the prefix binary / binary_size_bytes
int tree_walk_prefix(struct tree_root *root, const uint8_t *binary, size_t bin_length, walk_callback callback, void *context); int tree_walk_prefix(struct tree_root *root, const uint8_t *binary, size_t binary_size_bytes, walk_callback callback, void *context);
// Tree statistics.
struct tree_statistics {
size_t record_count;
size_t node_count;
size_t empty_node_count;
size_t maximum_depth;
};
struct tree_statistics tree_compute_statistics(struct tree_root *root);
#endif // __SERVAL_DNA__NIBBLE_TREE_H #endif // __SERVAL_DNA__NIBBLE_TREE_H

View File

@ -52,7 +52,7 @@ static struct broadcast bpilist[MAX_BPIS];
#define OA_CODE_P2P_ME 0xfc #define OA_CODE_P2P_ME 0xfc
#define OA_CODE_SIGNKEY 0xfb // full sign key of an identity, from which a SID can be derived #define OA_CODE_SIGNKEY 0xfb // full sign key of an identity, from which a SID can be derived
static __thread struct tree_root root={.binary_length=SID_SIZE}; static __thread struct tree_root root={.index_size_bytes=SID_SIZE};
static __thread struct subscriber *my_subscriber=NULL; static __thread struct subscriber *my_subscriber=NULL;

View File

@ -1,6 +1,7 @@
/* /*
Serval testing command line functions Serval testing command line functions
Copyright (C) 2014 Serval Project Inc. Copyright (C) 2014 Serval Project Inc.
Copyright (C) 2018 Flinders University
This program is free software; you can redistribute it and/or This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License modify it under the terms of the GNU General Public License
@ -34,6 +35,7 @@
#include "mem.h" #include "mem.h"
#include "str.h" #include "str.h"
#include "debug.h" #include "debug.h"
#include "nibble_tree.h"
DEFINE_FEATURE(cli_tests); DEFINE_FEATURE(cli_tests);
@ -323,3 +325,207 @@ static int app_config_test(const struct cli_parsed *UNUSED(parsed), struct cli_c
} }
return 0; return 0;
} }
// Nibble Tree test
#define ASSERT(C) do { if (!(C)) FATALF("(%s)", #C); } while (0)
#define ASSERTF(C,F,...) do { if (!(C)) FATALF("(%s) " F, "" #C, ##__VA_ARGS__); } while (0)
struct data {
uint8_t binary[4];
};
struct node {
size_t nbits;
struct data data;
};
static void *create_node_callback(void *context, const uint8_t *binary, size_t binary_size_bytes)
{
ASSERT(binary_size_bytes == sizeof(struct data));
struct node *ret = (struct node *) emalloc_zero(sizeof(struct node));
ASSERT(ret);
ret->data = *(const struct data *)binary;
*(struct node **)context = ret;
return ret;
}
static struct node *create_node(struct tree_root *root, uint32_t index) {
uint8_t binary[4] = { index >> 24, index >> 16, index >> 8, index };
struct node *result = NULL;
struct node *created_node = NULL;
tree_find(root, (void**)&result, binary, 4, create_node_callback, &created_node);
ASSERT(result);
ASSERT(created_node);
ASSERTF(created_node == result, "created_node=%p, result=%p", created_node, result);
ASSERTF(memcmp(result->data.binary, binary, 4) == 0,
"result->data.binary=%s, should be %s",
alloca_tohex(result->data.binary, sizeof result->data.binary),
alloca_tohex(binary, sizeof binary));
DEBUGF(verbose, "created %s -> %p, nbits=%zu", alloca_tohex(binary, sizeof binary), result, result->nbits);
return created_node;
}
static void advance_to(tree_iterator *it, uint32_t index, size_t bytes) {
uint8_t binary[4] = { index >> 24, index >> 16, index >> 8, index };
assert(bytes <= sizeof binary);
DEBUGF(verbose, "advance to %s", alloca_tohex(binary, bytes));
tree_iterator_advance_to(it, binary, bytes);
}
static void assert_current_node(tree_iterator *it, struct node *node) {
DEBUGF(verbose, "assert that current node is %p", node);
struct node **current = (struct node **) tree_iterator_get_node(it);
if (node) {
ASSERT(current);
ASSERTF(*current == node, "*current=%p, should be %p", *current, node);
}
else {
ASSERTF(current == NULL, "*current=%p, should be NULL", *current);
}
}
static void delete_current_node(tree_iterator *it) {
struct node **node = (struct node **) tree_iterator_get_node(it);
ASSERT(node);
ASSERT(*node);
struct data data = (*node)->data; // copy
free(*node);
*node = NULL;
DEBUGF(verbose, "deleted %s", alloca_tohex(data.binary, sizeof data.binary));
}
DEFINE_CMD(app_nibble_tree_test, 0,
"Run nibble tree test",
"test","nibble-tree");
static int app_nibble_tree_test(const struct cli_parsed *UNUSED(parsed), struct cli_context *UNUSED(context))
{
struct tree_root root = {.index_size_bytes = sizeof(struct data)};
struct tree_statistics stats;
tree_iterator it;
// Creation.
struct node *node1 = create_node(&root, 0x12345670);
stats = tree_compute_statistics(&root);
ASSERTF(stats.record_count == 1, "record_count=%zu", stats.record_count);
ASSERTF(stats.node_count == 1, "node_count=%zu", stats.node_count);
ASSERTF(stats.empty_node_count == 0, "empty_node_count=%zu", stats.empty_node_count);
ASSERTF(stats.maximum_depth == 0, "maximum_depth=%zu", stats.maximum_depth);
struct node *node2 = create_node(&root, 0x12345671);
stats = tree_compute_statistics(&root);
ASSERTF(stats.record_count == 2, "record_count=%zu", stats.record_count);
ASSERTF(stats.node_count == 8, "node_count=%zu", stats.node_count);
ASSERTF(stats.empty_node_count == 0, "empty_node_count=%zu", stats.empty_node_count);
ASSERTF(stats.maximum_depth == 7, "maximum_depth=%zu", stats.maximum_depth);
struct node *node3 = create_node(&root, 0x12345672);
stats = tree_compute_statistics(&root);
ASSERTF(stats.record_count == 3, "record_count=%zu", stats.record_count);
ASSERTF(stats.node_count == 8, "node_count=%zu", stats.node_count);
ASSERTF(stats.empty_node_count == 0, "empty_node_count=%zu", stats.empty_node_count);
ASSERTF(stats.maximum_depth == 7, "maximum_depth=%zu", stats.maximum_depth);
struct node *node4 = create_node(&root, 0x01234567);
stats = tree_compute_statistics(&root);
ASSERTF(stats.record_count == 4, "record_count=%zu", stats.record_count);
ASSERTF(stats.node_count == 8, "node_count=%zu", stats.node_count);
ASSERTF(stats.empty_node_count == 0, "empty_node_count=%zu", stats.empty_node_count);
ASSERTF(stats.maximum_depth == 7, "maximum_depth=%zu", stats.maximum_depth);
struct node *node5 = create_node(&root, 0x23456789);
stats = tree_compute_statistics(&root);
ASSERTF(stats.record_count == 5, "record_count=%zu", stats.record_count);
ASSERTF(stats.node_count == 8, "node_count=%zu", stats.node_count);
ASSERTF(stats.empty_node_count == 0, "empty_node_count=%zu", stats.empty_node_count);
ASSERTF(stats.maximum_depth == 7, "maximum_depth=%zu", stats.maximum_depth);
// Simple iteration through all nodes in order.
tree_iterator_start(&it, &root);
assert_current_node(&it, node4);
tree_iterator_advance(&it);
assert_current_node(&it, node1);
tree_iterator_advance(&it);
assert_current_node(&it, node2);
assert_current_node(&it, node2);
tree_iterator_advance(&it);
assert_current_node(&it, node3);
tree_iterator_advance(&it);
assert_current_node(&it, node5);
assert_current_node(&it, node5);
tree_iterator_advance(&it);
assert_current_node(&it, NULL);
assert_current_node(&it, NULL);
tree_iterator_advance(&it);
assert_current_node(&it, NULL);
tree_iterator_free(&it);
// Simple iteration through all nodes after a given starting point.
tree_iterator_start(&it, &root);
advance_to(&it, 0x12345672, 4);
assert_current_node(&it, node3);
tree_iterator_advance(&it);
assert_current_node(&it, node5);
tree_iterator_advance(&it);
assert_current_node(&it, NULL);
tree_iterator_free(&it);
// Simple advance to a prefix starting point.
tree_iterator_start(&it, &root);
advance_to(&it, 0x12340000, 2);
assert_current_node(&it, node1);
tree_iterator_advance(&it);
assert_current_node(&it, node2);
tree_iterator_advance(&it);
assert_current_node(&it, node3);
tree_iterator_advance(&it);
assert_current_node(&it, node5);
tree_iterator_advance(&it);
assert_current_node(&it, NULL);
tree_iterator_free(&it);
// Delete a record from a node, leaving it non-empty.
tree_iterator_start(&it, &root);
advance_to(&it, 0x12345672, 4);
assert_current_node(&it, node3);
delete_current_node(&it);
node3 = NULL;
stats = tree_compute_statistics(&root);
ASSERTF(stats.record_count == 4, "record_count=%zu", stats.record_count);
ASSERTF(stats.node_count == 8, "node_count=%zu", stats.node_count);
ASSERTF(stats.empty_node_count == 0, "empty_node_count=%zu", stats.empty_node_count);
ASSERTF(stats.maximum_depth == 7, "maximum_depth=%zu", stats.maximum_depth);
tree_iterator_free(&it);
// Delete records from a node, leaving it empty. A second iterator positioned in the node
// prevents the node from being free()d until it advances.
tree_iterator it2;
tree_iterator_start(&it2, &root);
advance_to(&it2, 0x12345670, 4);
assert_current_node(&it2, node1);
//
tree_iterator_start(&it, &root);
advance_to(&it, 0x12345670, 4);
assert_current_node(&it, node1);
delete_current_node(&it);
node1 = NULL;
stats = tree_compute_statistics(&root);
ASSERTF(stats.record_count == 3, "record_count=%zu", stats.record_count);
ASSERTF(stats.node_count == 8, "node_count=%zu", stats.node_count);
ASSERTF(stats.empty_node_count == 0, "empty_node_count=%zu", stats.empty_node_count);
ASSERTF(stats.maximum_depth == 7, "maximum_depth=%zu", stats.maximum_depth);
assert_current_node(&it, node2); // node is not empty yet
assert_current_node(&it2, node2);
delete_current_node(&it); // makes the node empty
node2 = NULL;
stats = tree_compute_statistics(&root);
ASSERTF(stats.record_count == 2, "record_count=%zu", stats.record_count);
ASSERTF(stats.node_count == 8, "node_count=%zu", stats.node_count);
ASSERTF(stats.empty_node_count == 1, "empty_node_count=%zu", stats.empty_node_count);
ASSERTF(stats.maximum_depth == 7, "maximum_depth=%zu", stats.maximum_depth);
assert_current_node(&it, node5); // does not free() the empty node
stats = tree_compute_statistics(&root);
ASSERTF(stats.record_count == 2, "record_count=%zu", stats.record_count);
ASSERTF(stats.node_count == 8, "node_count=%zu", stats.node_count);
ASSERTF(stats.empty_node_count == 1, "empty_node_count=%zu", stats.empty_node_count);
ASSERTF(stats.maximum_depth == 7, "maximum_depth=%zu", stats.maximum_depth);
assert_current_node(&it2, node5); // free()s the empty node
stats = tree_compute_statistics(&root);
ASSERTF(stats.record_count == 2, "record_count=%zu", stats.record_count);
ASSERTF(stats.node_count == 1, "node_count=%zu", stats.node_count);
ASSERTF(stats.empty_node_count == 0, "empty_node_count=%zu", stats.empty_node_count);
ASSERTF(stats.maximum_depth == 0, "maximum_depth=%zu", stats.maximum_depth);
tree_iterator_free(&it2);
tree_iterator_free(&it);
return 0;
}