]> git.ipfire.org Git - thirdparty/strongswan.git/commitdiff
enum: Add facility to register callbacks to print enum names enum-cb
authorTobias Brunner <tobias@strongswan.org>
Wed, 8 Feb 2023 12:51:07 +0000 (13:51 +0100)
committerTobias Brunner <tobias@strongswan.org>
Tue, 14 Feb 2023 17:56:48 +0000 (18:56 +0100)
This allows plugins to print names for private values of certain enums.

src/libstrongswan/tests/suites/test_enum.c
src/libstrongswan/utils/enum.c
src/libstrongswan/utils/enum.h

index c2329bdba4940d1054993e73728b8dd998dec63c..da1269dae22fd95c549d8c28acdd89b39e204cd6 100644 (file)
@@ -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;
 }
index 8fc2ab5955dc0599addb298ed45a3bf6ffc3db9f..53594cac4984964d3ae628b0049d63e7db4e0b31 100644 (file)
@@ -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;
                }
        }
index 95987449ca3d48f6a3cce65ea2221d8e612af104..1b1e203ccef67cb99f50c8cfbf33e6788e82de00 100644 (file)
@@ -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.
  *