From d8000909f4b202625c6448926f8bfd23c7d6579a Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 8 Feb 2023 13:51:07 +0100 Subject: [PATCH] enum: Add facility to register callbacks to print enum names This allows plugins to print names for private values of certain enums. --- src/libstrongswan/tests/suites/test_enum.c | 105 ++++++++++++++++++++- src/libstrongswan/utils/enum.c | 35 ++++++- src/libstrongswan/utils/enum.h | 86 +++++++++++++++++ 3 files changed, 223 insertions(+), 3 deletions(-) diff --git a/src/libstrongswan/tests/suites/test_enum.c b/src/libstrongswan/tests/suites/test_enum.c index c2329bdba4..da1269dae2 100644 --- a/src/libstrongswan/tests/suites/test_enum.c +++ b/src/libstrongswan/tests/suites/test_enum.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 Tobias Brunner + * Copyright (C) 2013-2023 Tobias Brunner * * Copyright (C) secunet Security Networks AG * @@ -441,6 +441,104 @@ START_TEST(test_enum_printf_hook_width) } END_TEST +typedef struct { + int val; + char *name; +} test_enum_cb_data_t; + +static test_enum_cb_data_t test_enum_cb_data[] = { + { 10, "TEN" }, + { 11, "11" } +}; + +CALLBACK(enum_cont_cb, int, + test_enum_cb_data_t *d, enum_name_t *e, int val, char *buf, size_t len) +{ + if (val == d->val) + { + return snprintf(buf, len, "%s", d->name); + } + return 0; +} + +START_TEST(test_enum_name_cb_cont) +{ + char buf[128]; + + snprintf(buf, sizeof(buf), "%N", test_enum_cont_names, 10); + ck_assert_str_eq("(10)", buf); + ck_assert(enum_name_cb_add(test_enum_cont_names, enum_cont_cb, + &test_enum_cb_data[0])); + ck_assert(enum_name_cb_add(test_enum_cont_names, enum_cont_cb, + &test_enum_cb_data[1])); + /* exceeds the current limit */ + ck_assert(!enum_name_cb_add(test_enum_cont_names, enum_cont_cb, + &test_enum_cb_data[0])); + snprintf(buf, sizeof(buf), "%N", test_enum_cont_names, 10); + ck_assert_str_eq("TEN", buf); + snprintf(buf, sizeof(buf), "%N", test_enum_cont_names, 11); + ck_assert_str_eq("11", buf); + snprintf(buf, sizeof(buf), "%N", test_enum_cont_names, CONT2); + ck_assert_str_eq("CONT2", buf); + enum_name_cb_remove(test_enum_cont_names, enum_cont_cb); + snprintf(buf, sizeof(buf), "%N", test_enum_cont_names, 11); + ck_assert_str_eq("(11)", buf); +} +END_TEST + +static int EXTRA_FLAG = (1 << 15); + +CALLBACK(enum_flag_cb_extra, int, + void *user, enum_name_t *e, int val, char *buf, size_t len) +{ + if (val == EXTRA_FLAG) + { + return snprintf(buf, len, "EXTRA"); + } + return 0; +} + +CALLBACK(enum_flag_cb_f2, int, + void *user, enum_name_t *e, int val, char *buf, size_t len) +{ + if (val == FLAG2) + { + return snprintf(buf, len, "F2"); + } + return 0; +} + +START_TEST(test_enum_name_cb_flags) +{ + char buf[128]; + + snprintf(buf, sizeof(buf), "%N", test_enum_flags_names, EXTRA_FLAG); + ck_assert_str_eq("(0x8000)", buf); + ck_assert(enum_name_cb_add(test_enum_flags_names, enum_flag_cb_extra, NULL)); + snprintf(buf, sizeof(buf), "%N", test_enum_flags_names, EXTRA_FLAG); + ck_assert_str_eq("EXTRA", buf); + snprintf(buf, sizeof(buf), "%N", test_enum_flags_names, FLAG2 | EXTRA_FLAG); + ck_assert_str_eq("FLAG2 | EXTRA", buf); + enum_name_cb_remove(test_enum_flags_names, enum_flag_cb_extra); + + /* the callback allows printing a name for previously suppressed flags */ + snprintf(buf, sizeof(buf), "%N", test_enum_flags_null_names, FLAG2 | FLAG3); + ck_assert_str_eq("FLAG3", buf); + ck_assert(enum_name_cb_add(test_enum_flags_null_names, enum_flag_cb_f2, NULL)); + snprintf(buf, sizeof(buf), "%N", test_enum_flags_null_names, FLAG2 | FLAG3); + ck_assert_str_eq("F2 | FLAG3", buf); + ck_assert(enum_name_cb_add(test_enum_flags_null_names, enum_flag_cb_extra, NULL)); + snprintf(buf, sizeof(buf), "%N", test_enum_flags_null_names, FLAG2 | EXTRA_FLAG); + ck_assert_str_eq("F2 | EXTRA", buf); + enum_name_cb_remove(test_enum_flags_null_names, enum_flag_cb_f2); + snprintf(buf, sizeof(buf), "%N", test_enum_flags_null_names, FLAG2 | EXTRA_FLAG); + ck_assert_str_eq("EXTRA", buf); + enum_name_cb_remove(test_enum_flags_null_names, enum_flag_cb_extra); + snprintf(buf, sizeof(buf), "%N", test_enum_flags_null_names, FLAG2 | EXTRA_FLAG); + ck_assert_str_eq("(0x8000)", buf); +} +END_TEST + Suite *enum_suite_create() { Suite *s; @@ -480,5 +578,10 @@ Suite *enum_suite_create() tcase_add_test(tc, test_enum_printf_hook_width); suite_add_tcase(s, tc); + tc = tcase_create("enum_name_cb"); + tcase_add_test(tc, test_enum_name_cb_cont); + tcase_add_test(tc, test_enum_name_cb_flags); + suite_add_tcase(s, tc); + return s; } diff --git a/src/libstrongswan/utils/enum.c b/src/libstrongswan/utils/enum.c index 8fc2ab5955..53594cac49 100644 --- a/src/libstrongswan/utils/enum.c +++ b/src/libstrongswan/utils/enum.c @@ -72,6 +72,26 @@ bool enum_from_name_as_int(enum_name_t *e, const char *name, int *val) return FALSE; } +/** + * Check if there any callbacks and if one of them can convert the enum value. + */ +static bool enum_name_cb(enum_name_t *e, u_int val, char *buf, size_t len) +{ + size_t written; + int i; + + for (i = 0; e->cb[i].cb && i < ENUM_NAME_CB_MAX; i++) + { + written = e->cb[i].cb(e->cb[i].user, e, val, buf, len); + if (written > 0 && written < len) + { + return TRUE; + } + } + return FALSE; +} + + /** * Get the position of a flag name using offset calculation */ @@ -122,11 +142,19 @@ char *enum_flags_to_string(enum_name_t *e, u_int val, char *buf, size_t len) if (val & flag) { - char *name = NULL, hex[32]; + char *name = NULL, cb[64], hex[32]; if (flag >= (u_int)cur->first && flag <= (u_int)cur->last) { name = cur->names[find_flag_pos(cur->first, i)]; + if (!name && enum_name_cb(e, flag, cb, sizeof(cb))) + { + name = cb; + } + } + else if (enum_name_cb(e, flag, cb, sizeof(cb))) + { + name = cb; } else { @@ -224,7 +252,10 @@ int enum_printf_hook(printf_hook_data_t *data, printf_hook_spec_t *spec, name = enum_to_name(ed, val); if (name == NULL) { - snprintf(buf, sizeof(buf), "(%d)", val); + if (!ed || !enum_name_cb(ed, val, buf, sizeof(buf))) + { + snprintf(buf, sizeof(buf), "(%d)", val); + } name = buf; } } diff --git a/src/libstrongswan/utils/enum.h b/src/libstrongswan/utils/enum.h index 95987449ca..1b1e203cce 100644 --- a/src/libstrongswan/utils/enum.h +++ b/src/libstrongswan/utils/enum.h @@ -33,6 +33,38 @@ typedef struct enum_name_elem_t enum_name_elem_t; */ #define ENUM_FLAG_MAGIC ((enum_name_elem_t*)~(uintptr_t)0) +/** + * Maximum number of callbacks per enum name that can be registered + */ +#ifndef ENUM_NAME_CB_MAX +#define ENUM_NAME_CB_MAX 2 +#endif + +/** + * Callback used if an enum value can't be mapped to a string statically. + * + * @note This does is primarily used in the printf hook, so it does not map + * values via enum_from_name(). However, it is called in enum_flags_to_string() + * to resolve individual flag values. + * + * @param user user supplied data + * @param e enum name for which callback is invoked + * @param val enum value (or individual flag) to map + * @param buf buffer to write to + * @param len buffer length + * @return number of characters written to buffer (without terminating \0) + */ +typedef int (*enum_name_cb_t)(void *user, enum_name_t *e, int val, + char *buf, size_t len); + +/** + * Struct to store enum name callbacks and context data. + */ +typedef struct { + enum_name_cb_t cb; + void *user; +} enum_name_cb_elem_t; + /** * Struct to store names for enums. * @@ -63,6 +95,8 @@ typedef struct enum_name_elem_t enum_name_elem_t; struct enum_name_t { /** first enum_name_elem_t in chain */ enum_name_elem_t *elem; + /** optional callbacks that serve as fallbacks */ + enum_name_cb_elem_t cb[ENUM_NAME_CB_MAX]; }; /** @@ -151,6 +185,58 @@ struct enum_name_elem_t { countof(((char*[]){__VA_ARGS__}))), \ ENUM_FLAG_MAGIC, { unset, __VA_ARGS__ }}; ENUM_END(name, last) +/** + * Add a callback that serves as fallback if a value can't be found in the given + * enum name list. + * + * @note This should only be called in single-threaded mode, i.e. when plugins + * and plugin features are loaded. + * + * @param e enum names + * @param cb callback to add + * @param user user data to pass to callback + * @return TRUE if cb added successfully + */ +static inline bool enum_name_cb_add(enum_name_t *e, enum_name_cb_t cb, + void *user) +{ + for (int i = 0; i < ENUM_NAME_CB_MAX; i++) + { + if (!e->cb[i].cb) + { + e->cb[i].cb = cb; + e->cb[i].user = user; + return TRUE; + } + } + return FALSE; +} + +/** + * Remove a callback that served as fallback if a value couldn't be found in + * the given enum name list. + * + * @note This should only be called in single-threaded mode, i.e. when plugins + * and plugin features are unloaded. + * + * @param e enum names + * @param cb callback to remove + */ +static inline void enum_name_cb_remove(enum_name_t *e, enum_name_cb_t cb) +{ + for (int i = 0; i < ENUM_NAME_CB_MAX; i++) + { + if (e->cb[i].cb == cb) + { + memmove(&e->cb[i], &e->cb[i + 1], + sizeof(e->cb[0]) * (ENUM_NAME_CB_MAX - 1 - i)); + e->cb[ENUM_NAME_CB_MAX - 1].cb = NULL; + e->cb[ENUM_NAME_CB_MAX - 1].user = NULL; + --i; + } + } +} + /** * Convert a enum value to its string representation. * -- 2.47.2