The FreeRADIUS server $Id: a4e7f8c94e8038b7627bcf52356b7ed15955c1d7 $
Loading...
Searching...
No Matches
pairmove_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 `radius_pairmove()`
18 *
19 * @file src/lib/server/pairmove_tests.c
20 *
21 * `radius_pairmove()` merges the pairs of the `from` list into the `to` list,
22 * and applies the operator of each pair in the `from` list. These tests
23 * record the behaviour of `radius_pairmove()`. A developer who rewrites the
24 * function runs the tests to check that the behaviour is unchanged.
25 *
26 * Each test writes a list as short text, for example `s0=a i0+=5`. Each
27 * entry has a name, an operator, and a value:
28 *
29 * s0, s1, s2 `Test-String-0`, `Test-String-1`, `Test-String-2`
30 * i0, i1 `Test-Uint32-0`, `Test-Uint32-1`
31 * p4, p6 `Test-IPv4-Prefix`, `Test-IPv6-Prefix`
32 * un, up `Test-User-Name`, `Test-User-Password`
33 *
34 * Each test renders the merged `to` list in the same text form, and compares
35 * the result with the expected text. Each case runs against the request list,
36 * the reply list, and the control list of a request. After the merge, the test
37 * frees the `to` list and checks that the number of talloc blocks under the
38 * request equals the number before the merge. The check catches a leak of
39 * pairs, or of the temporary arrays that `radius_pairmove()` allocates.
40 *
41 * @copyright 2026 The FreeRADIUS server project
42 */
43static void test_init(void);
44#define TEST_GLOBAL_INIT test_init()
45
46#include <freeradius-devel/util/test/acutest_common_init.h>
47#include <freeradius-devel/util/test/acutest_helpers.h>
48#include <freeradius-devel/util/dict_test.h>
49
50#include <freeradius-devel/server/pairmove.h>
51#include <freeradius-devel/server/request.h>
52#include <freeradius-devel/util/conf.h>
53#include <freeradius-devel/util/pair.h>
54
55static TALLOC_CTX *autofree;
57
58/*
59 * The text form of a list uses these short names for attributes.
60 * The `da` field is filled in by `fr_dict_test_init()`.
61 */
62static struct {
63 char const *name;
64 fr_dict_attr_t const *da;
65} attr_names[] = {
66 { .name = "s0" }, { .name = "s1" }, { .name = "s2" },
67 { .name = "i0" }, { .name = "i1" },
68 { .name = "p4" }, { .name = "p6" },
69 { .name = "un" }, { .name = "up" },
70};
71
73 { .attr = (fr_dict_test_attr_number_t) 1, .da = &attr_names[0].da, .name = "Test-String-0", .type = FR_TYPE_STRING },
74 { .attr = (fr_dict_test_attr_number_t) 2, .da = &attr_names[1].da, .name = "Test-String-1", .type = FR_TYPE_STRING },
75 { .attr = (fr_dict_test_attr_number_t) 3, .da = &attr_names[2].da, .name = "Test-String-2", .type = FR_TYPE_STRING },
76 { .attr = (fr_dict_test_attr_number_t) 4, .da = &attr_names[3].da, .name = "Test-Uint32-0", .type = FR_TYPE_UINT32 },
77 { .attr = (fr_dict_test_attr_number_t) 5, .da = &attr_names[4].da, .name = "Test-Uint32-1", .type = FR_TYPE_UINT32 },
78 { .attr = (fr_dict_test_attr_number_t) 6, .da = &attr_names[5].da, .name = "Test-IPv4-Prefix", .type = FR_TYPE_IPV4_PREFIX },
79 { .attr = (fr_dict_test_attr_number_t) 7, .da = &attr_names[6].da, .name = "Test-IPv6-Prefix", .type = FR_TYPE_IPV6_PREFIX },
80 { .attr = (fr_dict_test_attr_number_t) 8, .da = &attr_names[7].da, .name = "Test-User-Name", .type = FR_TYPE_STRING },
81 { .attr = (fr_dict_test_attr_number_t) 9, .da = &attr_names[8].da, .name = "Test-User-Password", .type = FR_TYPE_STRING },
82 { .attr = FR_TEST_ATTR_INVALID }
83};
84
85/** Global initialisation
86 */
87static void test_init(void)
88{
90 if (!autofree) {
91 error:
92 fr_perror("pairmove_tests");
93 fr_exit_now(EXIT_FAILURE);
94 }
95
96 /*
97 * Mismatch between the binary and the libraries it depends on
98 */
99 if (fr_check_lib_magic(RADIUSD_MAGIC_NUMBER) < 0) goto error;
100
101 if (fr_dict_test_init(autofree, &test_dict, test_attrs) < 0) goto error;
102
103 if (request_global_init() < 0) goto error;
104}
105
106/*
107 * `list_alloc()` uses the first operator in this table that matches the
108 * text. An operator that is the prefix of another operator, such as '='
109 * in '==', must come after the longer operator.
110 */
111static struct {
112 char const *str;
113 fr_token_t op;
114} const op_names[] = {
115 { ":=", T_OP_SET },
116 { "+=", T_OP_ADD_EQ },
117 { "-=", T_OP_SUB_EQ },
118 { "==", T_OP_CMP_EQ },
119 { "<=", T_OP_LE },
120 { ">=", T_OP_GE },
121 { "!*", T_OP_CMP_FALSE },
122 { "^=", T_OP_PREPEND },
123 { "=", T_OP_EQ },
125
126/** Build a list of pairs from the text in `spec`
127 *
128 * `list_alloc()` allocates the pairs in `ctx`. Because the '!*' operator
129 * does not take a value, `list_alloc()` gives the pair an empty string, or
130 * zero for an integer.
131 */
132static void list_alloc(TALLOC_CTX *ctx, fr_pair_list_t *list, char const *spec)
133{
134 char const *p = spec;
135
136 while (*p) {
137 char name[3];
138 char value[64];
139 fr_dict_attr_t const *da = NULL;
141 size_t i, len;
142 fr_pair_t *vp;
143
144 while (*p == ' ') p++;
145 if (!*p) break;
146
147 TEST_ASSERT(p[0] && p[1]);
148 name[0] = p[0];
149 name[1] = p[1];
150 name[2] = '\0';
151 p += 2;
152
153 for (i = 0; i < NUM_ELEMENTS(attr_names); i++) {
154 if (strcmp(attr_names[i].name, name) == 0) {
155 da = attr_names[i].da;
156 break;
157 }
158 }
159 TEST_ASSERT(da != NULL);
160
161 for (i = 0; i < NUM_ELEMENTS(op_names); i++) {
162 len = strlen(op_names[i].str);
163 if (strncmp(p, op_names[i].str, len) == 0) {
164 op = op_names[i].op;
165 p += len;
166 break;
167 }
168 }
169 TEST_ASSERT(op != T_INVALID);
170
171 len = strcspn(p, " ");
172 TEST_ASSERT(len < sizeof(value));
173 memcpy(value, p, len);
174 value[len] = '\0';
175 p += len;
176
177 if ((len == 0) && (name[0] == 'i')) strcpy(value, "0");
178
179 vp = fr_pair_afrom_da(ctx, da);
180 TEST_ASSERT(vp != NULL);
181#ifdef STATIC_ANALYZER
182 if (!vp) _exit(1);
183#endif
184
185 TEST_ASSERT(fr_pair_value_from_str(vp, value, strlen(value), NULL, false) == 0);
186
187 vp->op = op;
188 fr_pair_append(list, vp);
189 }
190}
191
192/** Render a list in the text form that `list_alloc()` accepts
193 *
194 * Because `list_str()` omits the '=' operator, a result pair that still has
195 * an operator from the `from` list shows the operator in the output.
196 */
197static char const *list_str(fr_pair_list_t const *list)
198{
199 static char buffer[4096];
201
202 buffer[0] = '\0';
203
204 fr_pair_list_foreach(list, vp) {
205 char const *name = NULL;
206 size_t i;
207
208 for (i = 0; i < NUM_ELEMENTS(attr_names); i++) {
209 if (attr_names[i].da == vp->da) {
210 name = attr_names[i].name;
211 break;
212 }
213 }
214 if (!name) name = vp->da->name;
215
216 if (fr_sbuff_used(&out) > 0) (void) fr_sbuff_in_char(&out, ' ');
217 (void) fr_sbuff_in_strcpy(&out, name);
218 if (vp->op != T_OP_EQ) {
219 (void) fr_sbuff_in_strcpy(&out, fr_tokens[vp->op]);
220 } else {
221 (void) fr_sbuff_in_char(&out, '=');
222 }
223 (void) fr_value_box_print(&out, &vp->data, NULL);
224 }
225
226 return buffer;
227}
228
235
236static char const *list_target_names[LIST_NUM] = { "request", "reply", "control" };
237
238/** Make a request with empty pair lists
239 */
241{
243
245 TEST_ASSERT(request != NULL);
246
247 request->packet = fr_packet_alloc(request, false);
248 TEST_ASSERT(request->packet != NULL);
249
250 request->reply = fr_packet_alloc(request, false);
251 TEST_ASSERT(request->reply != NULL);
252
253 return request;
254}
255
256/** Get the pair list and the talloc context for a target
257 *
258 * `list_target()` sets `*ctx` to the talloc context in which the server
259 * allocates the pairs of the list.
260 */
261static fr_pair_list_t *list_target(TALLOC_CTX **ctx, request_t *request, list_target_t target)
262{
263 switch (target) {
264 case LIST_REQUEST:
265 *ctx = request->request_ctx;
266 return &request->request_pairs;
267
268 case LIST_REPLY:
269 *ctx = request->reply_ctx;
270 return &request->reply_pairs;
271
272 default:
273 *ctx = request->control_ctx;
274 return &request->control_pairs;
275 }
276}
277
278typedef struct {
279 char const *name;
280 char const *to; //!< Text form of the `to` list before the merge.
281 char const *from; //!< Text form of the `from` list.
282 char const *expect; //!< Expected text form of the `to` list after the merge.
284
285static void pairmove_run(pairmove_case_t const *tc, list_target_t target)
286{
288 TALLOC_CTX *ctx;
289 fr_pair_list_t *to;
290 fr_pair_list_t from;
291 size_t before, after;
292
294 to = list_target(&ctx, request, target);
295
296 before = talloc_total_blocks(request);
297
298 list_alloc(ctx, to, tc->to);
299 fr_pair_list_init(&from);
300 list_alloc(ctx, &from, tc->from);
301
302 radius_pairmove(request, to, &from); /* consumes `from` */
303
304 TEST_CASE_("%s: %s", tc->name, list_target_names[target]);
306
308
309 /*
310 * Each pair must be in the `to` list or freed by `radius_pairmove()`.
311 * Freeing the `to` list then returns the number of talloc blocks to the
312 * value before the merge.
313 */
315 after = talloc_total_blocks(request);
316 TEST_CHECK(after == before);
317 TEST_MSG("Blocks before : %zu", before);
318 TEST_MSG("Blocks after : %zu", after);
319
321}
322
323static void pairmove_run_cases(pairmove_case_t const *cases, size_t num)
324{
325 size_t i;
326 list_target_t target;
327
328 for (i = 0; i < num; i++) {
329 for (target = LIST_REQUEST; target < LIST_NUM; target++) pairmove_run(&cases[i], target);
330 }
331}
332
333/*
334 * The '=' operator adds an attribute to the `to` list only if the `to`
335 * list does not already have an instance of the attribute.
336 */
337static void test_op_eq(void)
338{
339 static pairmove_case_t const cases[] = {
340 { "empty into empty", "", "", "" },
341 { "= into empty", "", "s0=a", "s0=a" },
342 { "= absent attribute", "s0=a", "s1=b", "s0=a s1=b" },
343 { "= present attribute", "s0=a", "s0=b", "s0=a" },
344 { "= present, others", "s1=x s0=a", "s0=b s2=c", "s1=x s0=a s2=c" },
345
346 /*
347 * Because '=' checks the original `to` list only, and the original
348 * `to` list is empty, the merge adds both `s0=a` and `s0=b`.
349 */
350 { "= twice, absent", "", "s0=a s0=b", "s0=a s0=b" },
351 };
352
353 pairmove_run_cases(cases, NUM_ELEMENTS(cases));
354}
355
356/*
357 * The ':=' operator replaces the first instance of the attribute in the
358 * `to` list. The operator adds the attribute when the `to` list does
359 * not have an instance.
360 */
361static void test_op_set(void)
362{
363 static pairmove_case_t const cases[] = {
364 { ":= into empty", "", "s0:=c", "s0=c" },
365 { ":= absent attribute", "s1=x", "s0:=c", "s1=x s0=c" },
366 { ":= replaces in place", "s1=x s0=a i0=1", "s0:=b", "s1=x s0=b i0=1" },
367 { ":= replaces first only", "s0=a s0=b", "s0:=c", "s0=c s0=b" },
368
369 /*
370 * Because the merge does not replace an instance twice, the second ':='
371 * replaces the second instance of the attribute, or adds a new instance
372 * when the `to` list has only one instance.
373 */
374 { ":= twice, one instance", "s0=a", "s0:=b s0:=c", "s0=b s0=c" },
375 { ":= twice, two instances", "s0=a s0=x", "s0:=b s0:=c", "s0=b s0=c" },
376 { ":= thrice, two instances", "s0=a s0=x", "s0:=b s0:=c s0:=d", "s0=b s0=c s0=d" },
377 { ":= integer", "i0=1", "i0:=2", "i0=2" },
378 };
379
380 pairmove_run_cases(cases, NUM_ELEMENTS(cases));
381}
382
383/*
384 * The '+=' operator always adds the attribute at the end of the `to` list.
385 */
386static void test_op_add(void)
387{
388 static pairmove_case_t const cases[] = {
389 { "+= into empty", "", "s0+=a", "s0=a" },
390 { "+= present attribute", "s0=a", "s0+=b", "s0=a s0=b" },
391 { "+= same value", "s0=a", "s0+=a", "s0=a s0=a" },
392 { "+= keeps from order", "s0=a", "s1+=x s2+=y s1+=z", "s0=a s1=x s2=y s1=z" },
393 { "+= integer", "i0=1", "i0+=2", "i0=1 i0=2" },
394 };
395
396 pairmove_run_cases(cases, NUM_ELEMENTS(cases));
397}
398
399/*
400 * The '^=' operator adds the attribute at the start of the `to` list.
401 *
402 * The merge applies the '^=' pairs in the order of the `from` list.
403 * Because each '^=' pair goes to the start of the `to` list, a later
404 * '^=' pair comes before an earlier '^=' pair. The result lists the '^='
405 * attributes in the reverse of the order of the `from` list.
406 */
407static void test_op_prepend(void)
408{
409 static pairmove_case_t const cases[] = {
410 { "^= into empty", "", "s0^=a", "s0=a" },
411 { "^= one attribute", "s0=a", "s1^=x", "s1=x s0=a" },
412 { "^= present attribute", "s0=a", "s0^=b", "s0=b s0=a" },
413 { "^= two attributes", "s0=a", "s1^=x s2^=y", "s2=y s1=x s0=a" },
414 { "^= three attributes", "", "s0^=a s1^=b s2^=c", "s2=c s1=b s0=a" },
415 { "^= and +=", "s0=a", "s1+=x s2^=y", "s2=y s0=a s1=x" },
416 };
417
418 pairmove_run_cases(cases, NUM_ELEMENTS(cases));
419}
420
421/*
422 * The '-=' operator deletes every instance of the attribute from the
423 * `to` list when the value of the instance matches the value of the
424 * `from` pair.
425 */
426static void test_op_sub(void)
427{
428 static pairmove_case_t const cases[] = {
429 { "-= from empty", "", "s0-=a", "" },
430 { "-= absent attribute", "s1=x", "s0-=a", "s1=x" },
431 { "-= no matching value", "s0=a", "s0-=b", "s0=a" },
432 { "-= matching value", "s0=a", "s0-=a", "" },
433 { "-= all matching values", "s0=a s0=b s0=a", "s0-=a", "s0=b" },
434 { "-= keeps others", "s1=x s0=a i0=1", "s0-=a", "s1=x i0=1" },
435 { "-= integer", "i0=1 i0=2", "i0-=2", "i0=1" },
436 { "-= twice", "s0=a s0=b s0=c", "s0-=a s0-=c", "s0=b" },
437 };
438
439 pairmove_run_cases(cases, NUM_ELEMENTS(cases));
440}
441
442/*
443 * The '!*' operator deletes every instance of the attribute from the
444 * `to` list, regardless of the value.
445 */
446static void test_op_cmp_false(void)
447{
448 static pairmove_case_t const cases[] = {
449 { "!* from empty", "", "s0!*", "" },
450 { "!* absent attribute", "s1=x", "s0!*", "s1=x" },
451 { "!* one instance", "s0=a", "s0!*", "" },
452 { "!* all instances", "s0=a s1=b s0=c", "s0!*", "s1=b" },
453 { "!* integer", "i0=1 i0=2", "i0!*", "" },
454 };
455
456 pairmove_run_cases(cases, NUM_ELEMENTS(cases));
457}
458
459/*
460 * The '==' operator deletes every instance of the attribute that does
461 * not have the value of the `from` pair. The operator keeps every
462 * instance that has the value, and does not add the attribute.
463 */
464static void test_op_cmp_eq(void)
465{
466 static pairmove_case_t const cases[] = {
467 { "== from empty", "", "s0==a", "" },
468 { "== absent attribute", "s1=x", "s0==a", "s1=x" },
469 { "== matching value", "s0=a", "s0==a", "s0=a" },
470 { "== no matching value", "s0=a", "s0==b", "" },
471 { "== some matching", "s0=a s0=b s0=a", "s0==a", "s0=a s0=a" },
472 { "== keeps others", "s1=x s0=b", "s0==a", "s1=x" },
473 };
474
475 pairmove_run_cases(cases, NUM_ELEMENTS(cases));
476}
477
478/*
479 * The '<=' operator replaces the first instance of the attribute that
480 * has a value greater than the value of the `from` pair.
481 * The '>=' operator replaces the first instance of the attribute that
482 * has a value less than the value of the `from` pair.
483 */
484static void test_op_le_ge(void)
485{
486 static pairmove_case_t const cases[] = {
487 { "<= larger value", "i0=10", "i0<=5", "i0=5" },
488
489 /*
490 * When the existing value already satisfies the operator, the merge
491 * keeps the existing value and does not add the value from the `from`
492 * list.
493 */
494 { "<= equal value", "i0=5", "i0<=5", "i0=5" },
495 { "<= smaller value", "i0=3", "i0<=5", "i0=3" },
496 { "<= absent attribute", "i1=1", "i0<=5", "i1=1 i0=5" },
497 { "<= into empty", "", "i0<=5", "i0=5" },
498 { "<= replaces in place", "i1=1 i0=10 s0=a", "i0<=5", "i1=1 i0=5 s0=a" },
499 { "<= all instances", "i0=10 i0=20", "i0<=5", "i0=5 i0=20" },
500
501 { ">= smaller value", "i0=3", "i0>=5", "i0=5" },
502 { ">= equal value", "i0=5", "i0>=5", "i0=5" },
503 { ">= larger value", "i0=9", "i0>=5", "i0=9" },
504 { ">= absent attribute", "i1=1", "i0>=5", "i1=1 i0=5" },
505 { ">= into empty", "", "i0>=5", "i0=5" },
506 { ">= replaces in place", "i1=1 i0=3 s0=a", "i0>=5", "i1=1 i0=5 s0=a" },
507 };
508
509 pairmove_run_cases(cases, NUM_ELEMENTS(cases));
510}
511
512/*
513 * The '<=' and '>=' operators with prefixes.
514 *
515 * `radius_pairmove()` compares the values with the '==' operator, and
516 * prefixes only compare as equal or not equal. There is no ordering, so
517 * '<=' never replaces a prefix, and '>=' replaces a prefix whenever the
518 * prefixes are different. This is true even when one prefix contains
519 * the other.
520 */
521static void test_op_le_ge_prefix(void)
522{
523 static pairmove_case_t const cases[] = {
524 /*
525 * IPv4.
526 */
527 { "<= ipv4 prefix, inside", "p4=10.0.0.0/8", "p4<=10.1.0.0/16", "p4=10.0.0.0/8" },
528 { "<= ipv4 prefix, equal", "p4=10.0.0.0/8", "p4<=10.0.0.0/8", "p4=10.0.0.0/8" },
529 { "<= ipv4 prefix, contains", "p4=10.0.0.0/8", "p4<=0.0.0.0/0", "p4=10.0.0.0/8" },
530 { "<= ipv4 prefix, disjoint", "p4=10.0.0.0/8", "p4<=192.168.0.0/16", "p4=10.0.0.0/8" },
531 { ">= ipv4 prefix, inside", "p4=10.0.0.0/8", "p4>=10.1.0.0/16", "p4=10.1.0.0/16" },
532 { ">= ipv4 prefix, equal", "p4=10.0.0.0/8", "p4>=10.0.0.0/8", "p4=10.0.0.0/8" },
533 { ">= ipv4 prefix, contains", "p4=10.0.0.0/8", "p4>=0.0.0.0/0", "p4=0.0.0.0/0" },
534 { ">= ipv4 prefix, disjoint", "p4=10.0.0.0/8", "p4>=192.168.0.0/16", "p4=192.168.0.0/16" },
535
536 /*
537 * IPv6.
538 */
539 { "<= ipv6 prefix, inside", "p6=2001:db8::/32", "p6<=2001:db8:1::/48", "p6=2001:db8::/32" },
540 { "<= ipv6 prefix, equal", "p6=2001:db8::/32", "p6<=2001:db8::/32", "p6=2001:db8::/32" },
541 { "<= ipv6 prefix, contains", "p6=2001:db8::/32", "p6<=::/0", "p6=2001:db8::/32" },
542 { "<= ipv6 prefix, disjoint", "p6=2001:db8::/32", "p6<=fe80::/10", "p6=2001:db8::/32" },
543 { ">= ipv6 prefix, inside", "p6=2001:db8::/32", "p6>=2001:db8:1::/48", "p6=2001:db8:1::/48" },
544 { ">= ipv6 prefix, equal", "p6=2001:db8::/32", "p6>=2001:db8::/32", "p6=2001:db8::/32" },
545 { ">= ipv6 prefix, contains", "p6=2001:db8::/32", "p6>=::/0", "p6=::/0" },
546 { ">= ipv6 prefix, disjoint", "p6=2001:db8::/32", "p6>=fe80::/10", "p6=fe80::/10" },
547
548 /*
549 * No existing prefix, so the pair is added.
550 */
551 { "<= prefix, absent", "", "p4<=10.0.0.0/8", "p4=10.0.0.0/8" },
552 { ">= prefix, absent", "", "p6>=2001:db8::/32", "p6=2001:db8::/32" },
553 };
554
555 pairmove_run_cases(cases, NUM_ELEMENTS(cases));
556}
557
558/*
559 * The operators apply to the original `to` list only. The operators do
560 * not apply to attributes that the same `radius_pairmove()` call adds.
561 */
562static void test_original_list_only(void)
563{
564 static pairmove_case_t const cases[] = {
565 { "+= then :=", "s0=a", "s0+=x s0:=y", "s0=y s0=x" },
566 { "+= then -=", "s0=a", "s0+=a s0-=a", "s0=a" },
567 { "+= then !*", "s0=a", "s0+=b s0!*", "s0=b" },
568 { ":= then -=", "s0=a", "s0:=b s0-=b", "s0=b" },
569 { "^= then !*", "s0=a", "s0^=b s0!*", "s0=b" },
570 };
571
572 pairmove_run_cases(cases, NUM_ELEMENTS(cases));
573}
574
575/*
576 * The '=' operator looks for an instance of the attribute in the `to`
577 * list as the earlier pairs of the `from` list have left it. An
578 * instance which an earlier pair replaced, or deleted, does not count.
579 * An instance which no earlier pair touched does count.
580 */
581static void test_op_eq_after_edit(void)
582{
583 static pairmove_case_t const cases[] = {
584 { ":= then =", "s0=a", "s0:=b s0=c", "s0=b s0=c" },
585 { "-= then =", "s0=a", "s0-=a s0=b", "s0=b" },
586 { "!* then =", "s0=a s0=x", "s0!* s0=b", "s0=b" },
587 { "<= then =", "i0=10", "i0<=5 i0=7", "i0=5 i0=7" },
588 { ">= then =", "i0=1", "i0>=5 i0=7", "i0=5 i0=7" },
589
590 /*
591 * The second instance was not touched, so '=' finds it.
592 */
593 { ":= then =, two instances", "s0=a s0=x", "s0:=b s0=c", "s0=b s0=x" },
594 { "-= then =, two instances", "s0=a s0=x", "s0-=a s0=c", "s0=x" },
595
596 /*
597 * An edit of another attribute has no effect.
598 */
599 { "other attribute then =", "s0=a s1=x", "s1:=y s0=c", "s0=a s1=y" },
600 };
601
602 pairmove_run_cases(cases, NUM_ELEMENTS(cases));
603}
604
605/*
606 * A pair which `radius_pairmove()` moves out of the `from` list must leave
607 * the `from` list completely. In each case below, a pair which stays in the
608 * result is followed in the `from` list by a pair which is freed, replaces
609 * an existing pair, or goes to the start of the list. If the first pair
610 * stays linked to the second one, then the result has extra entries at the
611 * end, or the result contains a pair which has been freed.
612 */
613static void test_stale_next(void)
614{
615 static pairmove_case_t const cases[] = {
616 /*
617 * The pair which follows is freed.
618 */
619 { "+= then -=", "s0=a", "s1+=x s0-=a", "s1=x" },
620 { "+= then !*", "s0=a s1=b", "i0+=1 s0!*", "s1=b i0=1" },
621 { "+= then == (no match)", "s0=a", "s1+=x s0==b", "s1=x" },
622 { "+= then = (present)", "s0=a", "s1+=x s0=b", "s0=a s1=x" },
623 { "+= then <= (kept)", "i0=3", "s1+=x i0<=5", "i0=3 s1=x" },
624 { "+= then >= (kept)", "i0=9", "s1+=x i0>=5", "i0=9 s1=x" },
625 { "+= then two freed", "s0=a s2=c", "s1+=x s0-=a s2!*", "s1=x" },
626 { "two += then freed", "s0=a", "s1+=x s2+=y s0-=a", "s1=x s2=y" },
627
628 /*
629 * The pair which follows replaces an existing pair.
630 */
631 { "+= then :=", "s0=a", "s1+=x s0:=y", "s0=y s1=x" },
632 { "+= then <= (replaced)", "i0=10", "s1+=x i0<=5", "i0=5 s1=x" },
633 { "+= then >= (replaced)", "i0=1", "s1+=x i0>=5", "i0=5 s1=x" },
634
635 /*
636 * The pair which follows goes to the start of the list.
637 */
638 { "+= then ^=", "s0=a", "s1+=x s2^=y", "s2=y s0=a s1=x" },
639 { "^= then -=", "s0=a s2=z", "s1^=x s0-=a", "s1=x s2=z" },
640
641 /*
642 * The replacing pair is followed by a pair which is freed.
643 */
644 { ":= then -=", "s0=a s2=z", "s0:=b s1-=q", "s0=b s2=z" },
645 { ":= then !*", "s0=a s2=z", "s0:=b s2!*", "s0=b" },
646
647 /*
648 * A pair which is added is followed by one which is added.
649 */
650 { "= then +=", "", "s0=a s1+=b", "s0=a s1=b" },
651 { ":= (absent) then -=", "", "s0:=a s1-=b", "s0=a" },
652 };
653
654 pairmove_run_cases(cases, NUM_ELEMENTS(cases));
655}
656
657/*
658 * These cases use several operators and several attributes in one call
659 * to `radius_pairmove()`.
660 */
661static void test_mixed(void)
662{
663 static pairmove_case_t const cases[] = {
664 { "all operators",
665 "s0=a s1=b s2=c i0=1 i1=2",
666 "s0:=A s1-=b s2!* i0<=0 i1+=3 un=bob",
667 "s0=A i0=0 i1=2 i1=3 un=bob" },
668 { "different attributes",
669 "s0=a i0=1",
670 "s1=b i1=2 s2=c",
671 "s0=a i0=1 s1=b i1=2 s2=c" },
672 { "prepend, replace, append",
673 "s0=a s1=b",
674 "i0^=1 s1:=B i1+=2",
675 "i0=1 s0=a s1=B i1=2" },
676 };
677
678 pairmove_run_cases(cases, NUM_ELEMENTS(cases));
679}
680
681/** Merge lists of up to 100 pairs
682 *
683 * This test checks the sizes of the temporary arrays that
684 * `radius_pairmove()` allocates. The lengths of the `to` list and the
685 * `from` list are 1 and 100, 100 and 1, 100 and 100, 0 and 100, and 100
686 * and 0.
687 */
688static void test_long_lists(void)
689{
690 static struct {
691 size_t to_num;
692 size_t from_num;
693 } const sizes[] = { { 1, 100 }, { 100, 1 }, { 100, 100 }, { 0, 100 }, { 100, 0 } };
694
695 size_t i, j;
696 list_target_t target;
697
698 for (i = 0; i < NUM_ELEMENTS(sizes); i++) {
699 for (target = LIST_REQUEST; target < LIST_NUM; target++) {
701 TALLOC_CTX *ctx;
702 fr_pair_list_t *to;
703 fr_pair_list_t from;
704 fr_pair_t *vp;
705 size_t before;
706
708 to = list_target(&ctx, request, target);
709 before = talloc_total_blocks(request);
710
711 fr_pair_list_init(&from);
712
713 for (j = 0; j < sizes[i].to_num; j++) {
714 TEST_ASSERT(fr_pair_append_by_da(ctx, &vp, to, attr_names[0].da) == 0);
715 TEST_ASSERT(fr_pair_value_from_str(vp, "to", 2, NULL, false) == 0);
716 vp->op = T_OP_EQ;
717 }
718
719 for (j = 0; j < sizes[i].from_num; j++) {
720 TEST_ASSERT(fr_pair_append_by_da(ctx, &vp, &from, attr_names[1].da) == 0);
721 TEST_ASSERT(fr_pair_value_from_str(vp, "from", 4, NULL, false) == 0);
722 vp->op = T_OP_ADD_EQ;
723 }
724
725 radius_pairmove(request, to, &from);
726
727 TEST_CASE_("long lists %zu and %zu: %s", sizes[i].to_num, sizes[i].from_num,
728 list_target_names[target]);
729 TEST_CHECK(fr_pair_list_num_elements(to) == (sizes[i].to_num + sizes[i].from_num));
730 TEST_MSG("Expected length : %zu", sizes[i].to_num + sizes[i].from_num);
731 TEST_MSG("Got length : %zu", fr_pair_list_num_elements(to));
732
733 /*
734 * The `to` attributes come first, then the `from` attributes.
735 */
736 j = 0;
737 fr_pair_list_foreach(to, pair) {
738 TEST_CHECK(pair->da == ((j < sizes[i].to_num) ? attr_names[0].da : attr_names[1].da));
739 j++;
740 }
741
743 TEST_CHECK(talloc_total_blocks(request) == before);
744
746 }
747 }
748}
749
750/*
751 * The `to` list is empty. The operators which add a pair add it to the
752 * empty list. The operators which look for an instance of the attribute
753 * find nothing, and then delete nothing.
754 */
755static void test_empty_to(void)
756{
757 static pairmove_case_t const cases[] = {
758 /*
759 * One operator.
760 */
761 { "= (empty to)", "", "s0=a", "s0=a" },
762 { ":= (empty to)", "", "s0:=a", "s0=a" },
763 { "+= (empty to)", "", "s0+=a", "s0=a" },
764 { "^= (empty to)", "", "s0^=a", "s0=a" },
765 { "-= (empty to)", "", "s0-=a", "" },
766 { "!* (empty to)", "", "s0!*", "" },
767 { "== (empty to)", "", "s0==a", "" },
768 { "<= (empty to)", "", "i0<=5", "i0=5" },
769 { ">= (empty to)", "", "i0>=5", "i0=5" },
770 { "<= prefix (empty to)", "", "p4<=10.0.0.0/8", "p4=10.0.0.0/8" },
771 { ">= prefix (empty to)", "", "p6>=2001:db8::/32", "p6=2001:db8::/32" },
772
773 /*
774 * The same operator twice. Because the `to` list has no instance of
775 * the attribute, the merge adds both pairs.
776 */
777 { "= twice", "", "s0=a s0=b", "s0=a s0=b" },
778 { ":= twice", "", "s0:=a s0:=b", "s0=a s0=b" },
779 { "+= twice", "", "s0+=a s0+=b", "s0=a s0=b" },
780 { "^= twice", "", "s0^=a s0^=b", "s0=b s0=a" },
781 { "-= twice", "", "s0-=a s0-=b", "" },
782 { "!* twice", "", "s0!* s0!*", "" },
783 { "== twice", "", "s0==a s0==b", "" },
784 { "<= twice", "", "i0<=5 i0<=3", "i0=5 i0=3" },
785 { ">= twice", "", "i0>=5 i0>=7", "i0=5 i0=7" },
786
787 /*
788 * A later pair does not apply to a pair which an earlier pair added.
789 */
790 { "= then :=", "", "s0=a s0:=b", "s0=a s0=b" },
791 { ":= then =", "", "s0:=a s0=b", "s0=a s0=b" },
792 { "+= then -=", "", "s0+=a s0-=a", "s0=a" },
793 { "+= then !*", "", "s0+=a s0!*", "s0=a" },
794 { "+= then ==", "", "s0+=a s0==b", "s0=a" },
795 { "^= then !*", "", "s0^=a s0!*", "s0=a" },
796
797 /*
798 * Pairs which delete nothing do not change the pairs which are added.
799 */
800 { "deletes then add", "", "s0-=a s1!* s2==c i0=1", "i0=1" },
801 { "add then deletes", "", "i0=1 s0-=a s1!* s2==c", "i0=1" },
802 { "add between deletes", "", "s0-=a i0=1 s1!*", "i0=1" },
803
804 /*
805 * Several attributes, in the order of the `from` list.
806 */
807 { "different attributes", "", "s0=a i0:=1 s1+=b", "s0=a i0=1 s1=b" },
808 { "prepend and append", "", "s0=a s1^=b s2+=c", "s1=b s0=a s2=c" },
809 { "all operators",
810 "",
811 "s0=a i0:=1 s1+=b s2^=c i1<=4 s0-=x s1!* un=bob",
812 "s2=c s0=a i0=1 s1=b i1=4 un=bob" },
813 };
814
815 pairmove_run_cases(cases, NUM_ELEMENTS(cases));
816}
817
819 { "op_eq", test_op_eq },
820 { "op_set", test_op_set },
821 { "op_add", test_op_add },
822 { "op_prepend", test_op_prepend },
823 { "op_sub", test_op_sub },
824 { "op_cmp_false", test_op_cmp_false },
825 { "op_cmp_eq", test_op_cmp_eq },
826 { "op_le_ge", test_op_le_ge },
827 { "op_le_ge_prefix", test_op_le_ge_prefix },
828 { "original_list_only", test_original_list_only },
829 { "op_eq_after_edit", test_op_eq_after_edit },
830 { "stale_next", test_stale_next },
831 { "mixed", test_mixed },
832 { "long_lists", test_long_lists },
833 { "empty_to", test_empty_to },
834
835 { NULL, NULL }
836};
static int const char char buffer[256]
Definition acutest.h:641
#define TEST_CHECK(cond)
Definition acutest.h:101
#define TEST_CASE_(...)
Definition acutest.h:202
strcpy(log_entry->msg, buffer)
#define TEST_ASSERT(cond)
Definition acutest.h:125
#define TEST_MSG(...)
Definition acutest.h:234
#define TEST_CHECK_STRCMP(_got, _exp)
static uint8_t request[4]
#define NUM_ELEMENTS(_t)
Definition build.h:437
#define fr_exit_now(_x)
Exit without calling atexit() handlers, producing a log message in debug builds.
Definition debug.h:272
int fr_dict_test_init(TALLOC_CTX *ctx, fr_dict_t **dict_p, fr_dict_test_attr_t const *test_defs)
Initialise a test dictionary and add our test_defs to it.
Definition dict_test.c:248
fr_dict_test_attr_number_t attr
Attribute number to create.
Definition dict_test.h:100
fr_dict_test_attr_number_t
Test attribute numbers.
Definition dict_test.h:35
@ FR_TEST_ATTR_INVALID
Definition dict_test.h:36
Test enumeration attributes.
Definition dict_test.h:99
Test enumeration values.
Definition dict_test.h:92
talloc_free(hp)
fr_packet_t * fr_packet_alloc(TALLOC_CTX *ctx, bool new_vector)
Allocate a new fr_packet_t.
Definition packet.c:38
@ FR_TYPE_IPV6_PREFIX
IPv6 Prefix.
@ FR_TYPE_STRING
String of printable characters.
@ FR_TYPE_UINT32
32 Bit unsigned integer.
@ FR_TYPE_IPV4_PREFIX
IPv4 Prefix.
int fr_pair_append_by_da(TALLOC_CTX *ctx, fr_pair_t **out, fr_pair_list_t *list, fr_dict_attr_t const *da)
Alloc a new fr_pair_t (and append)
Definition pair.c:1416
int fr_pair_value_from_str(fr_pair_t *vp, char const *value, size_t inlen, fr_sbuff_unescape_rules_t const *uerules, UNUSED bool tainted)
Convert string value to native attribute value.
Definition pair.c:2552
int fr_pair_append(fr_pair_list_t *list, fr_pair_t *to_add)
Add a VP to the end of the list.
Definition pair.c:1297
fr_pair_t * fr_pair_afrom_da(TALLOC_CTX *ctx, fr_dict_attr_t const *da)
Dynamically allocate a new attribute and assign a fr_dict_attr_t.
Definition pair.c:284
void fr_pair_list_init(fr_pair_list_t *list)
Initialise a pair list header.
Definition pair.c:47
void radius_pairmove(request_t *request, fr_pair_list_t *to, fr_pair_list_t *from)
Merge the "from" list into the "to" list, using the operator of each pair.
Definition pairmove.c:168
static void pairmove_run_cases(pairmove_case_t const *cases, size_t num)
static void test_op_cmp_eq(void)
static void test_stale_next(void)
TEST_LIST
static void test_op_le_ge_prefix(void)
static void test_op_le_ge(void)
static TALLOC_CTX * autofree
static void test_empty_to(void)
static request_t * request_make(void)
Make a request with empty pair lists.
static void test_original_list_only(void)
static char const * list_target_names[LIST_NUM]
static void test_op_cmp_false(void)
static fr_dict_test_attr_t const test_attrs[]
static void test_op_set(void)
static void test_op_eq(void)
static void test_op_prepend(void)
static void test_long_lists(void)
Merge lists of up to 100 pairs.
static void list_alloc(TALLOC_CTX *ctx, fr_pair_list_t *list, char const *spec)
Build a list of pairs from the text in spec
static void test_op_add(void)
static fr_dict_t * test_dict
static char const * list_str(fr_pair_list_t const *list)
Render a list in the text form that list_alloc() accepts.
char const * expect
Expected text form of the to list after the merge.
static void test_op_sub(void)
static void test_op_eq_after_edit(void)
static void test_mixed(void)
static struct @89 op_names[]
char const * name
char const * from
Text form of the from list.
static void test_init(void)
Global initialisation.
static struct @88 attr_names[]
list_target_t
@ LIST_REPLY
@ LIST_REQUEST
@ LIST_NUM
@ LIST_CONTROL
static void pairmove_run(pairmove_case_t const *tc, list_target_t target)
char const * to
Text form of the to list before the merge.
static fr_pair_list_t * list_target(TALLOC_CTX **ctx, request_t *request, list_target_t target)
Get the pair list and the talloc context for a target.
int request_global_init(void)
Definition request.c:614
#define request_local_alloc_external(_ctx, _args)
Allocate a new external request outside of the request pool.
Definition request.h:336
Optional arguments for initialising requests.
Definition request.h:288
static char const * name
ssize_t fr_sbuff_in_strcpy(fr_sbuff_t *sbuff, char const *str)
Copy bytes into the sbuff up to the first \0.
Definition sbuff.c:1529
#define FR_SBUFF_OUT(_start, _len_or_end)
#define fr_sbuff_used(_sbuff_or_marker)
#define fr_sbuff_in_char(_sbuff,...)
fr_aka_sim_id_type_t type
fr_pair_t * vp
Stores an attribute, a value and various bits of other data.
Definition pair.h:68
fr_dict_attr_t const *_CONST da
Dictionary attribute defines the attribute number, vendor and type of the pair.
Definition pair.h:69
#define talloc_autofree_context
The original function is deprecated, so replace it with our version.
Definition talloc.h:55
char const * fr_tokens[T_TOKEN_LAST]
Definition token.c:146
@ T_OP_SUB_EQ
Definition token.h:68
@ T_INVALID
Definition token.h:37
@ T_OP_EQ
Definition token.h:81
@ T_OP_SET
Definition token.h:82
@ T_OP_ADD_EQ
Definition token.h:67
@ T_OP_CMP_FALSE
Definition token.h:103
@ T_OP_CMP_EQ
Definition token.h:104
@ T_OP_LE
Definition token.h:98
@ T_OP_GE
Definition token.h:96
@ T_OP_PREPEND
Definition token.h:83
enum fr_token fr_token_t
bool fr_pair_list_empty(fr_pair_list_t const *list)
Is a valuepair list empty.
#define fr_pair_list_foreach(_list_head, _iter)
Iterate over the contents of a fr_pair_list_t.
Definition pair.h:285
void fr_pair_list_free(fr_pair_list_t *list)
Free memory used by a valuepair list.
size_t fr_pair_list_num_elements(fr_pair_list_t const *list)
Get the length of a list of fr_pair_t.
void fr_perror(char const *fmt,...)
Print the current error to stderr with a prefix.
Definition strerror.c:737
int fr_check_lib_magic(uint64_t magic)
Check if the application linking to the library has the correct magic number.
Definition version.c:40
#define RADIUSD_MAGIC_NUMBER
Definition version.h:81
ssize_t fr_value_box_print(fr_sbuff_t *out, fr_value_box_t const *data, fr_sbuff_escape_rules_t const *e_rules)
Print one boxed value to a string.
Definition value.c:6293
static fr_sbuff_err_t char ** out
Definition value.h:1061
static fr_sbuff_err_t char size_t * len
Definition value.h:1061