2 * This file is part of GNUnet
3 * Copyright (C) 2012-2017 GNUnet e.V.
5 * GNUnet is free software: you can redistribute it and/or modify it
6 * under the terms of the GNU Affero General Public License as published
7 * by the Free Software Foundation, either version 3 of the License,
8 * or (at your option) any later version.
10 * GNUnet is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Affero General Public License for more details.
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 SPDX-License-Identifier: AGPL3.0-or-later
21 * @file src/regex/regex_test_lib.c
22 * @brief library to read regexes representing IP networks from a file.
23 * and simplyfinying the into one big regex, in order to run
24 * tests (regex performance, cadet profiler).
25 * @author Bartlomiej Polot
29 #include "gnunet_util_lib.h"
33 * Struct to hold the tree formed by prefix-combining the regexes.
35 struct RegexCombineCtx
38 * Child nodes with same prefix and token.
40 struct RegexCombineCtx **children;
43 * Alphabet size (how many @a children there are)
57 * Convert a character into its int value depending on the base used
60 * @param size base (2, 8 or 16(hex))
62 * @return Int in range [0, (base-1)]
65 c2i (char c, int size)
75 if ((c >= '0') &&(c <= '9') )
77 else if ((c >= 'A') &&(c <= 'F') )
79 else if ((c >= 'a') &&(c <= 'f') )
83 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
84 "Cannot convert char %c in base %u\n",
97 * Printf spaces to indent the regex tree
99 * @param n Indentation level
104 for (int i = 0; i < n; i++)
105 fprintf (stderr, "| ");
110 * Printf the combined regex ctx.
112 * @param ctx The ctx to printf
113 * @param level Indentation level to start with
116 debugctx (struct RegexCombineCtx *ctx, int level)
122 fprintf (stderr, "%u:'%s'\n", c2i (ctx->s[0], ctx->size), ctx->s);
125 fprintf (stderr, "ROOT (base %u)\n", ctx->size);
126 for (unsigned int i = 0; i < ctx->size; i++)
128 if (NULL != ctx->children[i])
131 debugctx (ctx->children[i], level + 1);
140 * Add a single regex to a context, combining with exisiting regex by-prefix.
142 * @param ctx Context with 0 or more regexes.
143 * @param regex Regex to add.
146 regex_add (struct RegexCombineCtx *ctx,
151 * Create and initialize a new RegexCombineCtx.
153 * @param alphabet_size Size of the alphabet (and the Trie array)
155 static struct RegexCombineCtx *
156 new_regex_ctx (unsigned int alphabet_size)
158 struct RegexCombineCtx *ctx;
161 array_size = sizeof(struct RegexCombineCtx *) * alphabet_size;
162 ctx = GNUNET_new (struct RegexCombineCtx);
163 ctx->children = GNUNET_malloc (array_size);
164 ctx->size = alphabet_size;
171 move_children (struct RegexCombineCtx *dst,
172 const struct RegexCombineCtx *src)
176 array_size = sizeof(struct RegexCombineCtx *) * src->size;
177 GNUNET_memcpy (dst->children,
180 for (unsigned int i = 0; i < src->size; i++)
182 src->children[i] = NULL;
188 * Extract a string from all prefix-combined regexes.
190 * @param ctx Context with 0 or more regexes.
192 * @return Regex that matches any of the added regexes.
195 regex_combine (struct RegexCombineCtx *ctx)
197 struct RegexCombineCtx *p;
205 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "new combine %s\n", ctx->s);
206 regex = GNUNET_strdup ("");
208 for (i = 0; i < ctx->size; i++)
210 p = ctx->children[i];
213 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
214 "adding '%s' to innner %s\n",
216 s = regex_combine (p);
217 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, " total '%s'\n", s);
224 GNUNET_asprintf (&tmp, "%s%s|", regex, s);
225 GNUNET_free_non_null (regex);
228 GNUNET_free_non_null (s);
229 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, " so far '%s' for inner %s\n", regex,
233 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "opt: %d, innner: '%s'\n", opt, regex);
234 len = strlen (regex);
237 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "empty, returning ''\n");
239 return NULL == ctx->s ? NULL : GNUNET_strdup (ctx->s);
242 if ('|' == regex[len - 1])
243 regex[len - 1] = '\0';
248 GNUNET_asprintf (&s, "%s(%s)?", ctx->s, regex);
250 GNUNET_asprintf (&s, "%s(%s)", ctx->s, regex);
255 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "partial: %s\n", regex);
261 * Get the number of matching characters on the prefix of both strings.
263 * @param s1 String 1.
264 * @param s2 String 2.
266 * @return Number of characters of matching prefix.
269 get_prefix_length (const char *s1, const char *s2)
278 limit = l1 > l2 ? l2 : l1;
280 for (i = 0; i < limit; i++)
290 * Return the child context with the longest prefix match with the regex.
291 * Usually only one child will match, search all just in case.
293 * @param ctx Context whose children to search.
294 * @param regex String to match.
296 * @return Child with the longest prefix, NULL if no child matches.
298 static struct RegexCombineCtx *
299 get_longest_prefix (struct RegexCombineCtx *ctx, const char *regex)
301 struct RegexCombineCtx *p;
302 struct RegexCombineCtx *best;
310 for (i = 0; i < ctx->size; i++)
312 p = ctx->children[i];
316 l = get_prefix_length (p->s, regex);
319 GNUNET_break (0 == best_l);
328 regex_add_multiple (struct RegexCombineCtx *ctx,
330 struct RegexCombineCtx **children)
335 struct RegexCombineCtx *newctx;
343 /* Does the regex cover *all* possible children? Then don't add any,
344 * as it will be covered by the post-regex "(a-z)*"
348 for (i = 1UL; i < l; i++)
350 if ((regex[i] != '|') &&(regex[i] != ')') )
355 if (count == ctx->size)
360 /* Add every component as a child node */
362 for (i = 1UL; i < l; i++)
364 if ((regex[i] != '|') &&(regex[i] != ')') )
367 newctx = new_regex_ctx (ctx->size);
368 newctx->s = GNUNET_strdup (tmp);
369 if (children != NULL)
370 GNUNET_memcpy (newctx->children,
372 sizeof(*children) * ctx->size);
373 ctx->children[c2i (tmp[0], ctx->size)] = newctx;
379 * Add a single regex to a context, splitting the exisiting state.
381 * We only had a partial match, split existing state, truncate the current node
382 * so it only contains the prefix, add suffix(es) as children.
384 * @param ctx Context to split.
385 * @param len Lenght of ctx->s
386 * @param prefix_l Lenght of common prefix of the new regex and @a ctx->s
389 regex_split (struct RegexCombineCtx *ctx,
391 unsigned int prefix_l)
393 struct RegexCombineCtx *newctx;
397 suffix = GNUNET_malloc (len - prefix_l + 1);
399 * We can use GNUNET_strlcpy because ctx->s is null-terminated
401 GNUNET_strlcpy (suffix, &ctx->s[prefix_l], len - prefix_l + 1);
403 /* Suffix saved, truncate current node so it only contains the prefix,
404 * copy any children nodes to put as grandchildren and initialize new empty
407 ctx->s[prefix_l] = '\0';
409 /* If the suffix is an OR expression, add multiple children */
410 if ('(' == suffix[0])
412 struct RegexCombineCtx **tmp;
415 ctx->children = GNUNET_malloc (sizeof(*tmp) * ctx->size);
416 regex_add_multiple (ctx, suffix, tmp);
417 GNUNET_free (suffix);
422 /* The suffix is a normal string, add as one node */
423 newctx = new_regex_ctx (ctx->size);
425 move_children (newctx, ctx);
426 idx = c2i (suffix[0], ctx->size);
427 ctx->children[idx] = newctx;
432 * Add a single regex to a context, combining with exisiting regex by-prefix.
434 * @param ctx Context with 0 or more regexes.
435 * @param regex Regex to add.
438 regex_add (struct RegexCombineCtx *ctx, const char *regex)
440 struct RegexCombineCtx *p;
441 struct RegexCombineCtx *newctx;
443 unsigned int prefix_l;
449 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
450 "regex_add '%s' into '%s'\n",
456 /* If the regex is in the form of (a|b|c), add every character separately */
459 regex_add_multiple (ctx, regex, NULL);
463 p = get_longest_prefix (ctx, regex);
466 /* There is some prefix match, reduce regex and try again */
467 prefix_l = get_prefix_length (p->s, regex);
468 rest_s = &p->s[prefix_l];
469 rest_r = ®ex[prefix_l];
470 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "chosen '%s' [%u]\n", p->s, prefix_l);
471 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "prefix r '%.*s'\n", prefix_l, p->s);
472 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "rest r '%s'\n", rest_r);
473 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "rest s '%s'\n", rest_s);
477 regex_split (p, len, prefix_l);
479 regex_add (p, rest_r);
483 /* There is no prefix match, add new */
484 idx = c2i (regex[0], ctx->size);
485 if ((NULL == ctx->children[idx])&&(NULL != ctx->s))
487 /* this was the end before, add empty string */
488 newctx = new_regex_ctx (ctx->size);
489 newctx->s = GNUNET_strdup ("");
490 ctx->children[idx] = newctx;
492 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, " no match\n");
493 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, " new state %s\n", regex);
494 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, " under %s\n", ctx->s);
495 newctx = new_regex_ctx (ctx->size);
496 newctx->s = GNUNET_strdup (regex);
497 ctx->children[idx] = newctx;
502 * Free all resources used by the context node and all its children.
504 * @param ctx Context to free.
507 regex_ctx_destroy (struct RegexCombineCtx *ctx)
514 for (i = 0; i < ctx->size; i++)
516 regex_ctx_destroy (ctx->children[i]);
518 GNUNET_free_non_null (ctx->s); /* 's' on root node is null */
519 GNUNET_free (ctx->children);
525 * Combine an array of regexes into a single prefix-shared regex.
526 * Returns a prefix-combine regex that matches the same strings as
527 * any of the original regexes.
529 * WARNING: only useful for reading specific regexes for specific applications,
530 * namely the gnunet-regex-profiler / gnunet-regex-daemon.
531 * This function DOES NOT support arbitrary regex combining.
533 * @param regexes A NULL-terminated array of regexes.
534 * @param alphabet_size Size of the alphabet the regex uses.
536 * @return A string with a single regex that matches any of the original regexes
539 REGEX_TEST_combine (char *const regexes[], unsigned int alphabet_size)
544 struct RegexCombineCtx *ctx;
546 ctx = new_regex_ctx (alphabet_size);
547 for (i = 0; regexes[i]; i++)
549 current = regexes[i];
550 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Regex %u: %s\n", i, current);
551 regex_add (ctx, current);
554 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "\nCombining...\n");
557 combined = regex_combine (ctx);
559 regex_ctx_destroy (ctx);
566 * Read a set of regexes from a file, one per line and return them in an array
567 * suitable for REGEX_TEST_combine.
568 * The array must be free'd using REGEX_TEST_free_from_file.
570 * @param filename Name of the file containing the regexes.
572 * @return A newly allocated, NULL terminated array of regexes.
575 REGEX_TEST_read_from_file (const char *filename)
577 struct GNUNET_DISK_FileHandle *f;
586 f = GNUNET_DISK_file_open (filename,
587 GNUNET_DISK_OPEN_READ,
588 GNUNET_DISK_PERM_NONE);
591 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
592 "Can't open file %s for reading\n", filename);
595 if (GNUNET_OK != GNUNET_DISK_file_handle_size (f, &size))
597 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
598 "Can't get size of file %s\n", filename);
599 GNUNET_DISK_file_close (f);
602 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
603 "using file %s, size %llu\n",
604 filename, (unsigned long long) size);
606 buffer = GNUNET_malloc (size + 1);
607 GNUNET_DISK_file_read (f, buffer, size);
608 GNUNET_DISK_file_close (f);
609 regexes = GNUNET_malloc (sizeof(char *));
616 regex = GNUNET_malloc (size + 1);
617 len = (size_t) sscanf (&buffer[offset], "%s", regex);
620 len = strlen (regex);
625 regex = GNUNET_realloc (regex, len + 1);
626 GNUNET_array_grow (regexes, nr, nr + 1);
627 GNUNET_assert (NULL == regexes[nr - 2]);
628 regexes[nr - 2] = regex;
629 regexes[nr - 1] = NULL;
632 while (offset < size);
633 GNUNET_free_non_null (regex);
634 GNUNET_free (buffer);
641 * Free all memory reserved for a set of regexes created by read_from_file.
643 * @param regexes NULL-terminated array of regexes.
646 REGEX_TEST_free_from_file (char **regexes)
650 for (i = 0; regexes[i]; i++)
651 GNUNET_free (regexes[i]);
652 GNUNET_free (regexes);
655 /* end of regex_test_lib.c */