diff --git a/lib/generic/array.h b/lib/generic/array.h index 8fa93809fbe51e8f4a08d0972c98ba199b8112bb..96dcf19f0ffd8fa343a0e812e365db58b5fd5892 100644 --- a/lib/generic/array.h +++ b/lib/generic/array.h @@ -21,7 +21,7 @@ * Be aware of that, as direct usage of the macros in the evaluating macros * may lead to different expectations, i.e. * - * MIN(array_push(arr, val)) + * MIN(array_push(arr, val), other) * * May evaluate the code twice, leading to unexpected behaviour. * This is a price to pay for absence of proper generics. @@ -58,9 +58,40 @@ */ #pragma once +#include <stdlib.h> -/** @todo Implement mreserve over custom memory context. */ -#include <libknot/internal/mem.h> +/** Simplified Qt containers growth strategy. */ +static inline size_t array_next_count(size_t want) +{ + if (want < 2048) { + return (want < 20) ? want + 4 : want * 2; + } else { + return want + 2048; + } +} + +/** @internal Incremental memory reservation */ +static inline int array_std_reserve(void *baton, char **mem, size_t elm_size, size_t want, size_t *have) +{ + if (*have >= want) { + return 0; + } + /* Simplified Qt containers growth strategy */ + size_t next_size = array_next_count(want); + void *mem_new = realloc(*mem, next_size * elm_size); + if (mem_new != NULL) { + *mem = mem_new; + *have = next_size; + return 0; + } + return -1; +} + +/** @internal Wrapper for stdlib free. */ +static inline void array_std_free(void *baton, void *p) +{ + free(p); +} /** Declare an array structure. */ #define array_t(type) struct {type * at; size_t len; size_t cap; } @@ -70,14 +101,20 @@ /** Free and zero-initialize the array. */ #define array_clear(array) \ - free((array).at), array_init(array) + array_clear_mm(array, array_std_free, NULL) +/** @internal Clear array with a callback. */ +#define array_clear_mm(array, free, baton) \ + (free)((baton), (array).at), array_init(array) /** * Reserve capacity up to 'n' bytes. * @return >=0 if success */ #define array_reserve(array, n) \ - mreserve((char **) &(array).at, sizeof((array).at[0]), n, 0, &(array).cap) + array_reserve_mm(array, n, array_std_reserve, NULL) +/** @internal Reserve capacity using callback. */ +#define array_reserve_mm(array, n, reserve, baton) \ + (reserve)((baton), (char **) &(array).at, sizeof((array).at[0]), (n), &(array).cap) /** * Push value at the end of the array, resize it if necessary. @@ -86,7 +123,7 @@ */ #define array_push(array, val) \ (array).len < (array).cap ? ((array).at[(array).len] = val, (array).len++) \ - : (array_reserve(array, ((array).cap + 1) * 2) < 0 ? -1 \ + : (array_reserve(array, ((array).cap + 1)) < 0 ? -1 \ : ((array).at[(array).len] = val, (array).len++)) /** diff --git a/lib/generic/pack.h b/lib/generic/pack.h new file mode 100644 index 0000000000000000000000000000000000000000..171b7f1d3ca6286a40c2c043f88e0e9173798899 --- /dev/null +++ b/lib/generic/pack.h @@ -0,0 +1,118 @@ +/* Copyright (C) 2015 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/>. + */ + +/** + * Generics - array of lenght-prefixed packed objects + * + * Each object is prefixed by item length, unlike array this structure + * permits variable-length data. It is also equivallent to forward-only list + * backed by an array. + * + * @note Maximum object size is 2^16 bytes, @see pack_objlen_t + * + * Example usage: + * + * pack_t pack; + * pack_init(pack); + * + * // Reserve 2 objects, 6 bytes total + * pack_reserve(pack, 2, 4 + 2); + * + * // Push 2 objects + * pack_obj_push(pack, U8("jedi"), 4) + * pack_obj_push(pack, U8("\xbe\xef"), 2); + * + * // Iterate length-value pairs + * uint8_t *it = pack_head(pack); + * while (it != pack_tail(pack)) { + * uint8_t *val = pack_obj_val(it); + * it = pack_obj_next(it); + * } + * + * pack_clear(pack); + * + * \addtogroup generics + * @{ + */ + +#include <stdint.h> +#include <string.h> +#include "array.h" + +/** Packed object length type. */ +typedef uint16_t pack_objlen_t; + +/** Pack is defined as an array of bytes */ +typedef array_t(uint8_t) pack_t; + +/** Zero-initialize the pack. */ +#define pack_init(pack) \ + array_init(pack) +/** Free and the pack. */ +#define pack_clear(pack) \ + array_clear(pack) +/** @internal Clear pack with a callback. */ +#define pack_clear_mm(pack, free, baton) \ + array_clear_mm(pack, array_std_free, baton) +/** Incrementally reserve objects in the pack. */ +#define pack_reserve(pack, objs_count, objs_len) \ + pack_reserve_mm((pack), (objs_count), (objs_len), array_std_reserve, NULL) +/** @internal Reservation with a callback. */ +#define pack_reserve_mm(pack, objs_count, objs_len, reserve, baton) \ + array_reserve_mm((pack), (pack).len + (sizeof(pack_objlen_t)*(objs_count) + (objs_len)), (reserve), (baton)) +/** Return pointer to first packed object. */ +#define pack_head(pack) \ + &((pack).at[0]) +/** Return pack end pointer. */ +#define pack_tail(pack) \ + &((pack).at[(pack).len]) + +/** Return packed object length. */ +static inline pack_objlen_t pack_obj_len(uint8_t *it) +{ + pack_objlen_t len = 0; + memcpy(&len, it, sizeof(len)); + return len; +} + +/** Return packed object value. */ +static inline uint8_t *pack_obj_val(uint8_t *it) +{ + return it + sizeof(pack_objlen_t); +} + +/** Return pointer to next packed object. */ +static inline uint8_t *pack_obj_next(uint8_t *it) +{ + return pack_obj_val(it) + pack_obj_len(it); +} + +/** Push object to the end of the pack + * @return 0 on success, negative number on failure + */ +static inline int pack_obj_push(pack_t *pack, const uint8_t *obj, pack_objlen_t len) +{ + uint8_t *endp = pack_tail(*pack); + size_t packed_len = len + sizeof(len); + if (pack == NULL || (pack->len + packed_len) > pack->cap) { + return -1; + } + + memcpy(endp, (char *)&len, sizeof(len)); + memcpy(endp + sizeof(len), obj, len); + pack->len += packed_len; + return 0; +} \ No newline at end of file diff --git a/tests/test_pack.c b/tests/test_pack.c new file mode 100644 index 0000000000000000000000000000000000000000..27c09bffdea585b62964162bd58b67e9869fa4b9 --- /dev/null +++ b/tests/test_pack.c @@ -0,0 +1,64 @@ +/* Copyright (C) 2015 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 "tests/test.h" +#include "lib/generic/pack.h" + +#define U8(x) (const uint8_t *)(x) +mm_ctx_t global_mm; + +static void test_pack_std(void **state) +{ + int ret = 0; + pack_t pack; + pack_init(pack); + assert_int_equal(pack.len, 0); + + /* Push without reservation. */ + assert_int_not_equal(pack_obj_push(&pack, U8(""), 1), 0); + + /* Reserve capacity and fill. */ + assert_true(pack_reserve(pack, 10, 10 * 2) >= 0); + for (unsigned i = 0; i < 10; ++i) { + ret = pack_obj_push(&pack, U8("de"), 2); + assert_true(ret >= 0); + } + + /* Iterate */ + uint8_t *it = pack_head(pack); + assert_non_null(it); + unsigned count = 0; + while (it != pack_tail(pack)) { + assert_int_equal(pack_obj_len(it), 2); + assert_true(memcmp(pack_obj_val(it), "de", 2) == 0); + it = pack_obj_next(it); + count += 1; + } + + + pack_clear(pack); +} + +int main(void) +{ + test_mm_ctx_init(&global_mm); + + const UnitTest tests[] = { + unit_test(test_pack_std), + }; + + return run_tests(tests); +} diff --git a/tests/unit.mk b/tests/unit.mk index d6e09da09444103998632b8e76d408dc06408b65..d8ac592e79ef0095b506df2c1d4e16c12efdcefe 100644 --- a/tests/unit.mk +++ b/tests/unit.mk @@ -6,6 +6,7 @@ tests_BIN := \ test_set \ test_map \ test_array \ + test_pack \ test_utils \ test_module \ test_rplan \