From c7a2fb45730ef60972f73e05d9ccd298382c67c6 Mon Sep 17 00:00:00 2001 From: Andrew Bettison Date: Fri, 20 Oct 2017 17:05:14 +1030 Subject: [PATCH] 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. --- Makefile.in | 4 +- meshmb.c | 2 +- nibble_tree.c | 253 ++++++++++++++++++++++++++++++++-------------- nibble_tree.h | 120 ++++++++++++++++++---- overlay_address.c | 2 +- test_cli.c | 206 +++++++++++++++++++++++++++++++++++++ 6 files changed, 490 insertions(+), 97 deletions(-) diff --git a/Makefile.in b/Makefile.in index f5ab0b23..4cef5d2d 100644 --- a/Makefile.in +++ b/Makefile.in @@ -409,7 +409,9 @@ servaldwrap: $(OBJSDIR_SERVALD)/servalwrap.o @echo LINK $@ @$(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 $@ @$(CC) -Wall -o $@ $^ $(LDFLAGS) diff --git a/meshmb.c b/meshmb.c index 80698be6..b4745570 100644 --- a/meshmb.c +++ b/meshmb.c @@ -640,7 +640,7 @@ int meshmb_open(keyring_identity *id, struct meshmb_feeds **feeds) *feeds = emalloc_zero(sizeof(struct meshmb_feeds)); if (*feeds){ - (*feeds)->root.binary_length = sizeof(rhizome_bid_t); + (*feeds)->root.index_size_bytes = sizeof(rhizome_bid_t); (*feeds)->id = id; rhizome_manifest *m = rhizome_new_manifest(); if (m){ diff --git a/nibble_tree.c b/nibble_tree.c index 9a1e92f7..35ec1bb7 100644 --- a/nibble_tree.c +++ b/nibble_tree.c @@ -1,7 +1,7 @@ /* Serval DNA 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 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 #include #include +#include "lang.h" // for bool_t #include "mem.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)) byte=byte>>4; 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) { - assert(bin_length <= root->binary_length); + assert(binary_size_bytes <= root->index_size_bytes); struct tree_node *ptr = &root->_root_node; if (result) @@ -44,11 +45,11 @@ enum tree_error_reason tree_find(struct tree_root *root, void **result, const ui unsigned pos=0; while(1) { - if (pos>>1 >= bin_length) + if (pos >= binary_size_bytes * 2) return TREE_NOT_UNIQUE; - uint8_t nibble = get_nibble(binary, pos++); - void *node_ptr = ptr->tree_nodes[nibble]; + unsigned nibble = get_nibble(binary, pos++); + void *node_ptr = ptr->slot[nibble]; if (ptr->is_tree & (1<binary_length){ - node_ptr = create_node(context, binary, bin_length); + if (create_node && binary_size_bytes == root->index_size_bytes){ + node_ptr = create_node(context, binary, binary_size_bytes); if (!node_ptr) return TREE_ERROR; struct tree_record *tree_record = (struct tree_record *)node_ptr; - assert(memcmp(tree_record->binary, binary, bin_length) == 0); - tree_record ->tree_depth = pos*4; + assert(memcmp(tree_record->binary, binary, binary_size_bytes) == 0); + tree_record->binary_size_bits = pos*4; if (result) *result = node_ptr; - ptr->tree_nodes[nibble] = node_ptr; + ptr->slot[nibble] = node_ptr; return TREE_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; // 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) *result = node_ptr; return TREE_FOUND; @@ -88,88 +89,190 @@ enum tree_error_reason tree_find(struct tree_root *root, void **result, const ui if (!new_node) return TREE_ERROR; - ptr->tree_nodes[nibble] = new_node; + ptr->slot[nibble] = new_node; ptr->is_tree |= (1<binary, pos); - tree_record->tree_depth = (pos+1)*4; - ptr->tree_nodes[nibble] = node_ptr; + tree_record->binary_size_bits = (pos+1)*4; + ptr->slot[nibble] = node_ptr; } } } -static int walk(struct tree_node *node, unsigned pos, - uint8_t *empty, const uint8_t *binary, size_t bin_length, - walk_callback callback, void *context){ - unsigned i=0, e=16; - int ret=0; - *empty=1; +void tree_iterator_start(tree_iterator *it, struct tree_root *root) +{ + it->stack = &it->bottom; + it->bottom.down = NULL; + it->bottom.node = &root->_root_node; + it->bottom.slotnum = 0; + root->_root_node.ref_count++; +} - if (binary){ - assert(pos*2 < bin_length); - uint8_t n = get_nibble(binary, pos); - for(;itree_nodes[i]){ - *empty=0; - break; +static bool_t push(tree_iterator *it) +{ + assert(it->stack->node->is_tree & (1 << it->stack->slotnum)); + struct tree_node *child = it->stack->node->slot[it->stack->slotnum]; + assert(child); + tree_node_iterator *nit = (tree_node_iterator *) emalloc_zero(sizeof(tree_node_iterator)); + 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++; } } - } - - for (;iis_tree & (1<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<tree_nodes[i]){ - ret = callback(&node->tree_nodes[i], context); + else { + assert(it->stack->slotnum == 16); + pop(it); } - 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 // 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); - uint8_t ignore; - return walk(&root->_root_node, 0, &ignore, binary, bin_length, callback, context); + int ret = 0; + tree_iterator it; + 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); - //TODO if callback free's nodes, collapse parent tree nodes too without needing to walk again? - struct tree_node *node = &root->_root_node; - unsigned pos=0; - // look for a branch of the tree with a partial match - for (; node && posis_tree & (1<tree_nodes[i]; - // only one match? - if (tree_record && memcmp(tree_record->binary, binary, bin_length)==0){ - return callback(&node->tree_nodes[i], context); - } - return 0; - } - node = node->tree_nodes[i]; - } - // walk the whole branch - uint8_t ignore; - return walk(node, pos+1, &ignore, NULL, 0, callback, context); + assert(binary); + assert(binary_size_bytes <= root->index_size_bytes); + int ret = 0; + tree_iterator it; + tree_iterator_start(&it, root); + tree_iterator_advance_to(&it, binary, binary_size_bytes); + void **node; + while ( (node = tree_iterator_get_node(&it)) + && memcmp(((struct tree_record *)*node)->binary, binary, binary_size_bytes) == 0 + && (ret = callback(node, context)) == 0) + tree_iterator_advance(&it); + tree_iterator_free(&it); + return ret; +} + +static void walk_statistics(struct tree_node *node, unsigned depth, struct tree_statistics *stats) +{ + stats->node_count++; + if (depth > stats->maximum_depth) + 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; } diff --git a/nibble_tree.h b/nibble_tree.h index ae4d8de0..f5caa97f 100644 --- a/nibble_tree.h +++ b/nibble_tree.h @@ -1,7 +1,7 @@ /* Serval DNA 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 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 // for uint8_t, size_t -struct tree_record{ - // number of bits of the binary value, to uniquely identify this record within the tree's current contents - size_t tree_depth; +// Every record in a nibble tree has the following structure: +// - a count of the number of bits in the binary index +// - 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]; }; -// each node has 16 slots based on the next 4 bits of the binary value -// each slot either points to another tree node or a data record -struct tree_node{ - // bit flags for the type of object each element points to +// Each node in the nibble tree has 16 slots based on the next 4 bits of the +// binary value. +struct tree_node { + // 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; - 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{ - size_t binary_length; +// The root of a nibble tree specifies the binary index size, in bytes, and +// contains the root node. +struct tree_root { + size_t index_size_bytes; struct tree_node _root_node; }; @@ -49,28 +65,94 @@ enum tree_error_reason { TREE_FOUND = 0 }; - // allocate a new record and return it // 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 -// 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 // 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); +// 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 // 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 typedef int (*walk_callback) (void **record, void *context); // walk the tree, calling walk_callback for each node. -// if binary & bin_length 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); +// 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 binary_size_bytes, walk_callback callback, void *context); -// walk the tree where nodes match the prefix binary / bin_length -int tree_walk_prefix(struct tree_root *root, const uint8_t *binary, size_t bin_length, walk_callback callback, void *context); +// 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 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 diff --git a/overlay_address.c b/overlay_address.c index 1257d5b0..abfc638b 100644 --- a/overlay_address.c +++ b/overlay_address.c @@ -52,7 +52,7 @@ static struct broadcast bpilist[MAX_BPIS]; #define OA_CODE_P2P_ME 0xfc #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; diff --git a/test_cli.c b/test_cli.c index 75b16003..9f52a774 100644 --- a/test_cli.c +++ b/test_cli.c @@ -1,6 +1,7 @@ /* Serval testing command line functions Copyright (C) 2014 Serval Project Inc. + Copyright (C) 2018 Flinders University This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License @@ -34,6 +35,7 @@ #include "mem.h" #include "str.h" #include "debug.h" +#include "nibble_tree.h" DEFINE_FEATURE(cli_tests); @@ -323,3 +325,207 @@ static int app_config_test(const struct cli_parsed *UNUSED(parsed), struct cli_c } 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; +}