Commit abbd5e58 authored by Marek Vavruša's avatar Marek Vavruša

lib/generic: added k-v storage to crit-bit, cleanup

interface split to ‘map’ and ‘set’
added documentation
array: added array_tail() operation
parent c6fdfc59
......@@ -12,7 +12,7 @@ MODULEDIR := $(LIBDIR)/kdns_modules
# Tools
CC ?= cc
CFLAGS += -std=c99 -D_GNU_SOURCE -Wall -fPIC -I$(abspath .)
CFLAGS += -std=c99 -D_GNU_SOURCE -Wall -fPIC -I$(abspath .) -I$(abspath lib/generic)
CFLAGS += -DPACKAGE_VERSION="\"$(MAJOR).$(MINOR)\"" -DPREFIX="\"$(PREFIX)\"" -DMODULEDIR="\"$(MODULEDIR)\""
RM := rm -f
LN := ln -s
......
......@@ -9,10 +9,14 @@ as long as it comes with a test case in `tests/test_generics.c`.
Data structures
~~~~~~~~~~~~~~~
- Dynamic arrays
* ``array`` - a set of simple macros to make working with dynamic arrays easier.
* ``set`` - a `Crit-bit tree`_ simple implementation (public domain) that comes with tests.
* ``map`` - key-value map implemented on top of ``set``.
API reference and examples
~~~~~~~~~~~~~~~~~~~~~~~~~~
.. doxygengroup:: generics
:project: libkresolve
.. _`Crit-bit tree`: http://cr.yp.to/critbit.html
\ No newline at end of file
......@@ -101,6 +101,13 @@
* @return 0 on success, <0 on failure
*/
#define array_del(array, i) \
(i) < (array).len ? ((array).at[i] = (array).at[(array).len], (array).len -= 1, 0) : -1
(i) < (array).len ? ((array).len -= 1,(array).at[i] = (array).at[(array).len], 0) : -1
/**
* Return last element of the array.
* @warning Undefined if the array is empty.
*/
#define array_tail(array) \
(array).at[(array).len - 1]
/** @} */
/*
* critbit89 - A crit-bit tree implementation for strings in C89
* Written by Jonas Gehring <jonas@jgehring.net>
*/
#ifndef CRITBIT_H_
#define CRITBIT_H_
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
/*! Main data structure */
typedef struct {
void *root;
void *(*malloc)(size_t size, void *baton);
void (*free)(void *ptr, void *baton);
void *baton; /*! Passed to malloc() and free() */
} cb_tree_t;
/*! Creates an new, empty critbit tree */
extern cb_tree_t cb_tree_make();
/*! Returns non-zero if tree contains str */
extern int cb_tree_contains(cb_tree_t *tree, const char *str);
/*! Inserts str into tree, returns 0 on suceess */
extern int cb_tree_insert(cb_tree_t *tree, const char *str);
/*! Deletes str from the tree, returns 0 on suceess */
extern int cb_tree_delete(cb_tree_t *tree, const char *str);
/*! Clears the given tree */
extern void cb_tree_clear(cb_tree_t *tree);
/*! Calls callback for all strings in tree with the given prefix */
extern int cb_tree_walk_prefixed(cb_tree_t *tree, const char *prefix,
int (*callback)(const char *, void *), void *baton);
#ifdef __cplusplus
}
#endif
#endif /* CRITBIT_H_ */
\ No newline at end of file
/*
* critbit89 - A crit-bit tree implementation for strings in C89
* Written by Jonas Gehring <jonas@jgehring.net>
*/
/*
* Implemented key-value storing by Marek Vavrusa <marek.vavrusa@nic.cz>
*
* The code makes the assumption that malloc returns pointers aligned at at
* least a two-byte boundary. Since the C standard requires that malloc return
* pointers that can store any type, there are no commonly-used toolchains for
* which this assumption is false.
*
* See https://github.com/agl/critbit/blob/master/critbit.pdf for reference.
*/
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include "critbit.h"
#include "map.h"
#ifdef _MSC_VER /* MSVC */
typedef unsigned __int8 uint8_t;
......@@ -28,6 +29,10 @@
#include <stdint.h>
#endif
typedef struct {
void* value;
uint8_t key[];
} cb_data_t;
typedef struct {
void *child[2];
......@@ -35,6 +40,18 @@ typedef struct {
uint8_t otherbits;
} cb_node_t;
/* Return true if ptr is internal node. */
static inline int ref_is_internal(uint8_t *p)
{
return 1 & (intptr_t)p;
}
/* Get internal node. */
static inline cb_node_t *ref_get_internal(uint8_t *p)
{
return (cb_node_t *)(p - 1);
}
/* Standard memory allocation functions */
static void *malloc_std(size_t size, void *baton) {
(void)baton; /* Prevent compiler warnings */
......@@ -47,24 +64,27 @@ static void free_std(void *ptr, void *baton) {
}
/* Static helper functions */
static void cbt_traverse_delete(cb_tree_t *tree, void *top)
static void cbt_traverse_delete(map_t *map, void *top)
{
uint8_t *p = top;
if (1 & (intptr_t)p) {
cb_node_t *q = (void *)(p - 1);
cbt_traverse_delete(tree, q->child[0]);
cbt_traverse_delete(tree, q->child[1]);
tree->free(q, tree->baton);
if (ref_is_internal(p)) {
cb_node_t *q = ref_get_internal(p);
cbt_traverse_delete(map, q->child[0]);
cbt_traverse_delete(map, q->child[1]);
map->free(q, map->baton);
} else {
tree->free(p, tree->baton);
map->free(p, map->baton);
}
}
static int cbt_traverse_prefixed(uint8_t *top,
static int cbt_traverse_prefixed(void *top,
int (*callback)(const char *, void *), void *baton)
{
if (1 & (intptr_t)top) {
cb_node_t *q = (void *)(top - 1);
uint8_t *p = top;
cb_data_t *x = (cb_data_t *)top;
if (ref_is_internal(p)) {
cb_node_t *q = ref_get_internal(p);
int ret = 0;
ret = cbt_traverse_prefixed(q->child[0], callback, baton);
......@@ -78,34 +98,49 @@ static int cbt_traverse_prefixed(uint8_t *top,
return 0;
}
return (callback)((const char *)top, baton);
return (callback)((const char *)x->key, baton);
}
static cb_data_t *cbt_make_data(map_t *map, const uint8_t *str, size_t len, void *value)
{
cb_data_t *x = map->malloc(sizeof(cb_data_t) + len, map->baton);
if (x != NULL) {
x->value = value;
memcpy(x->key, str, len);
}
return x;
}
/*! Creates a new, empty critbit tree */
cb_tree_t cb_tree_make()
/*! Creates a new, empty critbit map */
map_t map_make(void)
{
cb_tree_t tree;
tree.root = NULL;
tree.malloc = &malloc_std;
tree.free = &free_std;
tree.baton = NULL;
return tree;
map_t map;
map.root = NULL;
map.malloc = &malloc_std;
map.free = &free_std;
map.baton = NULL;
return map;
}
/*! Returns non-zero if tree contains str */
int cb_tree_contains(cb_tree_t *tree, const char *str)
/*! Returns non-zero if map contains str */
int map_contains(map_t *map, const char *str)
{
return map_get(map, str) != NULL;
}
void *map_get(map_t *map, const char *str)
{
const uint8_t *ubytes = (void *)str;
const size_t ulen = strlen(str);
uint8_t *p = tree->root;
uint8_t *p = map->root;
cb_data_t *x = NULL;
if (p == NULL) {
return 0;
return NULL;
}
while (1 & (intptr_t)p) {
cb_node_t *q = (void *)(p - 1);
while (ref_is_internal(p)) {
cb_node_t *q = ref_get_internal(p);
uint8_t c = 0;
int direction;
......@@ -117,34 +152,38 @@ int cb_tree_contains(cb_tree_t *tree, const char *str)
p = q->child[direction];
}
return (strcmp(str, (const char *)p) == 0);
x = (cb_data_t *)p;
if (strcmp(str, (const char *)x->key) == 0) {
return x->value;
}
return NULL;
}
/*! Inserts str into tree, returns 0 on success */
int cb_tree_insert(cb_tree_t *tree, const char *str)
/*! Inserts str into map, returns 0 on success */
int map_set(map_t *map, const char *str, void *value)
{
const uint8_t *const ubytes = (void *)str;
const size_t ulen = strlen(str);
uint8_t *p = tree->root;
uint8_t c, *x;
uint32_t newbyte;
uint32_t newotherbits;
int direction, newdirection;
cb_node_t *newnode;
void **wherep;
uint8_t *p = map->root;
uint8_t c = 0, *x = NULL;
uint32_t newbyte = 0;
uint32_t newotherbits = 0;
int direction = 0, newdirection = 0;
cb_node_t *newnode = NULL;
cb_data_t *data = NULL;
void **wherep = NULL;
if (p == NULL) {
x = tree->malloc(ulen + 1, tree->baton);
if (x == NULL) {
map->root = cbt_make_data(map, (const uint8_t *)str, ulen + 1, value);
if (map->root == NULL) {
return ENOMEM;
}
memcpy(x, str, ulen + 1);
tree->root = x;
return 0;
}
while (1 & (intptr_t)p) {
cb_node_t *q = (void *)(p - 1);
while (ref_is_internal(p)) {
cb_node_t *q = ref_get_internal(p);
c = 0;
if (q->byte < ulen) {
c = ubytes[q->byte];
......@@ -154,15 +193,16 @@ int cb_tree_insert(cb_tree_t *tree, const char *str)
p = q->child[direction];
}
data = (cb_data_t *)p;
for (newbyte = 0; newbyte < ulen; ++newbyte) {
if (p[newbyte] != ubytes[newbyte]) {
newotherbits = p[newbyte] ^ ubytes[newbyte];
if (data->key[newbyte] != ubytes[newbyte]) {
newotherbits = data->key[newbyte] ^ ubytes[newbyte];
goto different_byte_found;
}
}
if (p[newbyte] != 0) {
newotherbits = p[newbyte];
if (data->key[newbyte] != 0) {
newotherbits = data->key[newbyte];
goto different_byte_found;
}
return 1;
......@@ -172,35 +212,34 @@ int cb_tree_insert(cb_tree_t *tree, const char *str)
newotherbits |= newotherbits >> 2;
newotherbits |= newotherbits >> 4;
newotherbits = (newotherbits & ~(newotherbits >> 1)) ^ 255;
c = p[newbyte];
c = data->key[newbyte];
newdirection = (1 + (newotherbits | c)) >> 8;
newnode = tree->malloc(sizeof(cb_node_t), tree->baton);
newnode = map->malloc(sizeof(cb_node_t), map->baton);
if (newnode == NULL) {
return ENOMEM;
}
x = tree->malloc(ulen + 1, tree->baton);
x = (uint8_t *)cbt_make_data(map, ubytes, ulen + 1, value);
if (x == NULL) {
tree->free(newnode, tree->baton);
map->free(newnode, map->baton);
return ENOMEM;
}
memcpy(x, ubytes, ulen + 1);
newnode->byte = newbyte;
newnode->otherbits = newotherbits;
newnode->child[1 - newdirection] = x;
/* Insert into tree */
wherep = &tree->root;
/* Insert into map */
wherep = &map->root;
for (;;) {
cb_node_t *q;
p = *wherep;
if (!(1 & (intptr_t)p)) {
if (!ref_is_internal(p)) {
break;
}
q = (void *)(p - 1);
q = ref_get_internal(p);
if (q->byte > newbyte) {
break;
}
......@@ -221,25 +260,26 @@ int cb_tree_insert(cb_tree_t *tree, const char *str)
return 0;
}
/*! Deletes str from the tree, returns 0 on success */
int cb_tree_delete(cb_tree_t *tree, const char *str)
/*! Deletes str from the map, returns 0 on success */
int map_del(map_t *map, const char *str)
{
const uint8_t *ubytes = (void *)str;
const size_t ulen = strlen(str);
uint8_t *p = tree->root;
void **wherep = 0, **whereq = 0;
cb_node_t *q = 0;
uint8_t *p = map->root;
void **wherep = NULL, **whereq = NULL;
cb_node_t *q = NULL;
cb_data_t *data = NULL;
int direction = 0;
if (tree->root == NULL) {
if (map->root == NULL) {
return 1;
}
wherep = &tree->root;
wherep = &map->root;
while (1 & (intptr_t)p) {
while (ref_is_internal(p)) {
uint8_t c = 0;
whereq = wherep;
q = (void *)(p - 1);
q = ref_get_internal(p);
if (q->byte < ulen) {
c = ubytes[q->byte];
......@@ -249,45 +289,47 @@ int cb_tree_delete(cb_tree_t *tree, const char *str)
p = *wherep;
}
if (strcmp(str, (const char *)p) != 0) {
data = (cb_data_t *)p;
if (strcmp(str, (const char *)data->key) != 0) {
return 1;
}
tree->free(p, tree->baton);
map->free(p, map->baton);
if (!whereq) {
tree->root = NULL;
map->root = NULL;
return 0;
}
*whereq = q->child[1 - direction];
tree->free(q, tree->baton);
map->free(q, map->baton);
return 0;
}
/*! Clears the given tree */
void cb_tree_clear(cb_tree_t *tree)
/*! Clears the given map */
void map_clear(map_t *map)
{
if (tree->root) {
cbt_traverse_delete(tree, tree->root);
if (map->root) {
cbt_traverse_delete(map, map->root);
}
tree->root = NULL;
map->root = NULL;
}
/*! Calls callback for all strings in tree with the given prefix */
int cb_tree_walk_prefixed(cb_tree_t *tree, const char *prefix,
/*! Calls callback for all strings in map with the given prefix */
int map_walk_prefixed(map_t *map, const char *prefix,
int (*callback)(const char *, void *), void *baton)
{
const uint8_t *ubytes = (void *)prefix;
const size_t ulen = strlen(prefix);
uint8_t *p = tree->root;
uint8_t *top = p;
uint8_t *p = map->root;
uint8_t *top = (uint8_t *)p;
cb_data_t *data = NULL;
if (p == NULL) {
return 0;
}
while (1 & (intptr_t)p) {
cb_node_t *q = (void *)(p - 1);
while (ref_is_internal(p)) {
cb_node_t *q = ref_get_internal(p);
uint8_t c = 0;
int direction;
......@@ -302,9 +344,9 @@ int cb_tree_walk_prefixed(cb_tree_t *tree, const char *prefix,
}
}
if (strlen((const char *)p) < ulen || memcmp(p, prefix, ulen) != 0) {
/* No strings match */
return 0;
data = (cb_data_t *)p;
if (strlen((const char *)data->key) < ulen || memcmp(data->key, prefix, ulen) != 0) {
return 0; /* No strings match */
}
return cbt_traverse_prefixed(top, callback, baton);
......
/*
* critbit89 - A crit-bit map implementation for strings in C89
* Written by Jonas Gehring <jonas@jgehring.net>
*/
/**
* Generics - A Crit-bit tree 'map' implementation.
*
* @warning If the user provides a custom allocator, it must return addresses aligned to 2B boundary.
*
* Example usage:
*
* map_t map = map_make();
*
* // Custom allocator (optional)
* map.malloc = &mymalloc;
* map.baton = &mymalloc_context;
*
* // Insert keys
* if (map_set(&map, "princess") != 0 ||
* map_set(&map, "prince") != 0 ||
* map_set(&map, "leia") != 0) {
* fail();
* }
*
* // Test membership
* if (map_contains(&map, "leia")) {
* success();
* }
*
* // Prefix search
* int i = 0;
* int count(const char *s, void *n) { (*(int *)n)++; return 0; }
* if (map_walk_prefixed(map, "princ", count, &i) == 0) {
* printf("%d matches\n", i);
* }
*
* // Delete
* if (map_del(&map, "badkey") != 0) {
* fail(); // No such key
* }
*
* // Clear the map
* map_clear(&map);
*
* \addtogroup generics
* @{
*/
#pragma once
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
/*! Main data structure */
typedef struct {
void *root;
void *(*malloc)(size_t size, void *baton);
void (*free)(void *ptr, void *baton);
void *baton; /*! Passed to malloc() and free() */
} map_t;
/*! Creates an new, empty critbit map */
map_t map_make(void);
/*! Returns non-zero if map contains str */
int map_contains(map_t *map, const char *str);
/*! Returns value if map contains str */
void *map_get(map_t *map, const char *str);
/*! Inserts str into map, returns 0 on suceess */
int map_set(map_t *map, const char *str, void *val);
/*! Deletes str from the map, returns 0 on suceess */
int map_del(map_t *map, const char *str);
/*! Clears the given map */
void map_clear(map_t *map);
/*! Calls callback for all strings in map with the given prefix */
int map_walk_prefixed(map_t *map, const char *prefix,
int (*callback)(const char *, void *), void *baton);
#ifdef __cplusplus
}
#endif
/** @} */
\ No newline at end of file
/**
* Generics - A Crit-bit set implementation.
*
* @note The API is based on @fn map.h, see it for more examples.
*
* Example usage:
*
* set_t set = set_make();
*
* // Insert keys
* if (set_add(&set, "princess") != 0 ||
* set_add(&set, "prince") != 0 ||
* set_add(&set, "leia") != 0) {
* fail();
* }
*
* // Test membership
* if (set_contains(&set, "leia")) {
* success();
* }
*
* // Prefix search
* int i = 0;
* int count(const char *s, void *n) { (*(int *)n)++; return 0; }
* if (set_walk_prefixed(set, "princ", count, &i) == 0) {
* printf("%d matches\n", i);
* }
*
* // Delete
* if (set_del(&set, "badkey") != 0) {
* fail(); // No such key
* }
*
* // Clear the set
* set_clear(&set);
*
* \addtogroup generics
* @{
*/
#pragma once
#include <stddef.h>
#include "map.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef map_t set_t;
typedef int (set_walk_cb)(const char *, void *);
/*! Creates an new, empty critbit set */
#define set_make() \
map_make()
/*! Returns non-zero if set contains str */
#define set_contains(set, str) \
map_contains((set), (str))
/*! Inserts str into set, returns 0 on suceess */
#define set_add(set, str) \
map_set((set), (str), (void *)1)
/*! Deletes str from the set, returns 0 on suceess */
#define set_del(set, str) \
map_del((set), (str))
/*! Clears the given set */
#define set_clear(set) \
map_clear(set)
/*! Calls callback for all strings in set with the given prefix */
#define set_walk_prefixed(set, prefix, callback, baton) \
map_walk_prefixed((set), (prefix), (callback), baton)
#ifdef __cplusplus
}
#endif
/** @} */
\ No newline at end of file
libkresolve_SOURCES := \
lib/generic/critbit.c \
lib/generic/map.c \
lib/layer/iterate.c \
lib/layer/itercache.c \
lib/utils.c \
......@@ -12,7 +12,8 @@ libkresolve_SOURCES := \
libkresolve_HEADERS := \
lib/generic/array.h \
lib/generic/critbit.h \
lib/generic/map.h \
lib/generic/set.h \
lib/layer.h \
lib/utils.h \
lib/nsrep.h \
......
......@@ -3,8 +3,9 @@
#
tests_BIN := \
test_critbit \
test_generics \
test_set \
test_map \
test_array \
test_utils \
test_module \
test_rplan \
......
......@@ -29,6 +29,7 @@ static void test_array(void **state)
assert_int_equal(arr.len, 0);
assert_int_equal(array_push(arr, 5), 0);
assert_int_equal(arr.at[0], 5);
assert_int_equal(array_tail(arr), 5);
array_clear(arr);
/* Reserve capacity and fill. */
......
/* Copyright (C) 2014 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "tests/test.h"
#include "lib/generic/map.h"
/*
* Sample dictionary
*/
static const char *dict[] = {
"catagmatic", "prevaricator", "statoscope", "workhand", "benzamide",
"alluvia", "fanciful", "bladish", "Tarsius", "unfast", "appropriative",
"seraphically", "monkeypod", "deflectometer", "tanglesome", "zodiacal",
"physiologically", "economizer", "forcepslike", "betrumpet",
"Danization", "broadthroat", "randir", "usherette", "nephropyosis",
"hematocyanin", "chrysohermidin", "uncave", "mirksome", "podophyllum",
"siphonognathous", "indoor", "featheriness", "forwardation",
"archruler", "soricoid", "Dailamite", "carmoisin", "controllability",
"unpragmatical", "childless", "transumpt", "productive",
"thyreotoxicosis", "oversorrow", "disshadow", "osse", "roar",
"pantomnesia", "talcer", "hydrorrhoea", "Satyridae", "undetesting",
"smoothbored", "widower", "sivathere", "pendle", "saltation",
"autopelagic", "campfight", "unexplained", "Macrorhamphosus",
"absconsa", "counterflory", "interdependent", "triact", "reconcentration",
"oversharpness", "sarcoenchondroma", "superstimulate", "assessory",
"pseudepiscopacy", "telescopically", "ventriloque", "politicaster",
"Caesalpiniaceae", "inopportunity", "Helion", "uncompatible",
"cephaloclasia", "oversearch", "Mahayanistic", "quarterspace",