subdir('ed25519')
subdir('chacha-poly1305')
-src_lib_tinc = [
+src_lib_common = [
'conf.c',
'dropin.c',
'keys.c',
'ifconfig.c',
'info.c',
'invitation.c',
- 'tincctl.c',
'top.c',
]
'raw_socket_device.c',
'route.c',
'subnet.c',
- 'tincd.c',
]
cc_flags_tincd = cc_flags
endforeach
if not cdata.has('HAVE_GETOPT_H') or not cc.has_function('getopt_long', prefix: have_prefix, args: cc_defs)
- src_lib_tinc += ['getopt.c', 'getopt1.c']
+ src_lib_common += ['getopt.c', 'getopt1.c']
endif
if not opt_miniupnpc.disabled()
build_by_default: false,
)
-deps_lib_tinc = [deps_common, dep_crypto]
+deps_lib_common = [deps_common, dep_crypto]
+deps_tinc += deps_lib_common
+deps_tincd += deps_lib_common
+
+lib_common = static_library(
+ 'common',
+ sources: src_lib_common,
+ dependencies: deps_lib_common,
+ link_with: [lib_ed25519, lib_chacha_poly, lib_crypto],
+ implicit_include_directories: false,
+ include_directories: inc_conf,
+ build_by_default: false,
+)
lib_tinc = static_library(
'tinc',
- sources: src_lib_tinc,
- dependencies: deps_lib_tinc,
- link_with: [lib_ed25519, lib_chacha_poly, lib_crypto],
+ sources: src_tinc,
+ dependencies: deps_tinc,
+ link_with: lib_common,
+ implicit_include_directories: false,
+ include_directories: inc_conf,
+ build_by_default: false,
+)
+
+lib_tincd = static_library(
+ 'tincd',
+ sources: src_tincd,
+ dependencies: deps_tincd,
+ link_with: lib_common,
+ c_args: cc_flags_tincd,
implicit_include_directories: false,
include_directories: inc_conf,
build_by_default: false,
exe_tinc = executable(
'tinc',
- sources: src_tinc,
- dependencies: [deps_lib_tinc, deps_tinc],
+ sources: 'tincctl.c',
+ dependencies: deps_tinc,
link_with: lib_tinc,
implicit_include_directories: false,
include_directories: inc_conf,
exe_tincd = executable(
'tincd',
- sources: src_tincd,
- dependencies: [deps_lib_tinc, deps_tincd],
- link_with: lib_tinc,
+ sources: 'tincd.c',
+ dependencies: deps_tincd,
+ link_with: lib_tincd,
c_args: cc_flags_tincd,
implicit_include_directories: false,
include_directories: inc_conf,
exe_sptps_test = executable(
'sptps_test',
sources: 'sptps_test.c',
- dependencies: deps_lib_tinc,
- link_with: lib_tinc,
+ dependencies: deps_lib_common,
+ link_with: lib_common,
implicit_include_directories: false,
include_directories: inc_conf,
build_by_default: false,
exe_sptps_keypair = executable(
'sptps_keypair',
sources: 'sptps_keypair.c',
- dependencies: deps_lib_tinc,
- link_with: lib_tinc,
+ dependencies: deps_lib_common,
+ link_with: lib_common,
implicit_include_directories: false,
include_directories: inc_conf,
build_by_default: false,
exe_sptps_speed = executable(
'sptps_speed',
sources: 'sptps_speed.c',
- dependencies: [deps_lib_tinc, dep_rt],
- link_with: lib_tinc,
+ dependencies: [deps_lib_common, dep_rt],
+ link_with: lib_common,
implicit_include_directories: false,
include_directories: inc_conf,
build_by_default: false,
--- /dev/null
+#include "unittest.h"
+#include "../../src/splay_tree.h"
+
+typedef struct node_t {
+ int id;
+} node_t;
+
+// We cannot use test_malloc / test_free here, because the library seems to be
+// checking for leaks right after running each test, before doing teardown,
+// which results in a bunch of spurious test failures. We rely on teardown to
+// clean up after us. Valgrind and ASAN show no leaks.
+static node_t *create_node(int id) {
+ node_t *node = malloc(sizeof(node_t));
+ node->id = id;
+ return node;
+}
+
+static void free_node(node_t *node) {
+ free(node);
+}
+
+static int node_compare(const node_t *lhs, const node_t *rhs) {
+ return lhs->id - rhs->id;
+}
+
+static int test_setup(void **state) {
+ splay_tree_t *tree = splay_alloc_tree((splay_compare_t) node_compare, (splay_action_t) free_node);
+
+ if(!tree) {
+ return -1;
+ }
+
+ *state = tree;
+ return 0;
+}
+
+static int test_teardown(void **state) {
+ splay_delete_tree(*state);
+ return 0;
+}
+
+static void test_tree_allocation_deletion(void **state) {
+ (void)state;
+
+ splay_tree_t *tree = splay_alloc_tree((splay_compare_t) node_compare,
+ (splay_action_t) free_node);
+ assert_non_null(tree);
+
+ node_t *one = create_node(1);
+ assert_non_null(splay_insert(tree, one));
+
+ node_t *two = create_node(2);
+ assert_non_null(splay_insert(tree, two));
+
+ // AddressSanitizer will notify us if there's a leak
+ splay_delete_tree(tree);
+}
+
+static int multiply_tree_node_calls = 0;
+
+static void increment_id_tree_node(node_t *node) {
+ ++node->id;
+ ++multiply_tree_node_calls;
+}
+
+static int multiply_splay_node_calls = 0;
+
+static void multiply_id_splay_node(splay_node_t *node) {
+ node_t *t = node->data;
+ t->id *= 2;
+ ++multiply_splay_node_calls;
+}
+
+static void test_splay_foreach(void **state) {
+ splay_tree_t *tree = *state;
+
+ node_t *one = create_node(1);
+ splay_node_t *node_one = splay_insert(tree, one);
+ assert_ptr_equal(one, node_one->data);
+
+ node_t *two = create_node(5);
+ splay_node_t *node_two = splay_insert(tree, two);
+ assert_ptr_equal(two, node_two->data);
+
+ splay_foreach(tree, (splay_action_t) increment_id_tree_node);
+ assert_int_equal(2, one->id);
+ assert_int_equal(6, two->id);
+
+ splay_foreach_node(tree, (splay_action_t) multiply_id_splay_node);
+ assert_int_equal(4, one->id);
+ assert_int_equal(12, two->id);
+
+ assert_int_equal(2, multiply_tree_node_calls);
+ assert_int_equal(2, multiply_splay_node_calls);
+}
+
+static void test_splay_each(void **state) {
+ splay_tree_t *tree = *state;
+
+ node_t *one = create_node(1);
+ node_t *two = create_node(2);
+
+ splay_insert(tree, one);
+ splay_insert(tree, two);
+
+ // splay_each should iterate over all nodes
+ for splay_each(node_t, n, tree) {
+ n->id = -n->id;
+ }
+
+ assert_int_equal(-1, one->id);
+ assert_int_equal(-2, two->id);
+
+ // splay_each should allow removal of the current node
+ for splay_each(node_t, n, tree) {
+ splay_delete(tree, n);
+ }
+}
+
+static void test_splay_basic_ops(void **state) {
+ splay_tree_t *tree = *state;
+ node_t *node = create_node(1);
+
+ // Should not find anything if the tree is empty
+ node_t *found_one = splay_search(tree, node);
+ assert_null(found_one);
+
+ // Insertion should return a non-NULL node with `data` pointing to our `tree_node`
+ splay_node_t *node_one = splay_insert(tree, node);
+ assert_ptr_equal(node, node_one->data);
+
+ // Should find after insertion
+ found_one = splay_search(tree, node);
+ assert_ptr_equal(node, found_one);
+}
+
+static void test_splay_insert_before_after(void **state) {
+ splay_tree_t *tree = *state;
+
+ node_t *one = create_node(1);
+ splay_node_t *node_one = splay_insert(tree, one);
+ assert_non_null(node_one);
+
+ // splay_insert_before should set up `prev` and `next` pointers
+ splay_node_t *node_two = splay_alloc_node();
+ assert_non_null(node_two);
+ node_two->data = create_node(2);
+
+ splay_insert_after(tree, node_one, node_two);
+ assert_null(node_one->prev);
+ assert_ptr_equal(node_one->next, node_two);
+ assert_ptr_equal(node_two->prev, node_one);
+ assert_null(node_two->next);
+
+ splay_node_t *node_thr = splay_alloc_node();
+ assert_non_null(node_thr);
+ node_thr->data = create_node(3);
+
+ splay_insert_after(tree, node_two, node_thr);
+ assert_null(node_one->prev);
+ assert_ptr_equal(node_one->next, node_two);
+ assert_ptr_equal(node_two->prev, node_one);
+ assert_ptr_equal(node_two->next, node_thr);
+ assert_ptr_equal(node_thr->prev, node_two);
+ assert_null(node_thr->next);
+}
+
+static void test_search_node(void **state) {
+ splay_tree_t *tree = *state;
+
+ node_t *one = create_node(1);
+ node_t *two = create_node(2);
+
+ splay_node_t *one_node = splay_search_node(tree, one);
+ assert_null(one_node);
+
+ one_node = splay_insert(tree, one);
+ assert_ptr_equal(one_node, splay_search_node(tree, one));
+
+ splay_node_t *two_node = splay_search_node(tree, two);
+ assert_null(two_node);
+
+ two_node = splay_insert(tree, two);
+ assert_ptr_equal(one_node, splay_search_node(tree, one));
+ assert_ptr_equal(two_node, splay_search_node(tree, two));
+
+ node_t *copy_one = create_node(1);
+ node_t *copy_two = create_node(2);
+
+ splay_delete(tree, one);
+ assert_null(splay_search_node(tree, copy_one));
+ assert_ptr_equal(two_node, splay_search_node(tree, two));
+
+ splay_delete(tree, two);
+ assert_null(splay_search_node(tree, copy_one));
+ assert_null(splay_search_node(tree, copy_two));
+
+ free_node(copy_one);
+ free_node(copy_two);
+}
+
+static void test_unlink(void **state) {
+ splay_tree_t *tree = *state;
+ node_t *one = create_node(1);
+
+ splay_node_t *node_one = splay_insert(tree, one);
+
+ // Unlink should return the unlinked node
+ splay_node_t *unlinked_one = splay_unlink(tree, one);
+ assert_ptr_equal(one, unlinked_one->data);
+
+ // Unlinking the same node should return NULL
+ assert_null(splay_unlink(tree, one));
+
+ // Inserting it back should return the same node
+ unlinked_one = splay_insert_node(tree, unlinked_one);
+ assert_ptr_equal(node_one, unlinked_one);
+}
+
+static void test_unlink_node(void **state) {
+ splay_tree_t *tree = *state;
+ node_t *one = create_node(1);
+
+ splay_node_t *node_one = splay_insert(tree, one);
+ assert_ptr_equal(one, node_one->data);
+ assert_ptr_equal(one, splay_search(tree, one));
+ assert_ptr_equal(node_one, splay_search_node(tree, one));
+
+ splay_unlink_node(tree, node_one);
+ assert_null(splay_search(tree, one));
+ assert_null(splay_search_node(tree, one));
+
+ splay_free_node(tree, node_one);
+}
+
+static void test_delete_node(void **state) {
+ splay_tree_t *tree = *state;
+ node_t *one = create_node(1);
+
+ splay_node_t *node_one = splay_insert(tree, one);
+ assert_ptr_equal(one, node_one->data);
+ assert_ptr_equal(one, splay_search(tree, one));
+ assert_ptr_equal(node_one, splay_search_node(tree, one));
+
+ node_t *copy = create_node(1);
+ assert_ptr_equal(one, splay_search(tree, copy));
+
+ splay_delete_node(tree, node_one);
+ assert_null(splay_search(tree, copy));
+
+ free_node(copy);
+}
+
+#define test_with_state(test_func) \
+ cmocka_unit_test_setup_teardown((test_func), test_setup, test_teardown)
+
+int main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(test_tree_allocation_deletion),
+ test_with_state(test_splay_basic_ops),
+ test_with_state(test_splay_insert_before_after),
+ test_with_state(test_splay_foreach),
+ test_with_state(test_splay_each),
+ test_with_state(test_search_node),
+ test_with_state(test_unlink),
+ test_with_state(test_unlink_node),
+ test_with_state(test_delete_node),
+ };
+ return cmocka_run_group_tests(tests, NULL, NULL);
+}
--- /dev/null
+#include "unittest.h"
+#include "../../src/subnet.h"
+
+typedef struct net_str_testcase {
+ const char *text;
+ subnet_t data;
+} net_str_testcase;
+
+static void test_subnet_compare_different_types(void **state) {
+ (void)state;
+
+ const subnet_t ipv4 = {.type = SUBNET_IPV4};
+ const subnet_t ipv6 = {.type = SUBNET_IPV6};
+ const subnet_t mac = {.type = SUBNET_MAC};
+
+ assert_int_not_equal(0, subnet_compare(&ipv4, &ipv6));
+ assert_int_not_equal(0, subnet_compare(&ipv4, &mac));
+ assert_int_not_equal(0, subnet_compare(&ipv6, &mac));
+}
+
+static const mac_t mac1 = {{0x00, 0x01, 0x02, 0x03, 0x04, 0x05}};
+static const mac_t mac2 = {{0x42, 0x01, 0x02, 0x03, 0x04, 0x05}};
+
+static const subnet_ipv4_t ipv4_1 = {.address = {{0x01, 0x02, 0x03, 0x04}}, .prefixlength = 24};
+static const subnet_ipv4_t ipv4_1_pref = {.address = {{0x01, 0x02, 0x03, 0x04}}, .prefixlength = 16};
+static const subnet_ipv4_t ipv4_2 = {.address = {{0x11, 0x22, 0x33, 0x44}}, .prefixlength = 16};
+
+static const subnet_ipv6_t ipv6_1 = {
+ .address = {{0x01, 0x02, 0x03, 0x04, 0x01, 0x02, 0x03, 0x04}},
+ .prefixlength = 24
+};
+
+static const subnet_ipv6_t ipv6_1_pref = {
+ .address = {{0x01, 0x02, 0x03, 0x04, 0x01, 0x02, 0x03, 0x04}},
+ .prefixlength = 16
+};
+
+static const subnet_ipv6_t ipv6_2 = {
+ .address = {{0x11, 0x22, 0x03, 0x04, 0x01, 0x02, 0x03, 0x04}},
+ .prefixlength = 24
+};
+
+static void test_maskcmp(void **state) {
+ (void)state;
+
+ const ipv4_t a = {{1, 2, 3, 4}};
+ const ipv4_t b = {{1, 2, 3, 0xff}};
+
+ for(int mask = 0; mask <= 24; ++mask) {
+ assert_int_equal(0, maskcmp(&a, &b, mask));
+ }
+
+ for(int mask = 25; mask <= 32; ++mask) {
+ assert_true(maskcmp(&a, &b, mask) != 0);
+ }
+}
+
+static void test_mask(void **state) {
+ (void)state;
+
+ ipv4_t dst = {{0xff, 0xff, 0xff, 0xff}};
+ mask(&dst, 23, sizeof(dst));
+
+ const ipv4_t ref = {{0xff, 0xff, 0xfe, 0x00}};
+ assert_memory_equal(&ref, &dst, sizeof(dst));
+}
+
+static void test_maskcpy(void **state) {
+ (void)state;
+
+ const ipv4_t src = {{0xff, 0xff, 0xff, 0xff}};
+ const ipv4_t ref = {{0xff, 0xff, 0xfe, 0x00}};
+ ipv4_t dst;
+
+ maskcpy(&dst, &src, 23, sizeof(src));
+
+ assert_memory_equal(&ref, &dst, sizeof(dst));
+}
+
+static void test_subnet_compare_mac_eq(void **state) {
+ (void)state;
+
+ node_t owner = {.name = strdup("foobar")};
+ const subnet_t a = {.type = SUBNET_MAC, .net.mac.address = mac1, .weight = 42, .owner = &owner};
+ const subnet_t b = {.type = SUBNET_MAC, .net.mac.address = mac1, .weight = 42, .owner = &owner};
+
+ assert_int_equal(0, subnet_compare(&a, &a));
+ assert_int_equal(0, subnet_compare(&a, &b));
+ assert_int_equal(0, subnet_compare(&b, &a));
+
+ free(owner.name);
+}
+
+static void test_subnet_compare_mac_neq_address(void **state) {
+ (void)state;
+
+ node_t owner = {.name = strdup("foobar")};
+ const subnet_t a = {.type = SUBNET_MAC, .net.mac.address = mac1, .weight = 10, .owner = &owner};
+ const subnet_t b = {.type = SUBNET_MAC, .net.mac.address = mac2, .weight = 10, .owner = &owner};
+
+ assert_true(subnet_compare(&a, &b) < 0);
+ assert_true(subnet_compare(&b, &a) > 0);
+
+ free(owner.name);
+}
+
+static void test_subnet_compare_mac_weight(void **state) {
+ (void)state;
+
+ node_t owner = {.name = strdup("foobar")};
+ const subnet_t a = {.type = SUBNET_MAC, .net.mac.address = mac1, .weight = 42, .owner = &owner};
+ const subnet_t b = {.type = SUBNET_MAC, .net.mac.address = mac1, .weight = 42, .owner = &owner};
+ const subnet_t c = {.type = SUBNET_MAC, .net.mac.address = mac1, .weight = 10, .owner = &owner};
+
+ assert_int_equal(0, subnet_compare(&a, &a));
+ assert_int_equal(0, subnet_compare(&a, &b));
+ assert_int_equal(0, subnet_compare(&b, &a));
+
+ assert_true(subnet_compare(&a, &c) > 0);
+ assert_true(subnet_compare(&c, &a) < 0);
+
+ free(owner.name);
+}
+
+static void test_subnet_compare_mac_owners(void **state) {
+ (void)state;
+
+ node_t foo = {.name = strdup("foo")};
+ node_t bar = {.name = strdup("bar")};
+
+ const subnet_t a = {.type = SUBNET_MAC, .net.mac.address = mac1, .weight = 42, .owner = &foo};
+ const subnet_t b = {.type = SUBNET_MAC, .net.mac.address = mac1, .weight = 42, .owner = &bar};
+
+ assert_int_equal(0, subnet_compare(&a, &a));
+ assert_int_equal(0, subnet_compare(&b, &b));
+
+ assert_true(subnet_compare(&a, &b) > 0);
+ assert_true(subnet_compare(&b, &a) < 0);
+
+ free(foo.name);
+ free(bar.name);
+}
+
+
+static void test_subnet_compare_ipv4_eq(void **state) {
+ (void)state;
+
+ const subnet_t a = {.type = SUBNET_IPV4, .net.ipv4 = ipv4_1};
+ const subnet_t b = {.type = SUBNET_IPV4, .net.ipv4 = ipv4_1};
+
+ assert_int_equal(0, subnet_compare(&a, &b));
+ assert_int_equal(0, subnet_compare(&b, &a));
+}
+
+static void test_subnet_compare_ipv4_neq(void **state) {
+ (void)state;
+
+ const subnet_t a = {.type = SUBNET_IPV4, .net.ipv4 = ipv4_1};
+ const subnet_t b = {.type = SUBNET_IPV4, .net.ipv4 = ipv4_1_pref};
+ const subnet_t c = {.type = SUBNET_IPV4, .net.ipv4 = ipv4_2};
+
+ assert_true(subnet_compare(&a, &b) < 0);
+ assert_true(subnet_compare(&b, &a) > 0);
+
+ assert_true(subnet_compare(&a, &c) < 0);
+ assert_true(subnet_compare(&b, &c) < 0);
+}
+
+static void test_subnet_compare_ipv4_weight(void **state) {
+ (void)state;
+
+ const subnet_t a = {.type = SUBNET_IPV4, .net.ipv4 = ipv4_1, .weight = 1};
+ const subnet_t b = {.type = SUBNET_IPV4, .net.ipv4 = ipv4_1, .weight = 2};
+
+ assert_true(subnet_compare(&a, &b) < 0);
+}
+
+static void test_subnet_compare_ipv4_owners(void **state) {
+ (void)state;
+
+ node_t foo = {.name = strdup("foo")};
+ node_t bar = {.name = strdup("bar")};
+
+ const subnet_t a = {.type = SUBNET_IPV4, .net.ipv4 = ipv4_1, .owner = &foo};
+ const subnet_t b = {.type = SUBNET_IPV4, .net.ipv4 = ipv4_1, .owner = &foo};
+ const subnet_t c = {.type = SUBNET_IPV4, .net.ipv4 = ipv4_1, .owner = &bar};
+
+ assert_int_equal(0, subnet_compare(&a, &b));
+ assert_true(subnet_compare(&a, &c) > 0);
+
+ free(foo.name);
+ free(bar.name);
+}
+
+static void test_subnet_compare_ipv6_eq(void **state) {
+ (void)state;
+
+ const subnet_t a = {.type = SUBNET_IPV6, .net.ipv6 = ipv6_1};
+ const subnet_t b = {.type = SUBNET_IPV6, .net.ipv6 = ipv6_1};
+
+ assert_int_equal(0, subnet_compare(&a, &b));
+ assert_int_equal(0, subnet_compare(&b, &a));
+}
+
+static void test_subnet_compare_ipv6_neq(void **state) {
+ (void)state;
+
+ const subnet_t a = {.type = SUBNET_IPV6, .net.ipv6 = ipv6_1};
+ const subnet_t b = {.type = SUBNET_IPV6, .net.ipv6 = ipv6_1_pref};
+ const subnet_t c = {.type = SUBNET_IPV6, .net.ipv6 = ipv6_2};
+
+ assert_true(subnet_compare(&a, &b) < 0);
+ assert_true(subnet_compare(&b, &a) > 0);
+
+ assert_true(subnet_compare(&a, &c) < 0);
+ assert_true(subnet_compare(&b, &c) > 0);
+}
+
+static void test_subnet_compare_ipv6_weight(void **state) {
+ (void)state;
+
+ const subnet_t a = {.type = SUBNET_IPV6, .net.ipv6 = ipv6_1, .weight = 1};
+ const subnet_t b = {.type = SUBNET_IPV6, .net.ipv6 = ipv6_1, .weight = 2};
+
+ assert_true(subnet_compare(&a, &b) < 0);
+}
+
+static void test_subnet_compare_ipv6_owners(void **state) {
+ (void)state;
+
+ node_t foo = {.name = strdup("foo")};
+ node_t bar = {.name = strdup("bar")};
+
+ const subnet_t a = {.type = SUBNET_IPV6, .net.ipv6 = ipv6_1, .owner = &foo};
+ const subnet_t b = {.type = SUBNET_IPV6, .net.ipv6 = ipv6_1, .owner = &foo};
+ const subnet_t c = {.type = SUBNET_IPV6, .net.ipv6 = ipv6_1, .owner = &bar};
+
+ assert_int_equal(0, subnet_compare(&a, &b));
+ assert_true(subnet_compare(&a, &c) > 0);
+
+ free(foo.name);
+ free(bar.name);
+}
+
+static void test_str2net_valid(void **state) {
+ (void)state;
+
+ const net_str_testcase testcases[] = {
+ {
+ .text = "1.2.3.0/24#42",
+ .data = {
+ .type = SUBNET_IPV4,
+ .weight = 42,
+ .net = {
+ .ipv4 = {
+ .address = {
+ .x = {1, 2, 3, 0}
+ },
+ .prefixlength = 24,
+ },
+ },
+ },
+ },
+ {
+ .text = "04fb:7deb:78db:1950:2d21:258d:40b6:f0d7/128#999",
+ .data = {
+ .type = SUBNET_IPV6,
+ .weight = 999,
+ .net = {
+ .ipv6 = {
+ .address = {
+ .x = {
+ htons(0x04fb), htons(0x7deb), htons(0x78db), htons(0x1950),
+ htons(0x2d21), htons(0x258d), htons(0x40b6), htons(0xf0d7),
+ }
+ },
+ .prefixlength = 128,
+ },
+ },
+ },
+ },
+ {
+ .text = "fe80::16dd:a9ff:fe7e:b4c2/64",
+ .data = {
+ .type = SUBNET_IPV6,
+ .weight = 10,
+ .net = {
+ .ipv6 = {
+ .address = {
+ .x = {
+ htons(0xfe80), htons(0x0000), htons(0x0000), htons(0x0000),
+ htons(0x16dd), htons(0xa9ff), htons(0xfe7e), htons(0xb4c2),
+ }
+ },
+ .prefixlength = 64,
+ },
+ },
+ },
+ },
+ {
+ .text = "57:04:13:01:f9:26#60",
+ .data = {
+ .type = SUBNET_MAC,
+ .weight = 60,
+ .net = {
+ .mac = {
+ .address = {
+ .x = {0x57, 0x04, 0x13, 0x01, 0xf9, 0x26},
+ },
+ },
+ },
+ },
+ },
+ };
+
+ for(size_t i = 0; i < sizeof(testcases) / sizeof(*testcases); ++i) {
+ const char *text = testcases[i].text;
+ const subnet_t *ref = &testcases[i].data;
+
+ subnet_t sub = {0};
+ bool ok = str2net(&sub, text);
+
+ // Split into separate assertions for more clear failures
+ assert_true(ok);
+ assert_int_equal(ref->type, sub.type);
+ assert_int_equal(ref->weight, sub.weight);
+
+ switch(ref->type) {
+ case SUBNET_MAC:
+ assert_memory_equal(&ref->net.mac.address, &sub.net.mac.address, sizeof(mac_t));
+ break;
+
+ case SUBNET_IPV4:
+ assert_int_equal(ref->net.ipv4.prefixlength, sub.net.ipv4.prefixlength);
+ assert_memory_equal(&ref->net.ipv4.address, &sub.net.ipv4.address, sizeof(ipv4_t));
+ break;
+
+ case SUBNET_IPV6:
+ assert_int_equal(ref->net.ipv6.prefixlength, sub.net.ipv6.prefixlength);
+ assert_memory_equal(&ref->net.ipv6.address, &sub.net.ipv6.address, sizeof(ipv6_t));
+ break;
+
+ default:
+ fail_msg("unknown subnet type %d", ref->type);
+ }
+ }
+}
+
+static void test_str2net_invalid(void **state) {
+ (void)state;
+
+ subnet_t sub = {0};
+
+ const char *test_cases[] = {
+ // Overflow
+ "1.2.256.0",
+
+ // Invalid mask
+ "1.2.3.0/",
+ "1.2.3.0/42",
+ "1.2.3.0/MASK",
+ "fe80::/129",
+ "fe80::/MASK",
+ "cb:0c:1b:60:ed:7a/1",
+
+ // Invalid weight
+ "1.2.3.4#WEIGHT",
+ "1.2.0.0/16#WEIGHT",
+ "1.2.0.0/16#",
+ "feff::/16#",
+ "feff::/16#w",
+
+ NULL,
+ };
+
+ for(const char **str = test_cases; *str; ++str) {
+ bool ok = str2net(&sub, *str);
+ assert_false(ok);
+ }
+}
+
+static void test_net2str_valid(void **state) {
+ (void)state;
+
+ const net_str_testcase testcases[] = {
+ {
+ .text = "12:fe:ff:3a:28:90#42",
+ .data = {
+ .type = SUBNET_MAC,
+ .weight = 42,
+ .net = {
+ .mac = {
+ .address = {
+ .x = {0x12, 0xfe, 0xff, 0x3a, 0x28, 0x90}
+ },
+ },
+ },
+ },
+ },
+ {
+ .text = "1.2.3.4",
+ .data = {
+ .type = SUBNET_IPV4,
+ .weight = 10,
+ .net = {
+ .ipv4 = {
+ .address = {
+ .x = {1, 2, 3, 4}
+ },
+ .prefixlength = 32,
+ },
+ },
+ },
+ },
+ {
+ .text = "181.35.16.0/27#1",
+ .data = {
+ .type = SUBNET_IPV4,
+ .weight = 1,
+ .net = {
+ .ipv4 = {
+ .address = {
+ .x = {181, 35, 16, 0}
+ },
+ .prefixlength = 27,
+ },
+ },
+ },
+ },
+ {
+ .text = "5fbf:5cfe:0:fdd2:fd76::/96#900",
+ .data = {
+ .type = SUBNET_IPV6,
+ .weight = 900,
+ .net = {
+ .ipv6 = {
+ .address = {
+ .x = {
+ htons(0x5fbf), htons(0x5cfe), htons(0x0000), htons(0xfdd2),
+ htons(0xfd76), htons(0x0000), htons(0x0000), htons(0x0000),
+ },
+ },
+ .prefixlength = 96,
+ },
+ },
+ },
+ },
+ };
+
+ for(size_t i = 0; i < sizeof(testcases) / sizeof(*testcases); ++i) {
+ const char *text = testcases[i].text;
+ const subnet_t *ref = &testcases[i].data;
+
+ char buf[256];
+ bool ok = net2str(buf, sizeof(buf), ref);
+
+ assert_true(ok);
+ assert_string_equal(text, buf);
+ }
+}
+
+static void test_net2str_invalid(void **state) {
+ (void)state;
+
+ const subnet_t sub = {0};
+ char buf[256];
+ assert_false(net2str(NULL, sizeof(buf), &sub));
+ assert_false(net2str(buf, sizeof(buf), NULL));
+}
+
+static void test_maskcheck_valid_ipv4(void **state) {
+ (void)state;
+
+ const ipv4_t a = {{10, 0, 0, 0}};
+ const ipv4_t b = {{192, 168, 0, 0}};
+ const ipv4_t c = {{192, 168, 24, 0}};
+
+ assert_true(maskcheck(&a, 8, sizeof(a)));
+ assert_true(maskcheck(&b, 16, sizeof(b)));
+ assert_true(maskcheck(&c, 24, sizeof(c)));
+}
+
+static void test_maskcheck_valid_ipv6(void **state) {
+ (void)state;
+
+ const ipv6_t a = {{10, 0, 0, 0, 0, 0, 0, 0}};
+ assert_true(maskcheck(&a, 8, sizeof(a)));
+
+ const ipv6_t b = {{10, 20, 0, 0, 0, 0, 0, 0}};
+ assert_true(maskcheck(&b, 32, sizeof(b)));
+
+ const ipv6_t c = {{192, 168, 24, 0, 0, 0, 0, 0}};
+ assert_true(maskcheck(&c, 48, sizeof(c)));
+}
+
+static void test_maskcheck_invalid_ipv4(void **state) {
+ (void)state;
+
+ const ipv4_t a = {{10, 20, 0, 0}};
+ const ipv4_t b = {{10, 20, 30, 0}};
+
+ assert_false(maskcheck(&a, 8, sizeof(a)));
+ assert_false(maskcheck(&b, 16, sizeof(b)));
+}
+
+static void test_maskcheck_invalid_ipv6(void **state) {
+ (void)state;
+
+ const ipv6_t a = {{1, 2, 3, 4, 5, 6, 7, 0xAABB}};
+
+ for(int mask = 0; mask < 128; mask += 8) {
+ assert_false(maskcheck(&a, mask, sizeof(a)));
+ }
+}
+
+int main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(test_maskcmp),
+ cmocka_unit_test(test_mask),
+ cmocka_unit_test(test_maskcpy),
+
+ cmocka_unit_test(test_subnet_compare_different_types),
+
+ cmocka_unit_test(test_subnet_compare_mac_eq),
+ cmocka_unit_test(test_subnet_compare_mac_neq_address),
+ cmocka_unit_test(test_subnet_compare_mac_weight),
+ cmocka_unit_test(test_subnet_compare_mac_owners),
+
+ cmocka_unit_test(test_subnet_compare_ipv4_eq),
+ cmocka_unit_test(test_subnet_compare_ipv4_neq),
+ cmocka_unit_test(test_subnet_compare_ipv4_weight),
+ cmocka_unit_test(test_subnet_compare_ipv4_owners),
+
+ cmocka_unit_test(test_subnet_compare_ipv6_eq),
+ cmocka_unit_test(test_subnet_compare_ipv6_neq),
+ cmocka_unit_test(test_subnet_compare_ipv6_weight),
+ cmocka_unit_test(test_subnet_compare_ipv6_owners),
+
+ cmocka_unit_test(test_str2net_valid),
+ cmocka_unit_test(test_str2net_invalid),
+
+ cmocka_unit_test(test_net2str_valid),
+ cmocka_unit_test(test_net2str_invalid),
+
+ cmocka_unit_test(test_maskcheck_valid_ipv4),
+ cmocka_unit_test(test_maskcheck_valid_ipv6),
+ cmocka_unit_test(test_maskcheck_invalid_ipv4),
+ cmocka_unit_test(test_maskcheck_invalid_ipv6),
+ };
+ return cmocka_run_group_tests(tests, NULL, NULL);
+}