/*
- * Copyright (C) 2013 Tobias Brunner
+ * Copyright (C) 2013-2023 Tobias Brunner
*
* Copyright (C) secunet Security Networks AG
*
}
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;
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;
}
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
*/
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
{
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;
}
}
*/
#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.
*
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];
};
/**
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.
*