api: fromjson/tojson enum flag support
Represent enum flags as JSON arrays (as these can have multiple values).
Add unit tests.
Type: improvement
Change-Id: I680c5b6f76ef6f05f360e2f3b9c4cbb927e15d7d
Signed-off-by: Ole Troan <ot@cisco.com>
diff --git a/src/tools/vppapigen/vppapigen_c.py b/src/tools/vppapigen/vppapigen_c.py
index 44f86be..66e0c2f 100644
--- a/src/tools/vppapigen/vppapigen_c.py
+++ b/src/tools/vppapigen/vppapigen_c.py
@@ -187,7 +187,21 @@
write('}\n')
_dispatch['Enum'] = print_enum
- _dispatch['EnumFlag'] = print_enum
+
+ def print_enum_flag(self, o):
+ '''Create cJSON object (string) for VPP API enum'''
+ write = self.stream.write
+ write('static inline cJSON *vl_api_{name}_t_tojson '
+ '(vl_api_{name}_t a) {{\n'.format(name=o.name))
+ write(' cJSON *array = cJSON_CreateArray();\n')
+
+ for b in o.block:
+ write(' if (a & {})\n'.format(b[0]))
+ write(' cJSON_AddItemToArray(array, cJSON_CreateString("{}"));\n'.format(b[0]))
+ write(' return array;\n')
+ write('}\n')
+
+ _dispatch['EnumFlag'] = print_enum_flag
def print_typedef(self, o):
'''Create cJSON (dictionary) object from VPP API typedef'''
@@ -456,7 +470,27 @@
write('}\n')
_dispatch['Enum'] = print_enum
- _dispatch['EnumFlag'] = print_enum
+
+ def print_enum_flag(self, o):
+ '''Convert to JSON enum(string) to VPP API enum (int)'''
+ write = self.stream.write
+ write('static inline void *vl_api_{n}_t_fromjson '
+ '(void *mp, int *len, cJSON *o, vl_api_{n}_t *a) {{\n'
+ .format(n=o.name))
+ write(' int i;\n')
+ write(' *a = 0;\n')
+ write(' for (i = 0; i < cJSON_GetArraySize(o); i++) {\n')
+ write(' cJSON *e = cJSON_GetArrayItem(o, i);\n')
+ write(' char *p = cJSON_GetStringValue(e);\n')
+ write(' if (!p) return 0;\n')
+ for b in o.block:
+ write(' if (strcmp(p, "{}") == 0) *a |= {};\n'
+ .format(b[0], b[1]))
+ write(' }\n')
+ write(' return mp;\n')
+ write('}\n')
+
+ _dispatch['EnumFlag'] = print_enum_flag
def print_typedef(self, o):
'''Convert from JSON object to VPP API binary representation'''
diff --git a/src/vat2/CMakeLists.txt b/src/vat2/CMakeLists.txt
index 690267c..73538b4 100644
--- a/src/vat2/CMakeLists.txt
+++ b/src/vat2/CMakeLists.txt
@@ -31,6 +31,29 @@
rt m dl crypto
)
+#
+# Unit test code. Call generator directly to avoid it being installed
+#set_source_files_properties(vat2_test.c PROPERTIES
+# COMPILE_FLAGS " -fsanitize=address"
+#)
+
+vpp_generate_api_c_header (test/vat2_test.api)
+add_vpp_executable(test_vat2 ENABLE_EXPORTS NO_INSTALL
+ SOURCES
+ test/vat2_test.c
+ jsonconvert.c
+
+ DEPENDS api_headers
+
+ LINK_LIBRARIES
+ vlibmemoryclient
+ svm
+ vppinfra
+ vppapiclient
+ Threads::Threads
+ rt m dl crypto
+)
+#target_link_options(test_vat2 PUBLIC "LINKER:-fsanitize=address")
##############################################################################
# vat2 headers
##############################################################################
diff --git a/src/vat2/jsonconvert.c b/src/vat2/jsonconvert.c
index 3aeaeed..ec06628 100644
--- a/src/vat2/jsonconvert.c
+++ b/src/vat2/jsonconvert.c
@@ -314,12 +314,12 @@
char *p = cJSON_GetStringValue(o);
if (!p) return 0;
unformat_init_string (&input, p, strlen(p));
- if (a->af == ADDRESS_IP4)
- unformat(&input, "%U", unformat_ip4_address, &a->un.ip4);
- else if (a->af == ADDRESS_IP6)
- unformat(&input, "%U", unformat_ip6_address, &a->un.ip6);
+ if (unformat (&input, "%U", unformat_ip4_address, &a->un.ip4))
+ a->af = ADDRESS_IP4;
+ else if (unformat (&input, "%U", unformat_ip6_address, &a->un.ip6))
+ a->af = ADDRESS_IP6;
else
- return 0;
+ return (0);
return mp;
}
@@ -328,14 +328,17 @@
unformat_input_t input;
char *p = cJSON_GetStringValue(o);
+
if (!p) return 0;
unformat_init_string (&input, p, strlen(p));
- if (a->address.af == ADDRESS_IP4)
- unformat(&input, "%U/%d", unformat_ip4_address, &a->address.un.ip4, &a->len);
- else if (a->address.af == ADDRESS_IP6)
- unformat(&input, "%U/%d", unformat_ip6_address, &a->address.un.ip6, &a->len);
+ int plen;
+ if (unformat (&input, "%U/%d", unformat_ip4_address, &a->address.un.ip4, &plen))
+ a->address.af = ADDRESS_IP4;
+ else if (unformat (&input, "%U/%d", unformat_ip6_address, &a->address.un.ip6, &plen))
+ a->address.af = ADDRESS_IP6;
else
- return 0;
+ return (0);
+ a->len = plen;
return mp;
}
diff --git a/src/vat2/test/vat2_test.api b/src/vat2/test/vat2_test.api
new file mode 100644
index 0000000..6a2c94d
--- /dev/null
+++ b/src/vat2/test/vat2_test.api
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2020 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import "vnet/ip/ip_types.api";
+
+autoreply define test_prefix {
+ vl_api_prefix_t pref;
+};
+
+enumflag test_enumflags {
+ RED = 0x1,
+ BLUE = 0x2,
+ GREEN = 0x4,
+};
+
+autoreply define test_enum {
+ vl_api_test_enumflags_t flags;
+};
diff --git a/src/vat2/test/vat2_test.c b/src/vat2/test/vat2_test.c
new file mode 100644
index 0000000..fe788f1
--- /dev/null
+++ b/src/vat2/test/vat2_test.c
@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) 2020 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdio.h>
+#include <assert.h>
+#include <vlibapi/api.h>
+#include "vat2/test/vat2_test.api_types.h"
+#include "vat2/test/vat2_test.api_tojson.h"
+#include "vat2/test/vat2_test.api_fromjson.h"
+
+typedef cJSON *(* tojson_fn_t)(void *);
+typedef void *(* fromjson_fn_t)(cJSON *o, int *len);
+
+static void
+test (tojson_fn_t tojson, fromjson_fn_t fromjson, cJSON *o, bool should_fail)
+{
+ // convert JSON object to API
+ int len = 0;
+ void *mp = (fromjson)(o, &len);
+ assert(mp);
+
+ // convert API to JSON
+ cJSON *o2 = (tojson)(mp);
+ assert(o2);
+
+ if (should_fail)
+ assert(!cJSON_Compare(o, o2, 1));
+ else
+ assert(cJSON_Compare(o, o2, 1));
+ char *s2 = cJSON_Print(o2);
+ assert(s2);
+
+ char *in = cJSON_Print(o);
+ printf("%s\n%s\n", in, s2);
+
+ free(in);
+ free(mp);
+ cJSON_Delete(o2);
+ free(s2);
+}
+
+struct msgs {
+ char *name;
+ tojson_fn_t tojson;
+ fromjson_fn_t fromjson;
+};
+struct tests {
+ char *s;
+ bool should_fail;
+};
+
+uword *function_by_name_tojson;
+uword *function_by_name_fromjson;
+static void
+register_functions(struct msgs msgs[], int n)
+{
+ int i;
+ function_by_name_tojson = hash_create_string (0, sizeof (uword));
+ function_by_name_fromjson = hash_create_string (0, sizeof (uword));
+ for (i = 0; i < n; i++) {
+ hash_set_mem(function_by_name_tojson, msgs[i].name, msgs[i].tojson);
+ hash_set_mem(function_by_name_fromjson, msgs[i].name, msgs[i].fromjson);
+ }
+}
+
+static void
+runtest (char *s, bool should_fail)
+{
+ cJSON *o = cJSON_Parse(s);
+ assert(o);
+ char *name = cJSON_GetStringValue(cJSON_GetObjectItem(o, "_msgname"));
+ assert(name);
+
+ uword *p = hash_get_mem(function_by_name_tojson, name);
+ assert(p);
+ tojson_fn_t tojson = (tojson_fn_t)p[0];
+
+ p = hash_get_mem(function_by_name_fromjson, name);
+ assert(p);
+ fromjson_fn_t fromjson = (fromjson_fn_t)p[0];
+
+ test(tojson, fromjson, o, should_fail);
+ cJSON_Delete(o);
+}
+
+struct msgs msgs[] = {
+{
+ .name = "test_prefix",
+ .tojson = (tojson_fn_t)vl_api_test_prefix_t_tojson,
+ .fromjson = (fromjson_fn_t)vl_api_test_prefix_t_fromjson,
+},
+{
+ .name = "test_enum",
+ .tojson = (tojson_fn_t)vl_api_test_enum_t_tojson,
+ .fromjson = (fromjson_fn_t)vl_api_test_enum_t_fromjson,
+},
+};
+
+struct tests tests[] = {
+ {.s = "{\"_msgname\": \"test_prefix\", \"pref\": \"2001:db8::/64\"}"},
+ {.s = "{\"_msgname\": \"test_prefix\", \"pref\": \"192.168.10.0/24\"}"},
+ {.s = "{\"_msgname\": \"test_enum\", \"flags\": [\"RED\", \"BLUE\"]}"},
+ {.s = "{\"_msgname\": \"test_enum\", \"flags\": [\"BLACK\", \"BLUE\"]}",
+ .should_fail = 1},
+};
+
+int main (int argc, char **argv)
+{
+ clib_mem_init (0, 64 << 20);
+ int n = sizeof(msgs)/sizeof(msgs[0]);
+ register_functions(msgs, n);
+
+ int i;
+ n = sizeof(tests)/sizeof(tests[0]);
+ for (i = 0; i < n; i++) {
+ runtest(tests[i].s, tests[i].should_fail);
+ }
+}