From 793be46324453e5326eb37a13ffb82f92b1f55b1 Mon Sep 17 00:00:00 2001 From: Ole Troan Date: Fri, 4 Dec 2020 13:15:30 +0100 Subject: [PATCH] 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 --- src/tools/vppapigen/vppapigen_c.py | 38 ++++++++++- src/vat2/CMakeLists.txt | 23 +++++++ src/vat2/jsonconvert.c | 23 ++++--- src/vat2/test/vat2_test.api | 30 +++++++++ src/vat2/test/vat2_test.c | 130 +++++++++++++++++++++++++++++++++++++ 5 files changed, 232 insertions(+), 12 deletions(-) create mode 100644 src/vat2/test/vat2_test.api create mode 100644 src/vat2/test/vat2_test.c diff --git a/src/tools/vppapigen/vppapigen_c.py b/src/tools/vppapigen/vppapigen_c.py index 44f86be9222..66e0c2f084c 100644 --- a/src/tools/vppapigen/vppapigen_c.py +++ b/src/tools/vppapigen/vppapigen_c.py @@ -187,7 +187,21 @@ class ToJSON(): 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 @@ class FromJSON(): 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 690267c6113..73538b4a2dd 100644 --- a/src/vat2/CMakeLists.txt +++ b/src/vat2/CMakeLists.txt @@ -31,6 +31,29 @@ add_vpp_executable(vat2 ENABLE_EXPORTS NO_INSTALL 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 3aeaeedb2f7..ec066287035 100644 --- a/src/vat2/jsonconvert.c +++ b/src/vat2/jsonconvert.c @@ -314,12 +314,12 @@ void *vl_api_address_t_fromjson(void *mp, int *len, cJSON *o, vl_api_address_t * 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 @@ void *vl_api_prefix_t_fromjson(void *mp, int *len, cJSON *o, vl_api_prefix_t *a) 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 00000000000..6a2c94d182e --- /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 00000000000..fe788f127c6 --- /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 +#include +#include +#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); + } +} -- 2.16.6