The FreeRADIUS server $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
Loading...
Searching...
No Matches
trie_tests.c
Go to the documentation of this file.
1/*
2 * This library is free software; you can redistribute it and/or
3 * modify it under the terms of the GNU Lesser General Public
4 * License as published by the Free Software Foundation; either
5 * version 2.1 of the License, or (at your option) any later version.
6 *
7 * This library is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
10 * Lesser General Public License for more details.
11 *
12 * You should have received a copy of the GNU Lesser General Public
13 * License along with this library; if not, write to the Free Software
14 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15 */
16
17/** Tests for the trie data structure
18 *
19 * @file src/lib/util/test/trie_tests.c
20 *
21 * @copyright 2026 Network RADIUS SAS (legal@networkradius.com)
22 */
23#include "acutest.h"
24#include "acutest_helpers.h"
25
26#include <freeradius-devel/util/trie.h>
27#include <freeradius-devel/util/inet.h>
28
29/*
30 * Test basic trie creation.
31 */
32static void test_trie_alloc(void)
33{
34 fr_trie_t *ft;
35
36 ft = fr_trie_alloc(NULL, NULL, NULL);
37 TEST_ASSERT(ft != NULL);
38
39 talloc_free(ft);
40}
41
42/*
43 * Test insert and exact lookup by key.
44 */
45static void test_trie_insert_lookup(void)
46{
47 fr_trie_t *ft;
48 char const *data1 = "hello";
49 char const *data2 = "world";
50 char const *data3 = "test";
51 void *found;
52 uint8_t key1[4] = { 10, 0, 0, 0 };
53 uint8_t key2[4] = { 10, 0, 1, 0 };
54 uint8_t key3[4] = { 192, 168, 0, 0 };
55 uint8_t missing[4] = { 172, 16, 0, 0 };
56
57 ft = fr_trie_alloc(NULL, NULL, NULL);
58 TEST_ASSERT(ft != NULL);
59
60 TEST_CASE("Insert keys");
61 TEST_CHECK(fr_trie_insert_by_key(ft, key1, 32, data1) == 0);
62 TEST_CHECK(fr_trie_insert_by_key(ft, key2, 32, data2) == 0);
63 TEST_CHECK(fr_trie_insert_by_key(ft, key3, 32, data3) == 0);
64
65 TEST_CASE("Exact lookup finds correct data");
66 found = fr_trie_lookup_by_key(ft, key1, 32);
67 TEST_CHECK(found == data1);
68
69 found = fr_trie_lookup_by_key(ft, key2, 32);
70 TEST_CHECK(found == data2);
71
72 found = fr_trie_lookup_by_key(ft, key3, 32);
73 TEST_CHECK(found == data3);
74
75 TEST_CASE("Lookup non-existent key returns NULL");
76 found = fr_trie_lookup_by_key(ft, missing, 32);
77 TEST_CHECK(found == NULL);
78
79 talloc_free(ft);
80}
81
82/*
83 * Test duplicate insertion fails.
84 */
86{
87 fr_trie_t *ft;
88 char const *data1 = "first";
89 char const *data2 = "second";
90 uint8_t key[4] = { 10, 0, 0, 1 };
91
92 ft = fr_trie_alloc(NULL, NULL, NULL);
93 TEST_ASSERT(ft != NULL);
94
95 TEST_CASE("First insert succeeds");
96 TEST_CHECK(fr_trie_insert_by_key(ft, key, 32, data1) == 0);
97
98 TEST_CASE("Duplicate insert fails");
99 TEST_CHECK(fr_trie_insert_by_key(ft, key, 32, data2) < 0);
100
101 TEST_CASE("Original data is preserved");
102 TEST_CHECK(fr_trie_lookup_by_key(ft, key, 32) == data1);
103
104 talloc_free(ft);
105}
106
107/*
108 * Test prefix (longest match) lookups.
109 */
111{
112 fr_trie_t *ft;
113 char const *net8 = "10/8";
114 char const *net16 = "10.0/16";
115 char const *net24 = "10.0.0/24";
116 void *found;
117
118 ft = fr_trie_alloc(NULL, NULL, NULL);
119 TEST_ASSERT(ft != NULL);
120
121 /*
122 * Insert prefixes of different lengths.
123 * 10.0.0.0/8, 10.0.0.0/16, 10.0.0/24
124 */
125 TEST_CASE("Insert and find overlapping prefixes of 3 different lengths");
126 {
127 uint8_t k8[] = { 10 };
128 uint8_t k16[] = { 10, 0 };
129 uint8_t k24[] = { 10, 0, 0 };
130
131 TEST_CHECK(fr_trie_insert_by_key(ft, k8, 8, net8) == 0);
132 found = fr_trie_match_by_key(ft, k8, 8);
133 TEST_CHECK(found == net8);
134
135 TEST_CHECK(fr_trie_insert_by_key(ft, k16, 16, net16) == 0);
136 found = fr_trie_match_by_key(ft, k8, 8);
137 TEST_CHECK(found == net8);
138 found = fr_trie_match_by_key(ft, k16, 16);
139 TEST_CHECK(found == net16);
140
141 TEST_CHECK(fr_trie_insert_by_key(ft, k24, 24, net24) == 0);
142 found = fr_trie_match_by_key(ft, k8, 8);
143 TEST_CHECK(found == net8);
144 found = fr_trie_match_by_key(ft, k16, 16);
145 TEST_CHECK(found == net16);
146 found = fr_trie_match_by_key(ft, k24, 24);
147 TEST_CHECK(found == net24);
148 }
149
150 TEST_CASE("Longest prefix lookup for 10.0.0.5 returns 10.0.0/24");
151 {
152 uint8_t host[] = { 10, 0, 0, 5 };
153 found = fr_trie_lookup_by_key(ft, host, 32);
154 TEST_CHECK(found == net24);
155 }
156
157 TEST_CASE("Longest prefix lookup for 10.0.1.5 returns 10.0/16");
158 {
159 uint8_t host[] = { 10, 0, 1, 5 };
160 found = fr_trie_lookup_by_key(ft, host, 32);
161 TEST_CHECK(found == net16);
162 }
163
164 TEST_CASE("Longest prefix lookup for 10.1.0.1 returns 10/8");
165 {
166 uint8_t host[] = { 10, 1, 0, 1 };
167 found = fr_trie_lookup_by_key(ft, host, 32);
168 TEST_CHECK(found == net8);
169 }
170
171 TEST_CASE("No match for 192.168.0.1");
172 {
173 uint8_t host[] = { 192, 168, 0, 1 };
174 found = fr_trie_lookup_by_key(ft, host, 32);
175 TEST_CHECK(found == NULL);
176 }
177
178 talloc_free(ft);
179}
180
181/*
182 * Test remove by key.
183 */
184static void test_trie_remove(void)
185{
186 fr_trie_t *ft;
187 char const *data1 = "first";
188 char const *data2 = "second";
189 void *removed;
190 uint8_t key1[] = { 10, 0, 0, 1 };
191 uint8_t key2[] = { 10, 0, 0, 2 };
192
193 ft = fr_trie_alloc(NULL, NULL, NULL);
194 TEST_ASSERT(ft != NULL);
195
196 TEST_CHECK(fr_trie_insert_by_key(ft, key1, 32, data1) == 0);
197 TEST_CHECK(fr_trie_insert_by_key(ft, key2, 32, data2) == 0);
198
199 TEST_CASE("Remove returns the data");
200 removed = fr_trie_remove_by_key(ft, key1, 32);
201 TEST_CHECK(removed == data1);
202
203 TEST_CASE("Removed key is no longer found");
204 TEST_CHECK(fr_trie_lookup_by_key(ft, key1, 32) == NULL);
205
206 TEST_CASE("Other key is still present");
207 TEST_CHECK(fr_trie_lookup_by_key(ft, key2, 32) == data2);
208
209 TEST_CASE("Remove non-existent key returns NULL");
210 removed = fr_trie_remove_by_key(ft, key1, 32);
211 TEST_CHECK(removed == NULL);
212
213 talloc_free(ft);
214}
215
216typedef struct {
217 int count;
218 uint8_t keys[32][4];
219 size_t keylens[32];
220} walk_ctx_t;
221
222static int walk_callback(uint8_t const *key, size_t keylen, UNUSED void *data, void *uctx)
223{
224 walk_ctx_t *ctx = uctx;
225
226 if (ctx->count < 32) {
227 memcpy(ctx->keys[ctx->count], key, (keylen + 7) / 8);
228 ctx->keylens[ctx->count] = keylen;
229 ctx->count++;
230 }
231
232 return 0;
233}
234
235/*
236 * Test walk (iteration) over the trie.
237 */
238static void test_trie_walk(void)
239{
240 fr_trie_t *ft;
241 walk_ctx_t ctx;
242 char const *data[] = { "a", "b", "c" };
243 uint8_t k1[] = { 10, 0, 0, 0 };
244 uint8_t k2[] = { 10, 0, 1, 0 };
245 uint8_t k3[] = { 192, 168, 0, 0 };
246
247 ft = fr_trie_alloc(NULL, NULL, NULL);
248 TEST_ASSERT(ft != NULL);
249
250 TEST_CHECK(fr_trie_insert_by_key(ft, k1, 32, data[0]) == 0);
251 TEST_CHECK(fr_trie_insert_by_key(ft, k2, 32, data[1]) == 0);
252 TEST_CHECK(fr_trie_insert_by_key(ft, k3, 32, data[2]) == 0);
253
254 memset(&ctx, 0, sizeof(ctx));
255
256 TEST_CASE("Walk visits all entries");
257 fr_trie_walk(ft, &ctx, walk_callback);
258 TEST_CHECK(ctx.count == 3);
259 TEST_MSG("expected 3 entries, got %d", ctx.count);
260
261 talloc_free(ft);
262}
263
264/*
265 * Test with many entries to exercise trie growth.
266 */
267static void test_trie_many(void)
268{
269 fr_trie_t *ft;
270 int i;
271 char *values[256];
272
273 ft = fr_trie_alloc(NULL, NULL, NULL);
274 TEST_ASSERT(ft != NULL);
275
276 TEST_CASE("Insert 256 /32 entries under 10.0.0.0/8");
277 for (i = 0; i < 256; i++) {
278 uint8_t key[] = { 10, 0, 0, (uint8_t)i };
279
280 values[i] = talloc_asprintf(ft, "host-%d", i);
281 TEST_CHECK(fr_trie_insert_by_key(ft, key, 32, values[i]) == 0);
282 TEST_MSG("insert host-%d failed", i);
283 }
284
285 TEST_CASE("Lookup all 256 entries");
286 for (i = 0; i < 256; i++) {
287 uint8_t key[] = { 10, 0, 0, (uint8_t)i };
288 void *found = fr_trie_lookup_by_key(ft, key, 32);
289
290 TEST_CHECK(found == values[i]);
291 TEST_MSG("lookup host-%d failed", i);
292 }
293
294 TEST_CASE("Remove all 256 entries");
295 for (i = 0; i < 256; i++) {
296 uint8_t key[] = { 10, 0, 0, (uint8_t)i };
297 void *removed = fr_trie_remove_by_key(ft, key, 32);
298
299 TEST_CHECK(removed == values[i]);
300 TEST_MSG("remove host-%d failed", i);
301 }
302
303 TEST_CASE("All entries removed, lookups return NULL");
304 for (i = 0; i < 256; i++) {
305 uint8_t key[] = { 10, 0, 0, (uint8_t)i };
306 TEST_CHECK(fr_trie_lookup_by_key(ft, key, 32) == NULL);
307 }
308
309 talloc_free(ft);
310}
311
312/*
313 * Test prefix insertion at bit boundaries (not byte-aligned).
314 */
315static void test_trie_bit_prefix(void)
316{
317 fr_trie_t *ft;
318 char const *net20 = "10.0.0.0/20";
319 char const *net28 = "10.0.0.0/28";
320 void *found;
321
322 ft = fr_trie_alloc(NULL, NULL, NULL);
323 TEST_ASSERT(ft != NULL);
324
325 /*
326 * Insert non-byte-aligned prefixes.
327 */
328 {
329 uint8_t k20[] = { 10, 0, 0 }; /* first 20 bits */
330 uint8_t k28[] = { 10, 0, 0, 0 }; /* first 28 bits */
331
332 TEST_CHECK(fr_trie_insert_by_key(ft, k20, 20, net20) == 0);
333 TEST_CHECK(fr_trie_insert_by_key(ft, k28, 28, net28) == 0);
334 }
335
336 TEST_CASE("Lookup 10.0.0.5 returns /28");
337 {
338 uint8_t host[] = { 10, 0, 0, 5 };
339 found = fr_trie_lookup_by_key(ft, host, 32);
340 TEST_CHECK(found == net28);
341 }
342
343 TEST_CASE("Lookup 10.0.1.5 returns /20");
344 {
345 uint8_t host[] = { 10, 0, 1, 5 };
346 found = fr_trie_lookup_by_key(ft, host, 32);
347 TEST_CHECK(found == net20);
348 }
349
350 talloc_free(ft);
351}
352
354 { "trie_alloc", test_trie_alloc },
355 { "trie_insert_lookup", test_trie_insert_lookup },
356 { "trie_insert_duplicate", test_trie_insert_duplicate },
357 { "trie_longest_prefix", test_trie_longest_prefix },
358 { "trie_remove", test_trie_remove },
359 { "trie_walk", test_trie_walk },
360 { "trie_many", test_trie_many },
361 { "trie_bit_prefix", test_trie_bit_prefix },
363};
#define TEST_CHECK(cond)
Definition acutest.h:87
#define TEST_CASE(name)
Definition acutest.h:186
#define TEST_ASSERT(cond)
Definition acutest.h:110
#define TEST_TERMINATOR
Definition acutest.h:64
#define TEST_MSG(...)
Definition acutest.h:217
#define UNUSED
Definition build.h:317
talloc_free(hp)
unsigned char uint8_t
#define talloc_asprintf
Definition talloc.h:147
void * fr_trie_remove_by_key(fr_trie_t *ft, void const *key, size_t keylen)
Remove a key and return the associated user ctx.
Definition trie.c:2157
fr_trie_t * fr_trie_alloc(TALLOC_CTX *ctx, fr_trie_key_t get_key, fr_free_t free_data)
Allocate a trie.
Definition trie.c:741
int fr_trie_walk(fr_trie_t *ft, void *ctx, fr_trie_walk_t callback)
Definition trie.c:2610
void * fr_trie_lookup_by_key(fr_trie_t const *ft, void const *key, size_t keylen)
Lookup a key in a trie and return user ctx, if any.
Definition trie.c:1265
void * fr_trie_match_by_key(fr_trie_t const *ft, void const *key, size_t keylen)
Match a key and length in a trie and return user ctx, if any.
Definition trie.c:1289
int fr_trie_insert_by_key(fr_trie_t *ft, void const *key, size_t keylen, void const *data)
Insert a key and user ctx into a trie.
Definition trie.c:1878
TEST_LIST
Definition trie_tests.c:353
static void test_trie_many(void)
Definition trie_tests.c:267
static int walk_callback(uint8_t const *key, size_t keylen, UNUSED void *data, void *uctx)
Definition trie_tests.c:222
static void test_trie_insert_duplicate(void)
Definition trie_tests.c:85
static void test_trie_longest_prefix(void)
Definition trie_tests.c:110
static void test_trie_alloc(void)
Definition trie_tests.c:32
static void test_trie_bit_prefix(void)
Definition trie_tests.c:315
uint8_t keys[32][4]
Definition trie_tests.c:218
static void test_trie_insert_lookup(void)
Definition trie_tests.c:45
size_t keylens[32]
Definition trie_tests.c:219
static void test_trie_remove(void)
Definition trie_tests.c:184
static void test_trie_walk(void)
Definition trie_tests.c:238
static fr_slen_t data
Definition value.h:1340