diff options
Diffstat (limited to 'src/vpp-api')
34 files changed, 5013 insertions, 2238 deletions
diff --git a/src/vpp-api/client/client.c b/src/vpp-api/client/client.c index 902ed3bd625..d59273ed6cb 100644 --- a/src/vpp-api/client/client.c +++ b/src/vpp-api/client/client.c @@ -30,7 +30,8 @@ #include <vlibapi/api.h> #include <vlibmemory/api.h> -#include <vpp/api/vpe_msg_enum.h> +#include <vlibmemory/memclnt.api_enum.h> +#include <vlibmemory/memclnt.api_types.h> #include "vppapiclient.h" @@ -48,14 +49,6 @@ bool rx_thread_done; * vac_read() -> resumes RX thread */ -#define vl_typedefs /* define message structures */ -#include <vpp/api/vpe_all_api_h.h> -#undef vl_typedefs - -#define vl_endianfun /* define message structures */ -#include <vpp/api/vpe_all_api_h.h> -#undef vl_endianfun - typedef struct { u8 connected_to_vlib; pthread_t rx_thread_handle; @@ -108,14 +101,6 @@ cleanup (void) clib_memset(pm, 0, sizeof(*pm)); } -/* - * Satisfy external references when -lvlib is not available. - */ -void vlib_cli_output (struct vlib_main_t * vm, char * fmt, ...) -{ - clib_warning ("vlib_cli_output called..."); -} - void vac_free (void * msg) { @@ -320,6 +305,8 @@ vac_connect (char * name, char * chroot_prefix, vac_callback_t cb, } /* Start read timeout thread */ + timeout_in_progress = false; + timeout_thread_cancelled = false; rv = pthread_create(&pm->timeout_thread_handle, NULL, vac_timeout_thread_fn, 0); if (rv) { @@ -497,10 +484,11 @@ vac_read (char **p, int *l, u16 timeout) /* * XXX: Makes the assumption that client_index is the first member */ -typedef VL_API_PACKED(struct _vl_api_header { +typedef struct _vl_api_header +{ u16 _vl_msg_id; u32 client_index; -}) vl_api_header_t; +} __attribute__ ((packed)) vl_api_header_t; static u32 vac_client_index (void) diff --git a/src/vpp-api/client/stat_client.c b/src/vpp-api/client/stat_client.c index 2c30be62326..359813f8d57 100644 --- a/src/vpp-api/client/stat_client.c +++ b/src/vpp-api/client/stat_client.c @@ -29,7 +29,8 @@ #include <vppinfra/vec.h> #include <vppinfra/lock.h> #include <stdatomic.h> -#include <vpp/stats/stat_segment.h> +#include <vlib/vlib.h> +#include <vlib/stats/stats.h> #include <vpp-api/client/stat_client.h> stat_client_main_t stat_client_main; @@ -81,8 +82,8 @@ recv_fd (int sock) return fd; } -static stat_segment_directory_entry_t * -get_stat_vector_r (stat_client_main_t * sm) +static vlib_stats_entry_t * +get_stat_vector_r (stat_client_main_t *sm) { ASSERT (sm->shared_header); return stat_segment_adjust (sm, @@ -172,7 +173,7 @@ double stat_segment_heartbeat_r (stat_client_main_t * sm) { stat_segment_access_t sa; - stat_segment_directory_entry_t *ep; + vlib_stats_entry_t *ep; /* Has directory been updated? */ if (sm->shared_header->epoch != sm->current_epoch) @@ -223,18 +224,18 @@ stat_vec_combined_init (vlib_counter_t c) * threads), otherwise copy out all values. */ static stat_segment_data_t -copy_data (stat_segment_directory_entry_t *ep, u32 index2, char *name, - stat_client_main_t *sm) +copy_data (vlib_stats_entry_t *ep, u32 index2, char *name, + stat_client_main_t *sm, bool via_symlink) { stat_segment_data_t result = { 0 }; int i; vlib_counter_t **combined_c; /* Combined counter */ counter_t **simple_c; /* Simple counter */ - uint64_t *error_vector; assert (sm->shared_header); result.type = ep->type; + result.via_symlink = via_symlink; result.name = strdup (name ? name : ep->name); switch (ep->type) @@ -270,18 +271,6 @@ copy_data (stat_segment_directory_entry_t *ep, u32 index2, char *name, } break; - case STAT_DIR_TYPE_ERROR_INDEX: - /* Gather errors from all threads into a vector */ - error_vector = - stat_segment_adjust (sm, (void *) sm->shared_header->error_vector); - vec_validate (result.error_vector, vec_len (error_vector) - 1); - for (i = 0; i < vec_len (error_vector); i++) - { - counter_t *cb = stat_segment_adjust (sm, (void *) error_vector[i]); - result.error_vector[i] = cb[ep->index]; - } - break; - case STAT_DIR_TYPE_NAME_VECTOR: { uint8_t **name_vector = stat_segment_adjust (sm, ep->data); @@ -297,9 +286,11 @@ copy_data (stat_segment_directory_entry_t *ep, u32 index2, char *name, case STAT_DIR_TYPE_SYMLINK: /* Gather info from all threads into a vector */ { - stat_segment_directory_entry_t *ep2; + vlib_stats_entry_t *ep2; ep2 = vec_elt_at_index (sm->directory_vector, ep->index1); - return copy_data (ep2, ep->index2, ep->name, sm); + /* We do not intend to return the "result", avoid a leak */ + free (result.name); + return copy_data (ep2, ep->index2, ep->name, sm, true); } case STAT_DIR_TYPE_EMPTY: @@ -334,10 +325,8 @@ stat_segment_data_free (stat_segment_data_t * res) vec_free (res[i].name_vector[j]); vec_free (res[i].name_vector); break; - case STAT_DIR_TYPE_ERROR_INDEX: - vec_free (res[i].error_vector); - break; case STAT_DIR_TYPE_SCALAR_INDEX: + case STAT_DIR_TYPE_EMPTY: break; default: assert (0); @@ -369,7 +358,7 @@ stat_segment_ls_r (uint8_t ** patterns, stat_client_main_t * sm) if (stat_segment_access_start (&sa, sm)) return 0; - stat_segment_directory_entry_t *counter_vec = get_stat_vector_r (sm); + vlib_stats_entry_t *counter_vec = get_stat_vector_r (sm); for (j = 0; j < vec_len (counter_vec); j++) { for (i = 0; i < vec_len (patterns); i++) @@ -412,7 +401,7 @@ stat_segment_data_t * stat_segment_dump_r (uint32_t * stats, stat_client_main_t * sm) { int i; - stat_segment_directory_entry_t *ep; + vlib_stats_entry_t *ep; stat_segment_data_t *res = 0; stat_segment_access_t sa; @@ -423,11 +412,20 @@ stat_segment_dump_r (uint32_t * stats, stat_client_main_t * sm) if (stat_segment_access_start (&sa, sm)) return 0; + /* preallocate the elements. + * This takes care of a special case where + * the vec_len(stats) == 0, + * such that we return a vector of + * length 0, rather than a null pointer + * (since null pointer is an error) + */ + vec_alloc (res, vec_len (stats)); + for (i = 0; i < vec_len (stats); i++) { /* Collect counter */ ep = vec_elt_at_index (sm->directory_vector, stats[i]); - vec_add1 (res, copy_data (ep, ~0, 0, sm)); + vec_add1 (res, copy_data (ep, ~0, 0, sm, false)); } if (stat_segment_access_end (&sa, sm)) @@ -435,6 +433,8 @@ stat_segment_dump_r (uint32_t * stats, stat_client_main_t * sm) fprintf (stderr, "Epoch changed while reading, invalid results\n"); // TODO increase counter + if (res) + stat_segment_data_free (res); return 0; } @@ -473,7 +473,7 @@ stat_segment_string_vector (uint8_t ** string_vector, const char *string) stat_segment_data_t * stat_segment_dump_entry_r (uint32_t index, stat_client_main_t * sm) { - stat_segment_directory_entry_t *ep; + vlib_stats_entry_t *ep; stat_segment_data_t *res = 0; stat_segment_access_t sa; @@ -486,7 +486,7 @@ stat_segment_dump_entry_r (uint32_t index, stat_client_main_t * sm) /* Collect counter */ ep = vec_elt_at_index (sm->directory_vector, index); - vec_add1 (res, copy_data (ep, ~0, 0, sm)); + vec_add1 (res, copy_data (ep, ~0, 0, sm, false)); if (stat_segment_access_end (&sa, sm)) return res; @@ -503,9 +503,9 @@ stat_segment_dump_entry (uint32_t index) char * stat_segment_index_to_name_r (uint32_t index, stat_client_main_t * sm) { - stat_segment_directory_entry_t *ep; + vlib_stats_entry_t *ep; stat_segment_access_t sa; - stat_segment_directory_entry_t *vec; + vlib_stats_entry_t *vec; /* Has directory been update? */ if (sm->shared_header->epoch != sm->current_epoch) @@ -514,6 +514,11 @@ stat_segment_index_to_name_r (uint32_t index, stat_client_main_t * sm) return 0; vec = get_stat_vector_r (sm); ep = vec_elt_at_index (vec, index); + if (ep->type == STAT_DIR_TYPE_EMPTY) + { + stat_segment_access_end (&sa, sm); + return 0; + } if (!stat_segment_access_end (&sa, sm)) return 0; return strdup (ep->name); diff --git a/src/vpp-api/client/stat_client.h b/src/vpp-api/client/stat_client.h index 730badd1728..d9671c69ff2 100644 --- a/src/vpp-api/client/stat_client.h +++ b/src/vpp-api/client/stat_client.h @@ -25,7 +25,7 @@ #include <vlib/counter_types.h> #include <time.h> #include <stdbool.h> -#include <vpp/stats/stat_segment_shared.h> +#include <vlib/stats/shared.h> /* Default socket to exchange segment fd */ /* TODO: Get from runtime directory */ @@ -36,6 +36,7 @@ typedef struct { char *name; stat_directory_type_t type; + bool via_symlink; union { double scalar_value; @@ -49,8 +50,8 @@ typedef struct typedef struct { uint64_t current_epoch; - stat_segment_shared_header_t *shared_header; - stat_segment_directory_entry_t *directory_vector; + vlib_stats_shared_header_t *shared_header; + vlib_stats_entry_t *directory_vector; ssize_t memory_size; uint64_t timeout; } stat_client_main_t; @@ -115,7 +116,7 @@ static inline int stat_segment_access_start (stat_segment_access_t * sa, stat_client_main_t * sm) { - stat_segment_shared_header_t *shared_header = sm->shared_header; + vlib_stats_shared_header_t *shared_header = sm->shared_header; uint64_t max_time; sa->epoch = shared_header->epoch; @@ -130,10 +131,8 @@ stat_segment_access_start (stat_segment_access_t * sa, while (shared_header->in_progress != 0) ; } - sm->directory_vector = - (stat_segment_directory_entry_t *) stat_segment_adjust (sm, - (void *) - sm->shared_header->directory_vector); + sm->directory_vector = (vlib_stats_entry_t *) stat_segment_adjust ( + sm, (void *) sm->shared_header->directory_vector); if (sm->timeout) return _time_now_nsec () < max_time ? 0 : -1; return 0; @@ -164,7 +163,7 @@ stat_segment_set_timeout (uint64_t timeout) static inline bool stat_segment_access_end (stat_segment_access_t * sa, stat_client_main_t * sm) { - stat_segment_shared_header_t *shared_header = sm->shared_header; + vlib_stats_shared_header_t *shared_header = sm->shared_header; if (shared_header->epoch != sa->epoch || shared_header->in_progress) return false; diff --git a/src/vpp-api/client/test.c b/src/vpp-api/client/test.c index 9bfed996e1b..c242e6611a4 100644 --- a/src/vpp-api/client/test.c +++ b/src/vpp-api/client/test.c @@ -32,14 +32,17 @@ #include <vlib/unix/unix.h> #include <vlibapi/api.h> #include <vppinfra/time.h> -#include <vpp/api/vpe_msg_enum.h> #include <signal.h> #include "vppapiclient.h" #include "stat_client.h" -#define vl_typedefs /* define message structures */ -#include <vpp/api/vpe_all_api_h.h> -#undef vl_typedefs +#include <vlibmemory/vlib.api_enum.h> +#include <vlibmemory/vlib.api_types.h> +#include <vlibmemory/memclnt.api_enum.h> +#include <vlibmemory/memclnt.api_types.h> + +#include <vpp/api/vpe.api_enum.h> +#include <vpp/api/vpe.api_types.h> volatile int sigterm_received = 0; volatile u32 result_ready; @@ -67,7 +70,6 @@ wrap_vac_callback (unsigned char *data, int len) static void test_connect () { - static int i; int rv = vac_connect("vac_client", NULL, wrap_vac_callback, 32 /* rx queue-length*/); if (rv != 0) { printf("Connect failed: %d\n", rv); @@ -75,7 +77,6 @@ test_connect () } printf("."); vac_disconnect(); - i++; } static void @@ -139,7 +140,7 @@ test_stats (void) assert(rv == 0); u32 *dir; - int i, j, k; + int i, k; stat_segment_data_t *res; u8 **pattern = 0; vec_add1(pattern, (u8 *)"/if/names"); @@ -158,11 +159,6 @@ test_stats (void) fformat (stdout, "[%d]: %s %s\n", k, res[i].name_vector[k], res[i].name); break; - case STAT_DIR_TYPE_ERROR_INDEX: - for (j = 0; j < vec_len (res[i].error_vector); j++) - fformat (stdout, "%llu %s\n", res[i].error_vector[j], - res[i].name); - break; default: assert(0); } diff --git a/src/vpp-api/python/CMakeLists.txt b/src/vpp-api/python/CMakeLists.txt index 6450fd92f2d..3059619ff21 100644 --- a/src/vpp-api/python/CMakeLists.txt +++ b/src/vpp-api/python/CMakeLists.txt @@ -11,26 +11,21 @@ # See the License for the specific language governing permissions and # limitations under the License. -if (CMAKE_VERSION VERSION_LESS 3.12) - find_package(PythonInterp 2.7) -else() - find_package(Python3 COMPONENTS Interpreter) -endif() +find_package(Python3 REQUIRED COMPONENTS Interpreter) +set(PYTHONINTERP_FOUND ${Python3_Interpreter_FOUND}) +set(PYTHON_EXECUTABLE ${Python3_EXECUTABLE}) -if(PYTHONINTERP_FOUND) - install( - CODE " - execute_process( - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} - COMMAND ${PYTHON_EXECUTABLE} ./setup.py - install - --root / - --prefix=${CMAKE_INSTALL_PREFIX} - --single-version-externally-managed - bdist_egg - --dist-dir=${CMAKE_INSTALL_PREFIX} - OUTPUT_QUIET - )" - COMPONENT vpp-api-python - ) -endif() +install( + CODE " + execute_process( + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + COMMAND ${PYTHON_EXECUTABLE} ./setup.py + install + --root=\$ENV{DESTDIR}/ + --prefix=${CMAKE_INSTALL_PREFIX} + --single-version-externally-managed + bdist_egg + OUTPUT_QUIET + )" + COMPONENT vpp-api-python +) diff --git a/src/vpp-api/python/README.rst b/src/vpp-api/python/README.rst deleted file mode 100644 index e69de29bb2d..00000000000 --- a/src/vpp-api/python/README.rst +++ /dev/null diff --git a/src/vpp-api/python/pyproject.toml b/src/vpp-api/python/pyproject.toml new file mode 100644 index 00000000000..638dd9c54fc --- /dev/null +++ b/src/vpp-api/python/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["setuptools>=61.0"] +build-backend = "setuptools.build_meta" diff --git a/src/vpp-api/python/setup.py b/src/vpp-api/python/setup.py index 8bf6def2227..784013fc606 100644 --- a/src/vpp-api/python/setup.py +++ b/src/vpp-api/python/setup.py @@ -11,7 +11,6 @@ # 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 sys try: from setuptools import setup, find_packages @@ -21,15 +20,17 @@ except ImportError: requirements = [] setup( - name='vpp_papi', - version='2.0.0', - description='VPP Python binding', - author='Ole Troan', - author_email='ot@cisco.com', - url='https://wiki.fd.io/view/VPP/Python_API', - license='Apache-2.0', - test_suite='vpp_papi.tests', + name="vpp_papi", + version="2.1.0", + description="VPP Python binding", + author="Ole Troan", + author_email="ot@cisco.com", + url="https://wiki.fd.io/view/VPP/Python_API", + license="Apache-2.0", + test_suite="vpp_papi.tests", install_requires=requirements, packages=find_packages(), - long_description='''VPP Python language binding.''', - zip_safe=True) + package_data={"vpp_papi": ["data/*.json"]}, + long_description="""VPP Python language binding.""", + zip_safe=True, +) diff --git a/src/vpp-api/python/vpp_papi/__init__.py b/src/vpp-api/python/vpp_papi/__init__.py index b2b4fc78fc1..dc58c1e18cb 100644 --- a/src/vpp-api/python/vpp_papi/__init__.py +++ b/src/vpp-api/python/vpp_papi/__init__.py @@ -3,7 +3,7 @@ from .vpp_papi import VppEnum, VppEnumType, VppEnumFlag # noqa: F401 from .vpp_papi import VPPIOError, VPPRuntimeError, VPPValueError # noqa: F401 from .vpp_papi import VPPApiClient # noqa: F401 from .vpp_papi import VPPApiJSONFiles # noqa: F401 -from . macaddress import MACAddress, mac_pton, mac_ntop # noqa: F401 +from .macaddress import MACAddress, mac_pton, mac_ntop # noqa: F401 # sorted lexicographically from .vpp_serializer import BaseTypes # noqa: F401 @@ -11,7 +11,8 @@ from .vpp_serializer import VPPEnumType, VPPType, VPPTypeAlias # noqa: F401 from .vpp_serializer import VPPMessage, VPPUnionType # noqa: F401 import pkg_resources # part of setuptools + try: __version__ = pkg_resources.get_distribution("vpp_papi").version -except (pkg_resources.DistributionNotFound): +except pkg_resources.DistributionNotFound: """Can't find vpp_papi via setuptools""" diff --git a/src/vpp-api/python/vpp_papi/data/memclnt.api.json b/src/vpp-api/python/vpp_papi/data/memclnt.api.json new file mode 100644 index 00000000000..1734cf12ab0 --- /dev/null +++ b/src/vpp-api/python/vpp_papi/data/memclnt.api.json @@ -0,0 +1,809 @@ +{ + "types": [ + [ + "module_version", + [ + "u32", + "major" + ], + [ + "u32", + "minor" + ], + [ + "u32", + "patch" + ], + [ + "string", + "name", + 64 + ] + ], + [ + "message_table_entry", + [ + "u16", + "index" + ], + [ + "string", + "name", + 64 + ] + ] + ], + "messages": [ + [ + "memclnt_create", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "i32", + "ctx_quota" + ], + [ + "u64", + "input_queue" + ], + [ + "string", + "name", + 64 + ], + [ + "u32", + "api_versions", + 8 + ], + { + "crc": "0x9c5e1c2f", + "options": { + "deprecated": null + }, + "comment": "/*\n * Create a client registration\n */" + } + ], + [ + "memclnt_create_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "i32", + "response" + ], + [ + "u64", + "handle" + ], + [ + "u32", + "index" + ], + [ + "u64", + "message_table" + ], + { + "crc": "0x42ec4560", + "options": { + "deprecated": null + } + } + ], + [ + "memclnt_delete", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "index" + ], + [ + "u64", + "handle" + ], + [ + "bool", + "do_cleanup" + ], + { + "crc": "0x7e1c04e3", + "options": {}, + "comment": "/*\n * Delete a client registration\n */" + } + ], + [ + "memclnt_delete_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "i32", + "response" + ], + [ + "u64", + "handle" + ], + { + "crc": "0x3d3b6312", + "options": {} + } + ], + [ + "rx_thread_exit", + [ + "u16", + "_vl_msg_id" + ], + [ + "u8", + "dummy" + ], + { + "crc": "0xc3a3a452", + "options": {}, + "comment": "/*\n * Client RX thread exit\n */" + } + ], + [ + "memclnt_rx_thread_suspend", + [ + "u16", + "_vl_msg_id" + ], + [ + "u8", + "dummy" + ], + { + "crc": "0xc3a3a452", + "options": {}, + "comment": "/*\n * Client RX thread suspend\n */" + } + ], + [ + "memclnt_read_timeout", + [ + "u16", + "_vl_msg_id" + ], + [ + "u8", + "dummy" + ], + { + "crc": "0xc3a3a452", + "options": {}, + "comment": "/*\n * Client read timeout\n */" + } + ], + [ + "rpc_call", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "u64", + "function" + ], + [ + "u8", + "multicast" + ], + [ + "u8", + "need_barrier_sync" + ], + [ + "u8", + "send_reply" + ], + [ + "u32", + "data_len" + ], + [ + "u8", + "data", + 0, + "data_len" + ], + { + "crc": "0x7e8a2c95", + "options": {}, + "comment": "/*\n * RPC\n */" + } + ], + [ + "rpc_call_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "i32", + "retval" + ], + { + "crc": "0xe8d4e804", + "options": {} + } + ], + [ + "get_first_msg_id", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "string", + "name", + 64 + ], + { + "crc": "0xebf79a66", + "options": {}, + "comment": "/*\n * Lookup message-ID base by name\n */" + } + ], + [ + "get_first_msg_id_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "i32", + "retval" + ], + [ + "u16", + "first_msg_id" + ], + { + "crc": "0x7d337472", + "options": {} + } + ], + [ + "api_versions", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + { + "crc": "0x51077d14", + "options": {}, + "comment": "/*\n * Get API version table (includes built-in and plugins)\n */" + } + ], + [ + "api_versions_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "i32", + "retval" + ], + [ + "u32", + "count" + ], + [ + "vl_api_module_version_t", + "api_versions", + 0, + "count" + ], + { + "crc": "0x5f0d99d6", + "options": {} + } + ], + [ + "trace_plugin_msg_ids", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "string", + "plugin_name", + 128 + ], + [ + "u16", + "first_msg_id" + ], + [ + "u16", + "last_msg_id" + ], + { + "crc": "0xf476d3ce", + "options": {}, + "comment": "/*\n * Trace the plugin message-id allocator\n * so we stand a chance of dealing with different sets of plugins\n * at api trace replay time\n */" + } + ], + [ + "sockclnt_create", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "string", + "name", + 64 + ], + { + "crc": "0x455fb9c4", + "options": {}, + "comment": "/*\n * Create a socket client registration.\n */" + } + ], + [ + "sockclnt_create_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "i32", + "response" + ], + [ + "u32", + "index" + ], + [ + "u16", + "count" + ], + [ + "vl_api_message_table_entry_t", + "message_table", + 0, + "count" + ], + { + "crc": "0x35166268", + "options": {} + } + ], + [ + "sockclnt_delete", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "u32", + "index" + ], + { + "crc": "0x8ac76db6", + "options": {}, + "comment": "/*\n * Delete a client registration\n */" + } + ], + [ + "sockclnt_delete_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "i32", + "response" + ], + { + "crc": "0x8f38b1ee", + "options": {} + } + ], + [ + "sock_init_shm", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + [ + "u32", + "requested_size" + ], + [ + "u8", + "nitems" + ], + [ + "u64", + "configs", + 0, + "nitems" + ], + { + "crc": "0x51646d92", + "options": {}, + "comment": "/*\n * Initialize shm api over socket api\n */" + } + ], + [ + "sock_init_shm_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "i32", + "retval" + ], + { + "crc": "0xe8d4e804", + "options": {} + } + ], + [ + "memclnt_keepalive", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + { + "crc": "0x51077d14", + "options": {}, + "comment": "/*\n * Memory client ping / response\n * Only sent on inactive connections\n */" + } + ], + [ + "memclnt_keepalive_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "i32", + "retval" + ], + { + "crc": "0xe8d4e804", + "options": {} + } + ], + [ + "control_ping", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + { + "crc": "0x51077d14", + "options": {}, + "comment": "/** \\brief Control ping from client to api server request\n @param client_index - opaque cookie to identify the sender\n @param context - sender context, to match reply w/ request\n*/" + } + ], + [ + "control_ping_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "i32", + "retval" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "vpe_pid" + ], + { + "crc": "0xf6b0b8ca", + "options": {}, + "comment": "/** \\brief Control ping from the client to the server response\n @param client_index - opaque cookie to identify the sender\n @param context - sender context, to match reply w/ request\n @param retval - return code for the request\n @param vpe_pid - the pid of the vpe, returned by the server\n*/" + } + ], + [ + "memclnt_create_v2", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "i32", + "ctx_quota" + ], + [ + "u64", + "input_queue" + ], + [ + "string", + "name", + 64 + ], + [ + "u32", + "api_versions", + 8 + ], + [ + "bool", + "keepalive", + { + "default": "true" + } + ], + { + "crc": "0xc4bd4882", + "options": {} + } + ], + [ + "memclnt_create_v2_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "i32", + "response" + ], + [ + "u64", + "handle" + ], + [ + "u32", + "index" + ], + [ + "u64", + "message_table" + ], + { + "crc": "0x42ec4560", + "options": {} + } + ], + [ + "get_api_json", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "client_index" + ], + [ + "u32", + "context" + ], + { + "crc": "0x51077d14", + "options": {} + } + ], + [ + "get_api_json_reply", + [ + "u16", + "_vl_msg_id" + ], + [ + "u32", + "context" + ], + [ + "i32", + "retval" + ], + [ + "string", + "json", + 0 + ], + { + "crc": "0xea715b59", + "options": {} + } + ] + ], + "unions": [], + "enums": [], + "enumflags": [], + "services": { + "memclnt_rx_thread_suspend": { + "reply": "null" + }, + "memclnt_read_timeout": { + "reply": "null" + }, + "rx_thread_exit": { + "reply": "null" + }, + "trace_plugin_msg_ids": { + "reply": "null" + }, + "memclnt_create": { + "reply": "memclnt_create_reply" + }, + "memclnt_delete": { + "reply": "memclnt_delete_reply" + }, + "rpc_call": { + "reply": "rpc_call_reply" + }, + "get_first_msg_id": { + "reply": "get_first_msg_id_reply" + }, + "api_versions": { + "reply": "api_versions_reply" + }, + "sockclnt_create": { + "reply": "sockclnt_create_reply" + }, + "sockclnt_delete": { + "reply": "sockclnt_delete_reply" + }, + "sock_init_shm": { + "reply": "sock_init_shm_reply" + }, + "memclnt_keepalive": { + "reply": "memclnt_keepalive_reply" + }, + "control_ping": { + "reply": "control_ping_reply" + }, + "memclnt_create_v2": { + "reply": "memclnt_create_v2_reply" + }, + "get_api_json": { + "reply": "get_api_json_reply" + } + }, + "options": { + "version": "2.1.0" + }, + "aliases": {}, + "vl_api_version": "0xb197c551", + "imports": [], + "counters": [], + "paths": [] +} diff --git a/src/vpp-api/python/vpp_papi/macaddress.py b/src/vpp-api/python/vpp_papi/macaddress.py index c3b10a3c11e..66349a3c19a 100644 --- a/src/vpp-api/python/vpp_papi/macaddress.py +++ b/src/vpp-api/python/vpp_papi/macaddress.py @@ -18,20 +18,19 @@ import binascii def mac_pton(s): - '''Convert MAC address as text to binary''' - return binascii.unhexlify(s.replace(':', '')) + """Convert MAC address as text to binary""" + return binascii.unhexlify(s.replace(":", "")) def mac_ntop(binary): - '''Convert MAC address as binary to text''' - x = b':'.join(binascii.hexlify(binary)[i:i + 2] - for i in range(0, 12, 2)) - return str(x.decode('ascii')) + """Convert MAC address as binary to text""" + x = b":".join(binascii.hexlify(binary)[i : i + 2] for i in range(0, 12, 2)) + return str(x.decode("ascii")) -class MACAddress(): +class MACAddress: def __init__(self, mac): - '''MAC Address as a text-string (aa:bb:cc:dd:ee:ff) or 6 bytes''' + """MAC Address as a text-string (aa:bb:cc:dd:ee:ff) or 6 bytes""" # Of course Python 2 doesn't distinguish str from bytes if type(mac) is bytes and len(mac) == 6: self.mac_binary = mac @@ -51,10 +50,9 @@ class MACAddress(): return self.mac_string def __repr__(self): - return '%s(%s)' % (self.__class__.__name__, self.mac_string) + return "%s(%s)" % (self.__class__.__name__, self.mac_string) def __eq__(self, other): - if not isinstance(other, MACAddress): try: # if it looks like a mac address, we'll take it. diff --git a/src/vpp-api/python/vpp_papi/tests/test_macaddress.py b/src/vpp-api/python/vpp_papi/tests/test_macaddress.py index 08e365afd92..e86ec75c76e 100644 --- a/src/vpp-api/python/vpp_papi/tests/test_macaddress.py +++ b/src/vpp-api/python/vpp_papi/tests/test_macaddress.py @@ -3,8 +3,6 @@ from vpp_papi import MACAddress class TestMacAddress(unittest.TestCase): - def test_eq(self): - mac = '11:22:33:44:55:66' - self.assertEqual(MACAddress(mac), - MACAddress(mac)) + mac = "11:22:33:44:55:66" + self.assertEqual(MACAddress(mac), MACAddress(mac)) diff --git a/src/vpp-api/python/vpp_papi/tests/test_vpp_format.py b/src/vpp-api/python/vpp_papi/tests/test_vpp_format.py index 5c179c02e0a..ae4d2c5126d 100644 --- a/src/vpp-api/python/vpp_papi/tests/test_vpp_format.py +++ b/src/vpp-api/python/vpp_papi/tests/test_vpp_format.py @@ -25,57 +25,64 @@ from vpp_papi import vpp_format from parameterized import parameterized -ip4_addr = '1.2.3.4' -ip4_addrn = b'\x01\x02\x03\x04' +ip4_addr = "1.2.3.4" +ip4_addrn = b"\x01\x02\x03\x04" ip4_prefix_len = 32 -ip4_prefix = '%s/%s' % (ip4_addr, ip4_prefix_len) +ip4_prefix = "%s/%s" % (ip4_addr, ip4_prefix_len) ipv4_network = ipaddress.IPv4Network(text_type(ip4_prefix)) -ip4_addr_format_vl_api_address_t = {'un': {'ip4': b'\x01\x02\x03\x04'}, - 'af': 0} -ip4_addr_format_vl_api_prefix_t = {'address': # noqa: E127,E501 - {'un': {'ip4': b'\x01\x02\x03\x04'}, - 'af': 0}, - 'len': ip4_prefix_len} -ip4_addr_format_vl_api_prefix_packed_t = {'address': b'\x01\x02\x03\x04', - 'len': ip4_prefix_len} - -ip6_addr = 'dead::' -ip6_addrn = b'\xde\xad\x00\x00\x00\x00\x00\x00' \ - b'\x00\x00\x00\x00\x00\x00\x00\x00' +ip4_addr_format_vl_api_address_t = {"un": {"ip4": b"\x01\x02\x03\x04"}, "af": 0} +ip4_addr_format_vl_api_prefix_t = { + "address": {"un": {"ip4": b"\x01\x02\x03\x04"}, "af": 0}, # noqa: E127,E501 + "len": ip4_prefix_len, +} +ip4_addr_format_vl_api_prefix_packed_t = { + "address": b"\x01\x02\x03\x04", + "len": ip4_prefix_len, +} + +ip6_addr = "dead::" +ip6_addrn = b"\xde\xad\x00\x00\x00\x00\x00\x00" b"\x00\x00\x00\x00\x00\x00\x00\x00" ip6_prefix_len = 127 -ip6_prefix = '%s/%s' % (ip6_addr, ip6_prefix_len) +ip6_prefix = "%s/%s" % (ip6_addr, ip6_prefix_len) ipv6_network = ipaddress.IPv6Network(text_type(ip6_prefix)) -ip6_addr_format_vl_api_address_t = {'un': {'ip6': b'\xde\xad\x00\x00' - b'\x00\x00\x00\x00' - b'\x00\x00\x00\x00' - b'\x00\x00\x00\x00'}, - 'af': 1} -ip6_addr_format_vl_api_prefix_t = {'address': # noqa: E127 - {'af': 1, - 'un': { - 'ip6': b'\xde\xad\x00\x00' - b'\x00\x00\x00\x00' - b'\x00\x00\x00\x00' - b'\x00\x00\x00\x00'}}, - 'len': ip6_prefix_len} -ip6_addr_format_vl_api_prefix_packed_t = {'address': b'\xde\xad\x00\x00' # noqa: E127,E501 - b'\x00\x00\x00\x00' - b'\x00\x00\x00\x00' - b'\x00\x00\x00\x00', - 'len': ip6_prefix_len} +ip6_addr_format_vl_api_address_t = { + "un": { + "ip6": b"\xde\xad\x00\x00" + b"\x00\x00\x00\x00" + b"\x00\x00\x00\x00" + b"\x00\x00\x00\x00" + }, + "af": 1, +} +ip6_addr_format_vl_api_prefix_t = { + "address": { # noqa: E127 + "af": 1, + "un": { + "ip6": b"\xde\xad\x00\x00" + b"\x00\x00\x00\x00" + b"\x00\x00\x00\x00" + b"\x00\x00\x00\x00" + }, + }, + "len": ip6_prefix_len, +} +ip6_addr_format_vl_api_prefix_packed_t = { + "address": b"\xde\xad\x00\x00" # noqa: E127,E501 + b"\x00\x00\x00\x00" + b"\x00\x00\x00\x00" + b"\x00\x00\x00\x00", + "len": ip6_prefix_len, +} class TestVppFormat(unittest.TestCase): - def test_format_vl_api_address_t(self): res = vpp_format.format_vl_api_address_t(ip4_addr) self.assertEqual(res, ip4_addr_format_vl_api_address_t) # PY2: raises socket.error # PY3: raises OSError - with self.assertRaises((TypeError, - socket.error, - OSError)): + with self.assertRaises((TypeError, socket.error, OSError)): res = vpp_format.format_vl_api_address_t(ip4_addrn) res = vpp_format.format_vl_api_address_t(ip6_addr) @@ -84,19 +91,14 @@ class TestVppFormat(unittest.TestCase): with self.assertRaises(TypeError): es = vpp_format.format_vl_api_address_t(ip6_addrn) - @parameterized.expand([('ip4 prefix', - ip4_prefix, - ip4_addr_format_vl_api_prefix_t), - ('ip6 prefix', - ip6_prefix, - ip6_addr_format_vl_api_prefix_t), - ('IPv4Network', - ipv4_network, - ip4_addr_format_vl_api_prefix_t), - ('IPv6Network', - ipv6_network, - ip6_addr_format_vl_api_prefix_t), - ]) + @parameterized.expand( + [ + ("ip4 prefix", ip4_prefix, ip4_addr_format_vl_api_prefix_t), + ("ip6 prefix", ip6_prefix, ip6_addr_format_vl_api_prefix_t), + ("IPv4Network", ipv4_network, ip4_addr_format_vl_api_prefix_t), + ("IPv6Network", ipv6_network, ip6_addr_format_vl_api_prefix_t), + ] + ) def test_format_vl_api_prefix_t(self, _, arg, expected): res = vpp_format.format_vl_api_prefix_t(arg) self.assertEqual(res, expected) diff --git a/src/vpp-api/python/vpp_papi/tests/test_vpp_papi.py b/src/vpp-api/python/vpp_papi/tests/test_vpp_papi.py index 99acb7c7469..51c024aa3ab 100644 --- a/src/vpp-api/python/vpp_papi/tests/test_vpp_papi.py +++ b/src/vpp-api/python/vpp_papi/tests/test_vpp_papi.py @@ -24,8 +24,7 @@ from vpp_papi import vpp_transport_shmem class TestVppPapiVPPApiClient(unittest.TestCase): def test_getcontext(self): - vpp_papi.VPPApiClient.apidir = '.' - c = vpp_papi.VPPApiClient(testmode=True, use_socket=True) + c = vpp_papi.VPPApiClient(apidir=".", testmode=True, use_socket=True) # reset initialization at module load time. c.get_context.context = mp.Value(ctypes.c_uint, 0) @@ -39,8 +38,7 @@ class TestVppPapiVPPApiClientMp(unittest.TestCase): # run_tests.py (eg. make test TEST_JOBS=10) def test_get_context_mp(self): - vpp_papi.VPPApiClient.apidir = '.' - c = vpp_papi.VPPApiClient(testmode=True, use_socket=True) + c = vpp_papi.VPPApiClient(apidir=".", testmode=True, use_socket=True) # reset initialization at module load time. c.get_context.context = mp.Value(ctypes.c_uint, 0) @@ -243,11 +241,11 @@ class TestVppPapiLogging(unittest.TestCase): pass client = Vpp - with self.assertLogs('vpp_papi', level='DEBUG') as cm: + with self.assertLogs("vpp_papi", level="DEBUG") as cm: vpp_papi.vpp_atexit(client) - self.assertEqual(cm.output, ['DEBUG:vpp_papi:Cleaning up VPP on exit']) + self.assertEqual(cm.output, ["DEBUG:vpp_papi:Cleaning up VPP on exit"]) with self.assertRaises(AssertionError): - with self.assertLogs('vpp_papi.serializer', level='DEBUG') as cm: + with self.assertLogs("vpp_papi.serializer", level="DEBUG") as cm: vpp_papi.vpp_atexit(client) self.assertEqual(cm.output, []) diff --git a/src/vpp-api/python/vpp_papi/tests/test_vpp_serializer.py b/src/vpp-api/python/vpp_papi/tests/test_vpp_serializer.py index c9b3d672d6a..f0d2846214a 100755 --- a/src/vpp-api/python/vpp_papi/tests/test_vpp_serializer.py +++ b/src/vpp-api/python/vpp_papi/tests/test_vpp_serializer.py @@ -13,61 +13,57 @@ from ipaddress import * class TestLimits(unittest.TestCase): def test_string(self): - fixed_string = VPPType('fixed_string', - [['string', 'name', 16]]) + fixed_string = VPPType("fixed_string", [["string", "name", 16]]) - b = fixed_string.pack({'name': 'foobar'}) + b = fixed_string.pack({"name": "foobar"}) self.assertEqual(len(b), 16) # Ensure string is nul terminated - self.assertEqual(b.decode('ascii')[6], '\x00') + self.assertEqual(b.decode("ascii")[6], "\x00") nt, size = fixed_string.unpack(b) self.assertEqual(size, 16) - self.assertEqual(nt.name, 'foobar') + self.assertEqual(nt.name, "foobar") # Empty string - b = fixed_string.pack({'name': ''}) + b = fixed_string.pack({"name": ""}) self.assertEqual(len(b), 16) nt, size = fixed_string.unpack(b) self.assertEqual(size, 16) - self.assertEqual(nt.name, '') + self.assertEqual(nt.name, "") # String too long with self.assertRaises(VPPSerializerValueError): - b = fixed_string.pack({'name': 'foobarfoobar1234'}) + b = fixed_string.pack({"name": "foobarfoobar1234"}) - variable_string = VPPType('variable_string', - [['string', 'name', 0]]) - b = variable_string.pack({'name': 'foobar'}) - self.assertEqual(len(b), 4 + len('foobar')) + variable_string = VPPType("variable_string", [["string", "name", 0]]) + b = variable_string.pack({"name": "foobar"}) + self.assertEqual(len(b), 4 + len("foobar")) nt, size = variable_string.unpack(b) - self.assertEqual(size, 4 + len('foobar')) - self.assertEqual(nt.name, 'foobar') - self.assertEqual(len(nt.name), len('foobar')) + self.assertEqual(size, 4 + len("foobar")) + self.assertEqual(nt.name, "foobar") + self.assertEqual(len(nt.name), len("foobar")) def test_limit(self): - limited_type = VPPType('limited_type_t', - [['string', 'name', 0, {'limit': 16}]]) - unlimited_type = VPPType('limited_type_t', - [['string', 'name', 0]]) + limited_type = VPPType("limited_type_t", [["string", "name", 0, {"limit": 16}]]) + unlimited_type = VPPType("limited_type_t", [["string", "name", 0]]) - b = limited_type.pack({'name': 'foobar'}) + b = limited_type.pack({"name": "foobar"}) self.assertEqual(len(b), 10) - b = unlimited_type.pack({'name': 'foobar'}) + b = unlimited_type.pack({"name": "foobar"}) self.assertEqual(len(b), 10) with self.assertRaises(VPPSerializerValueError): - b = limited_type.pack({'name': 'foobar'*3}) + b = limited_type.pack({"name": "foobar" * 3}) class TestDefaults(unittest.TestCase): def test_defaults(self): - default_type = VPPType('default_type_t', - [['u16', 'mtu', {'default': 1500, 'limit': 0}]]) - without_default_type = VPPType('without_default_type_t', - [['u16', 'mtu']]) + default_type = VPPType( + "default_type_t", [["u16", "mtu", {"default": 1500, "limit": 0}]] + ) + without_default_type = VPPType("without_default_type_t", [["u16", "mtu"]]) b = default_type.pack({}) self.assertEqual(len(b), 2) @@ -76,7 +72,7 @@ class TestDefaults(unittest.TestCase): self.assertEqual(nt.mtu, 1500) # distinguish between parameter 0 and parameter not passed - b = default_type.pack({'mtu': 0}) + b = default_type.pack({"mtu": 0}) self.assertEqual(len(b), 2) nt, size = default_type.unpack(b) self.assertEqual(len(b), size) @@ -90,13 +86,15 @@ class TestDefaults(unittest.TestCase): self.assertEqual(nt.mtu, 0) # default enum type - VPPEnumType('vl_api_enum_t', [["ADDRESS_IP4", 0], - ["ADDRESS_IP6", 1], - {"enumtype": "u32"}]) + VPPEnumType( + "vl_api_enum_t", + [["ADDRESS_IP4", 0], ["ADDRESS_IP6", 1], {"enumtype": "u32"}], + ) - default_with_enum = VPPType('default_enum_type_t', - [['u16', 'mtu'], ['vl_api_enum_t', - 'e', {'default': 1}]]) + default_with_enum = VPPType( + "default_enum_type_t", + [["u16", "mtu"], ["vl_api_enum_t", "e", {"default": 1}]], + ) b = default_with_enum.pack({}) self.assertEqual(len(b), 6) @@ -106,275 +104,275 @@ class TestDefaults(unittest.TestCase): class TestAddType(unittest.TestCase): - def test_union(self): - un = VPPUnionType('test_union', - [['u8', 'is_bool'], - ['u32', 'is_int']]) + un = VPPUnionType("test_union", [["u8", "is_bool"], ["u32", "is_int"]]) - b = un.pack({'is_int': 0x12345678}) + b = un.pack({"is_int": 0x12345678}) nt, size = un.unpack(b) self.assertEqual(len(b), size) self.assertEqual(nt.is_bool, 0x12) self.assertEqual(nt.is_int, 0x12345678) def test_address(self): - af = VPPEnumType('vl_api_address_family_t', [["ADDRESS_IP4", 0], - ["ADDRESS_IP6", 1], - {"enumtype": "u32"}]) - aff = VPPEnumFlagType('vl_api_address_family_flag_t', [["ADDRESS_IP4", 0], - ["ADDRESS_IP6", 1], - {"enumtype": "u32"}]) - ip4 = VPPTypeAlias('vl_api_ip4_address_t', {'type': 'u8', - 'length': 4}) - ip6 = VPPTypeAlias('vl_api_ip6_address_t', {'type': 'u8', - 'length': 16}) - VPPUnionType('vl_api_address_union_t', - [["vl_api_ip4_address_t", "ip4"], - ["vl_api_ip6_address_t", "ip6"]]) - - address = VPPType('vl_api_address_t', - [['vl_api_address_family_t', 'af'], - ['vl_api_address_union_t', 'un']]) - - prefix = VPPType('vl_api_prefix_t', - [['vl_api_address_t', 'address'], - ['u8', 'len']]) - - va_address_list = VPPType('list_addresses', - [['u8', 'count'], - ['vl_api_address_t', 'addresses', - 0, 'count']]) - - message_with_va_address_list = VPPType('msg_with_vla', - [['list_addresses', - 'vla_address'], - ['u8', 'is_cool']]) - - b = ip4.pack(inet_pton(AF_INET, '1.1.1.1')) + af = VPPEnumType( + "vl_api_address_family_t", + [["ADDRESS_IP4", 0], ["ADDRESS_IP6", 1], {"enumtype": "u32"}], + ) + aff = VPPEnumFlagType( + "vl_api_address_family_flag_t", + [["ADDRESS_IP4", 0], ["ADDRESS_IP6", 1], {"enumtype": "u32"}], + ) + ip4 = VPPTypeAlias("vl_api_ip4_address_t", {"type": "u8", "length": 4}) + ip6 = VPPTypeAlias("vl_api_ip6_address_t", {"type": "u8", "length": 16}) + VPPUnionType( + "vl_api_address_union_t", + [["vl_api_ip4_address_t", "ip4"], ["vl_api_ip6_address_t", "ip6"]], + ) + + address = VPPType( + "vl_api_address_t", + [["vl_api_address_family_t", "af"], ["vl_api_address_union_t", "un"]], + ) + + prefix = VPPType( + "vl_api_prefix_t", [["vl_api_address_t", "address"], ["u8", "len"]] + ) + + va_address_list = VPPType( + "list_addresses", + [["u8", "count"], ["vl_api_address_t", "addresses", 0, "count"]], + ) + + message_with_va_address_list = VPPType( + "msg_with_vla", [["list_addresses", "vla_address"], ["u8", "is_cool"]] + ) + + b = ip4.pack(inet_pton(AF_INET, "1.1.1.1")) self.assertEqual(len(b), 4) nt, size = ip4.unpack(b) - self.assertEqual(str(nt), '1.1.1.1') + self.assertEqual(str(nt), "1.1.1.1") - b = ip6.pack(inet_pton(AF_INET6, '1::1')) + b = ip6.pack(inet_pton(AF_INET6, "1::1")) self.assertEqual(len(b), 16) - b = address.pack({'af': af.ADDRESS_IP4, - 'un': - {'ip4': inet_pton(AF_INET, '2.2.2.2')}}) + b = address.pack( + {"af": af.ADDRESS_IP4, "un": {"ip4": inet_pton(AF_INET, "2.2.2.2")}} + ) self.assertEqual(len(b), 20) nt, size = address.unpack(b) - self.assertEqual(str(nt), '2.2.2.2') + self.assertEqual(str(nt), "2.2.2.2") # List of addresses address_list = [] for i in range(4): - address_list.append({'af': af.ADDRESS_IP4, - 'un': - {'ip4': inet_pton(AF_INET, '2.2.2.2')}}) - b = va_address_list.pack({'count': len(address_list), - 'addresses': address_list}) + address_list.append( + {"af": af.ADDRESS_IP4, "un": {"ip4": inet_pton(AF_INET, "2.2.2.2")}} + ) + b = va_address_list.pack( + {"count": len(address_list), "addresses": address_list} + ) self.assertEqual(len(b), 81) nt, size = va_address_list.unpack(b) - self.assertEqual(str(nt.addresses[0]), '2.2.2.2') - - b = message_with_va_address_list.pack({'vla_address': - {'count': len(address_list), - 'addresses': address_list}, - 'is_cool': 100}) + self.assertEqual(str(nt.addresses[0]), "2.2.2.2") + + b = message_with_va_address_list.pack( + { + "vla_address": {"count": len(address_list), "addresses": address_list}, + "is_cool": 100, + } + ) self.assertEqual(len(b), 82) nt, size = message_with_va_address_list.unpack(b) self.assertEqual(nt.is_cool, 100) def test_address_with_prefix(self): - af = VPPEnumType('vl_api_address_family_t', [["ADDRESS_IP4", 0], - ["ADDRESS_IP6", 1], - {"enumtype": "u32"}]) - ip4 = VPPTypeAlias('vl_api_ip4_address_t', {'type': 'u8', - 'length': 4}) - ip6 = VPPTypeAlias('vl_api_ip6_address_t', {'type': 'u8', - 'length': 16}) - VPPUnionType('vl_api_address_union_t', - [["vl_api_ip4_address_t", "ip4"], - ["vl_api_ip6_address_t", "ip6"]]) - - address = VPPType('vl_api_address_t', - [['vl_api_address_family_t', 'af'], - ['vl_api_address_union_t', 'un']]) - - prefix = VPPType('vl_api_prefix_t', - [['vl_api_address_t', 'address'], - ['u8', 'len']]) - prefix4 = VPPType('vl_api_ip4_prefix_t', - [['vl_api_ip4_address_t', 'address'], - ['u8', 'len']]) - prefix6 = VPPType('vl_api_ip6_prefix_t', - [['vl_api_ip6_address_t', 'address'], - ['u8', 'len']]) - - address_with_prefix = VPPTypeAlias('vl_api_address_with_prefix_t', {'type': 'vl_api_prefix_t' }) - address4_with_prefix = VPPTypeAlias('vl_api_ip4_address_with_prefix_t', - {'type': 'vl_api_ip4_prefix_t' }) - address6_with_prefix = VPPTypeAlias('vl_api_ip6_address_with_prefix_t', - {'type': 'vl_api_ip6_prefix_t' }) - - awp_type = VPPType('foobar_t', - [['vl_api_address_with_prefix_t', 'address']]) + af = VPPEnumType( + "vl_api_address_family_t", + [["ADDRESS_IP4", 0], ["ADDRESS_IP6", 1], {"enumtype": "u32"}], + ) + ip4 = VPPTypeAlias("vl_api_ip4_address_t", {"type": "u8", "length": 4}) + ip6 = VPPTypeAlias("vl_api_ip6_address_t", {"type": "u8", "length": 16}) + VPPUnionType( + "vl_api_address_union_t", + [["vl_api_ip4_address_t", "ip4"], ["vl_api_ip6_address_t", "ip6"]], + ) + + address = VPPType( + "vl_api_address_t", + [["vl_api_address_family_t", "af"], ["vl_api_address_union_t", "un"]], + ) + + prefix = VPPType( + "vl_api_prefix_t", [["vl_api_address_t", "address"], ["u8", "len"]] + ) + prefix4 = VPPType( + "vl_api_ip4_prefix_t", [["vl_api_ip4_address_t", "address"], ["u8", "len"]] + ) + prefix6 = VPPType( + "vl_api_ip6_prefix_t", [["vl_api_ip6_address_t", "address"], ["u8", "len"]] + ) + + address_with_prefix = VPPTypeAlias( + "vl_api_address_with_prefix_t", {"type": "vl_api_prefix_t"} + ) + address4_with_prefix = VPPTypeAlias( + "vl_api_ip4_address_with_prefix_t", {"type": "vl_api_ip4_prefix_t"} + ) + address6_with_prefix = VPPTypeAlias( + "vl_api_ip6_address_with_prefix_t", {"type": "vl_api_ip6_prefix_t"} + ) + + awp_type = VPPType("foobar_t", [["vl_api_address_with_prefix_t", "address"]]) # address with prefix - b = address_with_prefix.pack(IPv4Interface('2.2.2.2/24')) + b = address_with_prefix.pack(IPv4Interface("2.2.2.2/24")) self.assertEqual(len(b), 21) nt, size = address_with_prefix.unpack(b) self.assertTrue(isinstance(nt, IPv4Interface)) - self.assertEqual(str(nt), '2.2.2.2/24') + self.assertEqual(str(nt), "2.2.2.2/24") - b = address_with_prefix.pack(IPv6Interface('2::2/64')) + b = address_with_prefix.pack(IPv6Interface("2::2/64")) self.assertEqual(len(b), 21) nt, size = address_with_prefix.unpack(b) self.assertTrue(isinstance(nt, IPv6Interface)) - self.assertEqual(str(nt), '2::2/64') + self.assertEqual(str(nt), "2::2/64") - b = address_with_prefix.pack(IPv4Network('2.2.2.2/24', strict=False)) + b = address_with_prefix.pack(IPv4Network("2.2.2.2/24", strict=False)) self.assertEqual(len(b), 21) nt, size = address_with_prefix.unpack(b) self.assertTrue(isinstance(nt, IPv4Interface)) - self.assertEqual(str(nt), '2.2.2.0/24') + self.assertEqual(str(nt), "2.2.2.0/24") - b = address4_with_prefix.pack('2.2.2.2/24') + b = address4_with_prefix.pack("2.2.2.2/24") self.assertEqual(len(b), 5) nt, size = address4_with_prefix.unpack(b) self.assertTrue(isinstance(nt, IPv4Interface)) - self.assertEqual(str(nt), '2.2.2.2/24') - b = address4_with_prefix.pack(IPv4Interface('2.2.2.2/24')) + self.assertEqual(str(nt), "2.2.2.2/24") + b = address4_with_prefix.pack(IPv4Interface("2.2.2.2/24")) self.assertEqual(len(b), 5) - b = address6_with_prefix.pack('2::2/64') + b = address6_with_prefix.pack("2::2/64") self.assertEqual(len(b), 17) nt, size = address6_with_prefix.unpack(b) self.assertTrue(isinstance(nt, IPv6Interface)) - self.assertEqual(str(nt), '2::2/64') - b = address6_with_prefix.pack(IPv6Interface('2::2/64')) + self.assertEqual(str(nt), "2::2/64") + b = address6_with_prefix.pack(IPv6Interface("2::2/64")) self.assertEqual(len(b), 17) - b = prefix.pack('192.168.10.0/24') + b = prefix.pack("192.168.10.0/24") self.assertEqual(len(b), 21) nt, size = prefix.unpack(b) self.assertTrue(isinstance(nt, IPv4Network)) - self.assertEqual(str(nt), '192.168.10.0/24') + self.assertEqual(str(nt), "192.168.10.0/24") - b = awp_type.pack({'address': '1.2.3.4/24'}) + b = awp_type.pack({"address": "1.2.3.4/24"}) self.assertEqual(len(b), 21) nt, size = awp_type.unpack(b) self.assertTrue(isinstance(nt.address, IPv4Interface)) - self.assertEqual(str(nt.address), '1.2.3.4/24') + self.assertEqual(str(nt.address), "1.2.3.4/24") - b = awp_type.pack({'address': IPv4Interface('1.2.3.4/24')}) + b = awp_type.pack({"address": IPv4Interface("1.2.3.4/24")}) self.assertEqual(len(b), 21) nt, size = awp_type.unpack(b) self.assertTrue(isinstance(nt.address, IPv4Interface)) - self.assertEqual(str(nt.address), '1.2.3.4/24') + self.assertEqual(str(nt.address), "1.2.3.4/24") def test_recursive_address(self): - af = VPPEnumType('vl_api_address_family_t', [["ADDRESS_IP4", 0], - ["ADDRESS_IP6", 1], - {"enumtype": "u32"}]) - ip4 = VPPTypeAlias('vl_api_ip4_address_t', {'type': 'u8', - 'length': 4}) - b = ip4.pack('1.1.1.1') + af = VPPEnumType( + "vl_api_address_family_t", + [["ADDRESS_IP4", 0], ["ADDRESS_IP6", 1], {"enumtype": "u32"}], + ) + ip4 = VPPTypeAlias("vl_api_ip4_address_t", {"type": "u8", "length": 4}) + b = ip4.pack("1.1.1.1") self.assertEqual(len(b), 4) nt, size = ip4.unpack(b) - self.assertEqual(str(nt), '1.1.1.1') + self.assertEqual(str(nt), "1.1.1.1") - ip6 = VPPTypeAlias('vl_api_ip6_address_t', {'type': 'u8', - 'length': 16}) - VPPUnionType('vl_api_address_union_t', - [["vl_api_ip4_address_t", "ip4"], - ["vl_api_ip6_address_t", "ip6"]]) + ip6 = VPPTypeAlias("vl_api_ip6_address_t", {"type": "u8", "length": 16}) + VPPUnionType( + "vl_api_address_union_t", + [["vl_api_ip4_address_t", "ip4"], ["vl_api_ip6_address_t", "ip6"]], + ) - address = VPPType('vl_api_address_t', - [['vl_api_address_family_t', 'af'], - ['vl_api_address_union_t', 'un']]) + address = VPPType( + "vl_api_address_t", + [["vl_api_address_family_t", "af"], ["vl_api_address_union_t", "un"]], + ) - prefix = VPPType('vl_api_prefix_t', - [['vl_api_address_t', 'address'], - ['u8', 'len']]) - message = VPPMessage('svs', - [['vl_api_prefix_t', 'prefix']]) - message_addr = VPPMessage('svs_address', - [['vl_api_address_t', 'address']]) + prefix = VPPType( + "vl_api_prefix_t", [["vl_api_address_t", "address"], ["u8", "len"]] + ) + message = VPPMessage("svs", [["vl_api_prefix_t", "prefix"]]) + message_addr = VPPMessage("svs_address", [["vl_api_address_t", "address"]]) - b = message_addr.pack({'address': "1::1"}) + b = message_addr.pack({"address": "1::1"}) self.assertEqual(len(b), 20) nt, size = message_addr.unpack(b) self.assertEqual("1::1", str(nt.address)) - b = message_addr.pack({'address': "1.1.1.1"}) + b = message_addr.pack({"address": "1.1.1.1"}) self.assertEqual(len(b), 20) nt, size = message_addr.unpack(b) self.assertEqual("1.1.1.1", str(nt.address)) - b = message.pack({'prefix': "1.1.1.0/24"}) + b = message.pack({"prefix": "1.1.1.0/24"}) self.assertEqual(len(b), 21) nt, size = message.unpack(b) self.assertEqual("1.1.1.0/24", str(nt.prefix)) - message_array = VPPMessage('address_array', - [['vl_api_ip6_address_t', - 'addresses', 2]]) - b = message_array.pack({'addresses': [IPv6Address(u"1::1"), "2::2"]}) + message_array = VPPMessage( + "address_array", [["vl_api_ip6_address_t", "addresses", 2]] + ) + b = message_array.pack({"addresses": [IPv6Address("1::1"), "2::2"]}) self.assertEqual(len(b), 32) - message_array_vla = VPPMessage('address_array_vla', - [['u32', 'num'], - ['vl_api_ip6_address_t', - 'addresses', 0, 'num']]) - b = message_array_vla.pack({'addresses': ["1::1", "2::2"], 'num': 2}) + message_array_vla = VPPMessage( + "address_array_vla", + [["u32", "num"], ["vl_api_ip6_address_t", "addresses", 0, "num"]], + ) + b = message_array_vla.pack({"addresses": ["1::1", "2::2"], "num": 2}) self.assertEqual(len(b), 36) - message_array4 = VPPMessage('address_array4', - [['vl_api_ip4_address_t', - 'addresses', 2]]) - b = message_array4.pack({'addresses': ["1.1.1.1", "2.2.2.2"]}) + message_array4 = VPPMessage( + "address_array4", [["vl_api_ip4_address_t", "addresses", 2]] + ) + b = message_array4.pack({"addresses": ["1.1.1.1", "2.2.2.2"]}) self.assertEqual(len(b), 8) - b = message_array4.pack({'addresses': [IPv4Address(u"1.1.1.1"), - "2.2.2.2"]}) + b = message_array4.pack({"addresses": [IPv4Address("1.1.1.1"), "2.2.2.2"]}) self.assertEqual(len(b), 8) - message = VPPMessage('address', [['vl_api_address_t', 'address']]) - b = message.pack({'address': '1::1'}) + message = VPPMessage("address", [["vl_api_address_t", "address"]]) + b = message.pack({"address": "1::1"}) self.assertEqual(len(b), 20) - b = message.pack({'address': '1.1.1.1'}) + b = message.pack({"address": "1.1.1.1"}) self.assertEqual(len(b), 20) - message = VPPMessage('prefix', [['vl_api_prefix_t', 'prefix']]) - b = message.pack({'prefix': '1::1/130'}) + message = VPPMessage("prefix", [["vl_api_prefix_t", "prefix"]]) + b = message.pack({"prefix": "1::1/130"}) self.assertEqual(len(b), 21) - b = message.pack({'prefix': IPv6Network(u'1::/119')}) + b = message.pack({"prefix": IPv6Network("1::/119")}) self.assertEqual(len(b), 21) - b = message.pack({'prefix': IPv4Network(u'1.1.0.0/16')}) + b = message.pack({"prefix": IPv4Network("1.1.0.0/16")}) self.assertEqual(len(b), 21) def test_zero_vla(self): - '''Default zero'ed out for VLAs''' - list = VPPType('vl_api_list_t', - [['u8', 'count', 10]]) + """Default zero'ed out for VLAs""" + list = VPPType("vl_api_list_t", [["u8", "count", 10]]) # Define an embedded VLA type - valist = VPPType('vl_api_valist_t', - [['u8', 'count'], - ['u8', 'string', 0, 'count']]) + valist = VPPType( + "vl_api_valist_t", [["u8", "count"], ["u8", "string", 0, "count"]] + ) # Define a message - vamessage = VPPMessage('vamsg', - [['vl_api_valist_t', 'valist'], - ['u8', 'is_something']]) + vamessage = VPPMessage( + "vamsg", [["vl_api_valist_t", "valist"], ["u8", "is_something"]] + ) - message = VPPMessage('msg', - [['vl_api_list_t', 'list'], - ['u8', 'is_something']]) + message = VPPMessage("msg", [["vl_api_list_t", "list"], ["u8", "is_something"]]) # Pack message without VLA specified - b = message.pack({'is_something': 1}) - b = vamessage.pack({'is_something': 1}) + b = message.pack({"is_something": 1}) + b = vamessage.pack({"is_something": 1}) def test_arrays(self): # Test cases @@ -382,254 +380,299 @@ class TestAddType(unittest.TestCase): # 2. Fixed list of variable length sub type # 3. Variable length type # - s = VPPType('str', [['u32', 'length'], - ['u8', 'string', 0, 'length']]) + s = VPPType("str", [["u32", "length"], ["u8", "string", 0, "length"]]) - ip4 = VPPType('ip4_address', [['u8', 'address', 4]]) - listip4 = VPPType('list_ip4_t', [['ip4_address', 'addresses', 4]]) - valistip4 = VPPType('list_ip4_t', - [['u8', 'count'], - ['ip4_address', 'addresses', 0, 'count']]) + ip4 = VPPType("ip4_address", [["u8", "address", 4]]) + listip4 = VPPType("list_ip4_t", [["ip4_address", "addresses", 4]]) + valistip4 = VPPType( + "list_ip4_t", [["u8", "count"], ["ip4_address", "addresses", 0, "count"]] + ) - valistip4_legacy = VPPType('list_ip4_t', - [['u8', 'foo'], - ['ip4_address', 'addresses', 0]]) + valistip4_legacy = VPPType( + "list_ip4_t", [["u8", "foo"], ["ip4_address", "addresses", 0]] + ) addresses = [] for i in range(4): - addresses.append({'address': inet_pton(AF_INET, '2.2.2.2')}) - b = listip4.pack({'addresses': addresses}) + addresses.append({"address": inet_pton(AF_INET, "2.2.2.2")}) + b = listip4.pack({"addresses": addresses}) self.assertEqual(len(b), 16) nt, size = listip4.unpack(b) - self.assertEqual(nt.addresses[0].address, - inet_pton(AF_INET, '2.2.2.2')) + self.assertEqual(nt.addresses[0].address, inet_pton(AF_INET, "2.2.2.2")) - b = valistip4.pack({'count': len(addresses), 'addresses': addresses}) + b = valistip4.pack({"count": len(addresses), "addresses": addresses}) self.assertEqual(len(b), 17) nt, size = valistip4.unpack(b) self.assertEqual(nt.count, 4) - self.assertEqual(nt.addresses[0].address, - inet_pton(AF_INET, '2.2.2.2')) + self.assertEqual(nt.addresses[0].address, inet_pton(AF_INET, "2.2.2.2")) - b = valistip4_legacy.pack({'foo': 1, 'addresses': addresses}) + b = valistip4_legacy.pack({"foo": 1, "addresses": addresses}) self.assertEqual(len(b), 17) nt, size = valistip4_legacy.unpack(b) self.assertEqual(len(nt.addresses), 4) - self.assertEqual(nt.addresses[0].address, - inet_pton(AF_INET, '2.2.2.2')) + self.assertEqual(nt.addresses[0].address, inet_pton(AF_INET, "2.2.2.2")) - string = 'foobar foobar' - b = s.pack({'length': len(string), 'string': string.encode('utf-8')}) + string = "foobar foobar" + b = s.pack({"length": len(string), "string": string.encode("utf-8")}) nt, size = s.unpack(b) self.assertEqual(len(b), size) def test_string(self): - s = VPPType('str', [['u32', 'length'], - ['u8', 'string', 0, 'length']]) + s = VPPType("str", [["u32", "length"], ["u8", "string", 0, "length"]]) - string = '' - b = s.pack({'length': len(string), 'string': string.encode('utf-8')}) + string = "" + b = s.pack({"length": len(string), "string": string.encode("utf-8")}) nt, size = s.unpack(b) self.assertEqual(len(b), size) + # Try same with VLA u8 + byte_array = [b"\0"] * (10) + vla_u8 = VPPType("vla_u8", [["u8", "length"], ["u8", "data", 0, "length"]]) + b = vla_u8.pack({"length": len(byte_array), "data": byte_array}) + nt, size = vla_u8.unpack(b) + + # VLA Array of fixed length strings + fixed_string = VPPType("fixed_string", [["string", "data", 32]]) + s = VPPType( + "string_vla", [["u32", "length"], ["fixed_string", "services", 0, "length"]] + ) + + string_list = [{"data": "foobar1"}, {"data": "foobar2"}] + b = s.pack({"length": 2, "services": string_list}) + nt, size = s.unpack(b) + + # Try same with u8 + fixed_u8 = VPPType("fixed_u8", [["u8", "data", 32]]) + s = VPPType( + "u8_vla", [["u32", "length"], ["fixed_string", "services", 0, "length"]] + ) + + u8_list = [{"data": "foobar1"}, {"data": "foobar2"}] + b = s.pack({"length": 2, "services": u8_list}) + nt, size = s.unpack(b) + def test_message(self): - foo = VPPMessage('foo', [['u16', '_vl_msg_id'], - ['u8', 'client_index'], - ['u8', 'something'], - {"crc": "0x559b9f3c"}]) - b = foo.pack({'_vl_msg_id': 1, 'client_index': 5, - 'something': 200}) + foo = VPPMessage( + "foo", + [ + ["u16", "_vl_msg_id"], + ["u8", "client_index"], + ["u8", "something"], + {"crc": "0x559b9f3c"}, + ], + ) + b = foo.pack({"_vl_msg_id": 1, "client_index": 5, "something": 200}) nt, size = foo.unpack(b) self.assertEqual(len(b), size) self.assertEqual(nt.something, 200) def test_abf(self): + fib_mpls_label = VPPType( + "vl_api_fib_mpls_label_t", + [["u8", "is_uniform"], ["u32", "label"], ["u8", "ttl"], ["u8", "exp"]], + ) - fib_mpls_label = VPPType('vl_api_fib_mpls_label_t', - [['u8', 'is_uniform'], - ['u32', 'label'], - ['u8', 'ttl'], - ['u8', 'exp']]) - - label_stack = {'is_uniform': 0, - 'label': 0, - 'ttl': 0, - 'exp': 0} + label_stack = {"is_uniform": 0, "label": 0, "ttl": 0, "exp": 0} b = fib_mpls_label.pack(label_stack) self.assertEqual(len(b), 7) - fib_path = VPPType('vl_api_fib_path_t', - [['u32', 'sw_if_index'], - ['u32', 'table_id'], - ['u8', 'weight'], - ['u8', 'preference'], - ['u8', 'is_local'], - ['u8', 'is_drop'], - ['u8', 'is_udp_encap'], - ['u8', 'is_unreach'], - ['u8', 'is_prohibit'], - ['u8', 'is_resolve_host'], - ['u8', 'is_resolve_attached'], - ['u8', 'is_dvr'], - ['u8', 'is_source_lookup'], - ['u8', 'afi'], - ['u8', 'next_hop', 16], - ['u32', 'next_hop_id'], - ['u32', 'rpf_id'], - ['u32', 'via_label'], - ['u8', 'n_labels'], - ['vl_api_fib_mpls_label_t', 'label_stack', 16]]) + fib_path = VPPType( + "vl_api_fib_path_t", + [ + ["u32", "sw_if_index"], + ["u32", "table_id"], + ["u8", "weight"], + ["u8", "preference"], + ["u8", "is_local"], + ["u8", "is_drop"], + ["u8", "is_udp_encap"], + ["u8", "is_unreach"], + ["u8", "is_prohibit"], + ["u8", "is_resolve_host"], + ["u8", "is_resolve_attached"], + ["u8", "is_dvr"], + ["u8", "is_source_lookup"], + ["u8", "afi"], + ["u8", "next_hop", 16], + ["u32", "next_hop_id"], + ["u32", "rpf_id"], + ["u32", "via_label"], + ["u8", "n_labels"], + ["vl_api_fib_mpls_label_t", "label_stack", 16], + ], + ) label_stack_list = [] for i in range(16): label_stack_list.append(label_stack) - paths = {'is_udp_encap': 0, - 'next_hop': b'\x10\x02\x02\xac', - 'table_id': 0, - 'afi': 0, - 'weight': 1, - 'next_hop_id': 4294967295, - 'label_stack': label_stack_list, - 'n_labels': 0, - 'sw_if_index': 4294967295, - 'preference': 0} + paths = { + "is_udp_encap": 0, + "next_hop": b"\x10\x02\x02\xac", + "table_id": 0, + "afi": 0, + "weight": 1, + "next_hop_id": 4294967295, + "label_stack": label_stack_list, + "n_labels": 0, + "sw_if_index": 4294967295, + "preference": 0, + } b = fib_path.pack(paths) - self.assertEqual(len(b), (7*16) + 49) + self.assertEqual(len(b), (7 * 16) + 49) - abf_policy = VPPType('vl_api_abf_policy_t', - [['u32', 'policy_id'], - ['u32', 'acl_index'], - ['u8', 'n_paths'], - ['vl_api_fib_path_t', 'paths', 0, 'n_paths']]) + abf_policy = VPPType( + "vl_api_abf_policy_t", + [ + ["u32", "policy_id"], + ["u32", "acl_index"], + ["u8", "n_paths"], + ["vl_api_fib_path_t", "paths", 0, "n_paths"], + ], + ) - policy = { - 'n_paths': 1, - 'paths': [paths], - 'acl_index': 0, - 'policy_id': 10} + policy = {"n_paths": 1, "paths": [paths], "acl_index": 0, "policy_id": 10} b = abf_policy.pack(policy) - self.assertEqual(len(b), (7*16) + 49 + 9) - - abf_policy_add_del = VPPMessage('abf_policy_add_del', - [['u16', '_vl_msg_id'], - ['u32', 'client_index'], - ['u32', 'context'], - ['u8', 'is_add'], - ['vl_api_abf_policy_t', 'policy']]) - - b = abf_policy_add_del.pack({'is_add': 1, - 'context': 66, - '_vl_msg_id': 1066, - 'policy': policy}) + self.assertEqual(len(b), (7 * 16) + 49 + 9) + + abf_policy_add_del = VPPMessage( + "abf_policy_add_del", + [ + ["u16", "_vl_msg_id"], + ["u32", "client_index"], + ["u32", "context"], + ["u8", "is_add"], + ["vl_api_abf_policy_t", "policy"], + ], + ) + + b = abf_policy_add_del.pack( + {"is_add": 1, "context": 66, "_vl_msg_id": 1066, "policy": policy} + ) nt, size = abf_policy_add_del.unpack(b) - self.assertEqual(nt.policy.paths[0].next_hop, - b'\x10\x02\x02\xac\x00\x00\x00\x00' - b'\x00\x00\x00\x00\x00\x00\x00\x00') + self.assertEqual( + nt.policy.paths[0].next_hop, + b"\x10\x02\x02\xac\x00\x00\x00\x00" b"\x00\x00\x00\x00\x00\x00\x00\x00", + ) def test_bier(self): - - bier_table_id = VPPType('vl_api_bier_table_id_t', - [['u8', 'bt_set'], - ['u8', 'bt_sub_domain'], - ['u8', 'bt_hdr_len_id']]) - - bier_imp_add = VPPMessage('bier_imp_add', - [['u32', 'client_index'], - ['u32', 'context'], - ['vl_api_bier_table_id_t', 'bi_tbl_id'], - ['u16', 'bi_src'], - ['u8', 'bi_n_bytes'], - ['u8', 'bi_bytes', 0, 'bi_n_bytes']]) - - table_id = {'bt_set': 0, - 'bt_sub_domain': 0, - 'bt_hdr_len_id': 0} - - bibytes = b'foobar' - - b = bier_imp_add.pack({'bi_tbl_id': table_id, - 'bi_n_bytes': len(bibytes), - 'bi_bytes': bibytes}) + bier_table_id = VPPType( + "vl_api_bier_table_id_t", + [["u8", "bt_set"], ["u8", "bt_sub_domain"], ["u8", "bt_hdr_len_id"]], + ) + + bier_imp_add = VPPMessage( + "bier_imp_add", + [ + ["u32", "client_index"], + ["u32", "context"], + ["vl_api_bier_table_id_t", "bi_tbl_id"], + ["u16", "bi_src"], + ["u8", "bi_n_bytes"], + ["u8", "bi_bytes", 0, "bi_n_bytes"], + ], + ) + + table_id = {"bt_set": 0, "bt_sub_domain": 0, "bt_hdr_len_id": 0} + + bibytes = b"foobar" + + b = bier_imp_add.pack( + {"bi_tbl_id": table_id, "bi_n_bytes": len(bibytes), "bi_bytes": bibytes} + ) self.assertEqual(len(b), 20) def test_lisp(self): - VPPEnumType('vl_api_eid_type_t', - [["EID_TYPE_API_PREFIX", 0], - ["EID_TYPE_API_MAC", 1], - ["EID_TYPE_API_NSH", 2], - {"enumtype": "u32"}]) - - VPPTypeAlias('vl_api_mac_address_t', {'type': 'u8', - 'length': 6}) - - VPPType('vl_api_nsh_t', - [["u32", "spi"], - ["u8", "si"]]) - - VPPEnumType('vl_api_address_family_t', [["ADDRESS_IP4", 0], - ["ADDRESS_IP6", 1], - {"enumtype": "u32"}]) - VPPTypeAlias('vl_api_ip4_address_t', {'type': 'u8', - 'length': 4}) - VPPTypeAlias('vl_api_ip6_address_t', {'type': 'u8', - 'length': 16}) - VPPUnionType('vl_api_address_union_t', - [["vl_api_ip4_address_t", "ip4"], - ["vl_api_ip6_address_t", "ip6"]]) - - VPPType('vl_api_address_t', - [['vl_api_address_family_t', 'af'], - ['vl_api_address_union_t', 'un']]) - - VPPType('vl_api_prefix_t', - [['vl_api_address_t', 'address'], - ['u8', 'len']]) - - VPPUnionType('vl_api_eid_address_t', - [["vl_api_prefix_t", "prefix"], - ["vl_api_mac_address_t", "mac"], - ["vl_api_nsh_t", "nsh"]]) - - eid = VPPType('vl_api_eid_t', - [["vl_api_eid_type_t", "type"], - ["vl_api_eid_address_t", "address"]]) - - b = eid.pack({'type':1, - 'address': { - 'mac': MACAddress('aa:bb:cc:dd:ee:ff')}}) + VPPEnumType( + "vl_api_eid_type_t", + [ + ["EID_TYPE_API_PREFIX", 0], + ["EID_TYPE_API_MAC", 1], + ["EID_TYPE_API_NSH", 2], + {"enumtype": "u32"}, + ], + ) + + VPPTypeAlias("vl_api_mac_address_t", {"type": "u8", "length": 6}) + + VPPType("vl_api_nsh_t", [["u32", "spi"], ["u8", "si"]]) + + VPPEnumType( + "vl_api_address_family_t", + [["ADDRESS_IP4", 0], ["ADDRESS_IP6", 1], {"enumtype": "u32"}], + ) + VPPTypeAlias("vl_api_ip4_address_t", {"type": "u8", "length": 4}) + VPPTypeAlias("vl_api_ip6_address_t", {"type": "u8", "length": 16}) + VPPUnionType( + "vl_api_address_union_t", + [["vl_api_ip4_address_t", "ip4"], ["vl_api_ip6_address_t", "ip6"]], + ) + + VPPType( + "vl_api_address_t", + [["vl_api_address_family_t", "af"], ["vl_api_address_union_t", "un"]], + ) + + VPPType("vl_api_prefix_t", [["vl_api_address_t", "address"], ["u8", "len"]]) + + VPPUnionType( + "vl_api_eid_address_t", + [ + ["vl_api_prefix_t", "prefix"], + ["vl_api_mac_address_t", "mac"], + ["vl_api_nsh_t", "nsh"], + ], + ) + + eid = VPPType( + "vl_api_eid_t", + [["vl_api_eid_type_t", "type"], ["vl_api_eid_address_t", "address"]], + ) + + b = eid.pack({"type": 1, "address": {"mac": MACAddress("aa:bb:cc:dd:ee:ff")}}) self.assertEqual(len(b), 25) nt, size = eid.unpack(b) - self.assertEqual(str(nt.address.mac), 'aa:bb:cc:dd:ee:ff') + self.assertEqual(str(nt.address.mac), "aa:bb:cc:dd:ee:ff") self.assertIsNone(nt.address.prefix) class TestVppSerializerLogging(unittest.TestCase): - def test_logger(self): # test logger name 'vpp_papi.serializer' with self.assertRaises(VPPSerializerValueError) as ctx: - with self.assertLogs('vpp_papi.serializer', level='DEBUG') as cm: - u = VPPUnionType('vl_api_eid_address_t', - [["vl_api_prefix_t", "prefix"], - ["vl_api_mac_address_t", "mac"], - ["vl_api_nsh_t", "nsh"]]) - self.assertEqual(cm.output, ["DEBUG:vpp_papi.serializer:Unknown union type vl_api_prefix_t"]) + with self.assertLogs("vpp_papi.serializer", level="DEBUG") as cm: + u = VPPUnionType( + "vl_api_eid_address_t", + [ + ["vl_api_prefix_t", "prefix"], + ["vl_api_mac_address_t", "mac"], + ["vl_api_nsh_t", "nsh"], + ], + ) + self.assertEqual( + cm.output, ["DEBUG:vpp_papi.serializer:Unknown union type vl_api_prefix_t"] + ) # test parent logger name 'vpp_papi' with self.assertRaises(VPPSerializerValueError) as ctx: - with self.assertLogs('vpp_papi', level='DEBUG') as cm: - u = VPPUnionType('vl_api_eid_address_t', - [["vl_api_prefix_t", "prefix"], - ["vl_api_mac_address_t", "mac"], - ["vl_api_nsh_t", "nsh"]]) - self.assertEqual(cm.output, ["DEBUG:vpp_papi.serializer:Unknown union type vl_api_prefix_t"]) - - -if __name__ == '__main__': + with self.assertLogs("vpp_papi", level="DEBUG") as cm: + u = VPPUnionType( + "vl_api_eid_address_t", + [ + ["vl_api_prefix_t", "prefix"], + ["vl_api_mac_address_t", "mac"], + ["vl_api_nsh_t", "nsh"], + ], + ) + self.assertEqual( + cm.output, ["DEBUG:vpp_papi.serializer:Unknown union type vl_api_prefix_t"] + ) + + +if __name__ == "__main__": unittest.main() diff --git a/src/vpp-api/python/vpp_papi/vpp_format.py b/src/vpp-api/python/vpp_papi/vpp_format.py index 0b85eb4fcb6..f80a781c753 100644 --- a/src/vpp-api/python/vpp_papi/vpp_format.py +++ b/src/vpp-api/python/vpp_papi/vpp_format.py @@ -25,8 +25,8 @@ ADDRESS_IP6 = 1 def verify_enum_hint(e): - return (e.ADDRESS_IP4.value == ADDRESS_IP4) and\ - (e.ADDRESS_IP6.value == ADDRESS_IP6) + return (e.ADDRESS_IP4.value == ADDRESS_IP4) and (e.ADDRESS_IP6.value == ADDRESS_IP6) + # # Type conversion for input arguments and return values @@ -35,146 +35,128 @@ def verify_enum_hint(e): def format_vl_api_address_t(args): try: - return {'un': {'ip6': inet_pton(AF_INET6, args)}, - 'af': ADDRESS_IP6} + return {"un": {"ip6": inet_pton(AF_INET6, args)}, "af": ADDRESS_IP6} # PY2: raises socket.error # PY3: raises OSError except (socket.error, OSError): - return {'un': {'ip4': inet_pton(AF_INET, args)}, - 'af': ADDRESS_IP4} + return {"un": {"ip4": inet_pton(AF_INET, args)}, "af": ADDRESS_IP4} def format_vl_api_prefix_t(args): if isinstance(args, (ipaddress.IPv4Network, ipaddress.IPv6Network)): - return {'address': format_vl_api_address_t( - str(args.network_address)), - 'len': int(args.prefixlen)} - p, length = args.split('/') - return {'address': format_vl_api_address_t(p), - 'len': int(length)} + return { + "address": format_vl_api_address_t(str(args.network_address)), + "len": int(args.prefixlen), + } + p, length = args.split("/") + return {"address": format_vl_api_address_t(p), "len": int(length)} def format_vl_api_address_with_prefix_t(args): if isinstance(args, (ipaddress.IPv4Interface, ipaddress.IPv6Interface)): - return {'address': format_vl_api_address_t( - str(args.network_address)), - 'len': int(args.prefixlen)} - p, length = args.split('/') - return {'address': format_vl_api_address_t(p), - 'len': int(length)} + return { + "address": format_vl_api_address_t(str(args.network_address)), + "len": int(args.prefixlen), + } + p, length = args.split("/") + return {"address": format_vl_api_address_t(p), "len": int(length)} def format_vl_api_ip6_prefix_t(args): if isinstance(args, ipaddress.IPv6Network): - return {'address': args.network_address.packed, - 'len': int(args.prefixlen)} - p, length = args.split('/') - return {'address': inet_pton(AF_INET6, p), - 'len': int(length)} + return {"address": args.network_address.packed, "len": int(args.prefixlen)} + p, length = args.split("/") + return {"address": inet_pton(AF_INET6, p), "len": int(length)} def format_vl_api_ip6_address_with_prefix_t(args): if isinstance(args, ipaddress.IPv6Interface): - return {'address': args.network_address.packed, - 'len': int(args.prefixlen)} - p, length = args.split('/') - return {'address': inet_pton(AF_INET6, p), - 'len': int(length)} + return {"address": args.network_address.packed, "len": int(args.prefixlen)} + p, length = args.split("/") + return {"address": inet_pton(AF_INET6, p), "len": int(length)} def format_vl_api_ip4_prefix_t(args): if isinstance(args, ipaddress.IPv4Network): - return {'address': args.network_address.packed, - 'len': int(args.prefixlen)} - p, length = args.split('/') - return {'address': inet_pton(AF_INET, p), - 'len': int(length)} + return {"address": args.network_address.packed, "len": int(args.prefixlen)} + p, length = args.split("/") + return {"address": inet_pton(AF_INET, p), "len": int(length)} def format_vl_api_ip4_address_with_prefix_t(args): if isinstance(args, ipaddress.IPv4Interface): - return {'address': args.network_address.packed, - 'len': int(args.prefixlen)} - p, length = args.split('/') - return {'address': inet_pton(AF_INET, p), - 'len': int(length)} + return {"address": args.network_address.packed, "len": int(args.prefixlen)} + p, length = args.split("/") + return {"address": inet_pton(AF_INET, p), "len": int(length)} conversion_table = { - 'vl_api_ip6_address_t': - { - 'IPv6Address': lambda o: o.packed, - 'str': lambda s: inet_pton(AF_INET6, s) + "vl_api_ip6_address_t": { + "IPv6Address": lambda o: o.packed, + "str": lambda s: inet_pton(AF_INET6, s), + }, + "vl_api_ip4_address_t": { + "IPv4Address": lambda o: o.packed, + "str": lambda s: inet_pton(AF_INET, s), }, - 'vl_api_ip4_address_t': - { - 'IPv4Address': lambda o: o.packed, - 'str': lambda s: inet_pton(AF_INET, s) + "vl_api_ip6_prefix_t": { + "IPv6Network": lambda o: { + "address": o.network_address.packed, + "len": o.prefixlen, + }, + "str": lambda s: format_vl_api_ip6_prefix_t(s), }, - 'vl_api_ip6_prefix_t': - { - 'IPv6Network': lambda o: {'address': o.network_address.packed, - 'len': o.prefixlen}, - 'str': lambda s: format_vl_api_ip6_prefix_t(s) + "vl_api_ip4_prefix_t": { + "IPv4Network": lambda o: { + "address": o.network_address.packed, + "len": o.prefixlen, + }, + "str": lambda s: format_vl_api_ip4_prefix_t(s), }, - 'vl_api_ip4_prefix_t': - { - 'IPv4Network': lambda o: {'address': o.network_address.packed, - 'len': o.prefixlen}, - 'str': lambda s: format_vl_api_ip4_prefix_t(s) + "vl_api_address_t": { + "IPv4Address": lambda o: {"af": ADDRESS_IP4, "un": {"ip4": o.packed}}, + "IPv6Address": lambda o: {"af": ADDRESS_IP6, "un": {"ip6": o.packed}}, + "str": lambda s: format_vl_api_address_t(s), }, - 'vl_api_address_t': - { - 'IPv4Address': lambda o: {'af': ADDRESS_IP4, 'un': {'ip4': o.packed}}, - 'IPv6Address': lambda o: {'af': ADDRESS_IP6, 'un': {'ip6': o.packed}}, - 'str': lambda s: format_vl_api_address_t(s) + "vl_api_prefix_t": { + "IPv4Network": lambda o: { + "address": {"af": ADDRESS_IP4, "un": {"ip4": o.network_address.packed}}, + "len": o.prefixlen, + }, + "IPv6Network": lambda o: { + "address": {"af": ADDRESS_IP6, "un": {"ip6": o.network_address.packed}}, + "len": o.prefixlen, + }, + "str": lambda s: format_vl_api_prefix_t(s), }, - 'vl_api_prefix_t': - { - 'IPv4Network': lambda o: {'address': - {'af': ADDRESS_IP4, 'un': - {'ip4': o.network_address.packed}}, - 'len': o.prefixlen}, - 'IPv6Network': lambda o: {'address': - {'af': ADDRESS_IP6, 'un': - {'ip6': o.network_address.packed}}, - 'len': o.prefixlen}, - 'str': lambda s: format_vl_api_prefix_t(s) + "vl_api_address_with_prefix_t": { + "IPv4Interface": lambda o: { + "address": {"af": ADDRESS_IP4, "un": {"ip4": o.packed}}, + "len": o.network.prefixlen, + }, + "IPv6Interface": lambda o: { + "address": {"af": ADDRESS_IP6, "un": {"ip6": o.packed}}, + "len": o.network.prefixlen, + }, + "str": lambda s: format_vl_api_address_with_prefix_t(s), }, - 'vl_api_address_with_prefix_t': - { - 'IPv4Interface': lambda o: {'address': - {'af': ADDRESS_IP4, 'un': - {'ip4': o.packed}}, - 'len': o.network.prefixlen}, - 'IPv6Interface': lambda o: {'address': - {'af': ADDRESS_IP6, 'un': - {'ip6': o.packed}}, - 'len': o.network.prefixlen}, - 'str': lambda s: format_vl_api_address_with_prefix_t(s) + "vl_api_ip4_address_with_prefix_t": { + "IPv4Interface": lambda o: {"address": o.packed, "len": o.network.prefixlen}, + "str": lambda s: format_vl_api_ip4_address_with_prefix_t(s), }, - 'vl_api_ip4_address_with_prefix_t': - { - 'IPv4Interface': lambda o: {'address': o.packed, - 'len': o.network.prefixlen}, - 'str': lambda s: format_vl_api_ip4_address_with_prefix_t(s) + "vl_api_ip6_address_with_prefix_t": { + "IPv6Interface": lambda o: {"address": o.packed, "len": o.network.prefixlen}, + "str": lambda s: format_vl_api_ip6_address_with_prefix_t(s), }, - 'vl_api_ip6_address_with_prefix_t': - { - 'IPv6Interface': lambda o: {'address': o.packed, - 'len': o.network.prefixlen}, - 'str': lambda s: format_vl_api_ip6_address_with_prefix_t(s) + "vl_api_mac_address_t": { + "MACAddress": lambda o: o.packed, + "str": lambda s: macaddress.mac_pton(s), }, - 'vl_api_mac_address_t': - { - 'MACAddress': lambda o: o.packed, - 'str': lambda s: macaddress.mac_pton(s) + "vl_api_timestamp_t": { + "datetime.datetime": lambda o: ( + o - datetime.datetime(1970, 1, 1) + ).total_seconds() }, - 'vl_api_timestamp_t': - { - 'datetime.datetime': lambda o: - (o - datetime.datetime(1970, 1, 1)).total_seconds() - } } @@ -197,7 +179,7 @@ def unformat_api_prefix_t(o): return ipaddress.IPv4Network((o.address, o.len), False) if isinstance(o.address, ipaddress.IPv6Address): return ipaddress.IPv6Network((o.address, o.len), False) - raise ValueError('Unknown instance {}', format(o)) + raise ValueError("Unknown instance {}", format(o)) def unformat_api_address_with_prefix_t(o): @@ -217,16 +199,20 @@ def unformat_api_ip6_address_with_prefix_t(o): conversion_unpacker_table = { - 'vl_api_ip6_address_t': lambda o: ipaddress.IPv6Address(o), - 'vl_api_ip6_prefix_t': lambda o: ipaddress.IPv6Network((o.address, o.len)), - 'vl_api_ip4_address_t': lambda o: ipaddress.IPv4Address(o), - 'vl_api_ip4_prefix_t': lambda o: ipaddress.IPv4Network((o.address, o.len)), - 'vl_api_address_t': lambda o: unformat_api_address_t(o), - 'vl_api_prefix_t': lambda o: unformat_api_prefix_t(o), - 'vl_api_address_with_prefix_t': lambda o: unformat_api_address_with_prefix_t(o), - 'vl_api_ip4_address_with_prefix_t': lambda o: unformat_api_ip4_address_with_prefix_t(o), - 'vl_api_ip6_address_with_prefix_t': lambda o: unformat_api_ip6_address_with_prefix_t(o), - 'vl_api_mac_address_t': lambda o: macaddress.MACAddress(o), - 'vl_api_timestamp_t': lambda o: datetime.datetime.fromtimestamp(o), - 'vl_api_timedelta_t': lambda o: datetime.timedelta(seconds=o), + "vl_api_ip6_address_t": lambda o: ipaddress.IPv6Address(o), + "vl_api_ip6_prefix_t": lambda o: ipaddress.IPv6Network((o.address, o.len)), + "vl_api_ip4_address_t": lambda o: ipaddress.IPv4Address(o), + "vl_api_ip4_prefix_t": lambda o: ipaddress.IPv4Network((o.address, o.len)), + "vl_api_address_t": lambda o: unformat_api_address_t(o), + "vl_api_prefix_t": lambda o: unformat_api_prefix_t(o), + "vl_api_address_with_prefix_t": lambda o: unformat_api_address_with_prefix_t(o), + "vl_api_ip4_address_with_prefix_t": lambda o: unformat_api_ip4_address_with_prefix_t( + o + ), + "vl_api_ip6_address_with_prefix_t": lambda o: unformat_api_ip6_address_with_prefix_t( + o + ), + "vl_api_mac_address_t": lambda o: macaddress.MACAddress(o), + "vl_api_timestamp_t": lambda o: datetime.datetime.fromtimestamp(o), + "vl_api_timedelta_t": lambda o: datetime.timedelta(seconds=o), } diff --git a/src/vpp-api/python/vpp_papi/vpp_papi.py b/src/vpp-api/python/vpp_papi/vpp_papi.py index 3465f503e9e..30c00cd8dd3 100644 --- a/src/vpp-api/python/vpp_papi/vpp_papi.py +++ b/src/vpp-api/python/vpp_papi/vpp_papi.py @@ -18,7 +18,6 @@ from __future__ import print_function from __future__ import absolute_import import ctypes import ipaddress -import sys import multiprocessing as mp import os import queue @@ -30,13 +29,15 @@ import fnmatch import weakref import atexit import time -from . vpp_format import verify_enum_hint -from . vpp_serializer import VPPType, VPPEnumType, VPPEnumFlagType, VPPUnionType -from . vpp_serializer import VPPMessage, vpp_get_type, VPPTypeAlias +import pkg_resources +from .vpp_format import verify_enum_hint +from .vpp_serializer import VPPType, VPPEnumType, VPPEnumFlagType, VPPUnionType +from .vpp_serializer import VPPMessage, vpp_get_type, VPPTypeAlias try: import VppTransport except ModuleNotFoundError: + class V: """placeholder for VppTransport as the implementation is dependent on VPPAPIClient's initialization values @@ -44,15 +45,22 @@ except ModuleNotFoundError: VppTransport = V -from . vpp_transport_socket import VppTransport +from .vpp_transport_socket import VppTransport -logger = logging.getLogger('vpp_papi') +logger = logging.getLogger("vpp_papi") logger.addHandler(logging.NullHandler()) -__all__ = ('FuncWrapper', 'VppApiDynamicMethodHolder', - 'VppEnum', 'VppEnumType', 'VppEnumFlag', - 'VPPIOError', 'VPPRuntimeError', 'VPPValueError', - 'VPPApiClient', ) +__all__ = ( + "FuncWrapper", + "VppApiDynamicMethodHolder", + "VppEnum", + "VppEnumType", + "VppEnumFlag", + "VPPIOError", + "VPPRuntimeError", + "VPPValueError", + "VPPApiClient", +) def metaclass(metaclass): @@ -83,7 +91,7 @@ def vpp_atexit(vpp_weakref): """Clean up VPP connection on shutdown.""" vpp_instance = vpp_weakref() if vpp_instance and vpp_instance.transport.connected: - logger.debug('Cleaning up VPP on exit') + logger.debug("Cleaning up VPP on exit") vpp_instance.disconnect() @@ -98,9 +106,9 @@ def add_convenience_methods(): def _vapi_af_name(self): if 6 == self._version: - return 'ip6' + return "ip6" if 4 == self._version: - return 'ip4' + return "ip4" raise ValueError("Invalid _version.") ipaddress._IPAddressBase.vapi_af = property(_vapi_af) @@ -121,7 +129,7 @@ class FuncWrapper: return self._func(**kwargs) def __repr__(self): - return '<FuncWrapper(func=<%s(%s)>)>' % (self.__name__, self.__doc__) + return "<FuncWrapper(func=<%s(%s)>)>" % (self.__name__, self.__doc__) class VPPApiError(Exception): @@ -146,7 +154,7 @@ class VPPValueError(ValueError): class VPPApiJSONFiles: @classmethod - def find_api_dir(cls, dirs): + def find_api_dir(cls, dirs=[]): """Attempt to find the best directory in which API definition files may reside. If the value VPP_API_DIR exists in the environment then it is first on the search list. If we're inside a recognized @@ -161,7 +169,11 @@ class VPPApiJSONFiles: # perhaps we're in the 'src/scripts' or 'src/vpp-api/python' dir; # in which case, plot a course to likely places in the src tree import __main__ as main - if hasattr(main, '__file__'): + + if os.getenv("VPP_API_DIR"): + dirs.append(os.getenv("VPP_API_DIR")) + + if hasattr(main, "__file__"): # get the path of the calling script localdir = os.path.dirname(os.path.realpath(main.__file__)) else: @@ -171,7 +183,7 @@ class VPPApiJSONFiles: def dmatch(dir): """Match dir against right-hand components of the script dir""" - d = dir.split('/') # param 'dir' assumes a / separator + d = dir.split("/") # param 'dir' assumes a / separator length = len(d) return len(localdir_s) > length and localdir_s[-length:] == d @@ -180,43 +192,45 @@ class VPPApiJSONFiles: 'variant' (typically '' or '_debug')""" # Since 'core' and 'plugin' files are staged # in separate directories, we target the parent dir. - return os.path.sep.join(( - srcdir, - 'build-root', - 'install-vpp%s-native' % variant, - 'vpp', - 'share', - 'vpp', - 'api', - )) + return os.path.sep.join( + ( + srcdir, + "build-root", + "install-vpp%s-native" % variant, + "vpp", + "share", + "vpp", + "api", + ) + ) srcdir = None - if dmatch('src/scripts'): + if dmatch("src/scripts"): srcdir = os.path.sep.join(localdir_s[:-2]) - elif dmatch('src/vpp-api/python'): + elif dmatch("src/vpp-api/python"): srcdir = os.path.sep.join(localdir_s[:-3]) - elif dmatch('test'): + elif dmatch("test"): # we're apparently running tests srcdir = os.path.sep.join(localdir_s[:-1]) if srcdir: # we're in the source tree, try both the debug and release # variants. - dirs.append(sdir(srcdir, '_debug')) - dirs.append(sdir(srcdir, '')) + dirs.append(sdir(srcdir, "_debug")) + dirs.append(sdir(srcdir, "")) # Test for staged copies of the scripts # For these, since we explicitly know if we're running a debug versus # release variant, target only the relevant directory - if dmatch('build-root/install-vpp_debug-native/vpp/bin'): + if dmatch("build-root/install-vpp_debug-native/vpp/bin"): srcdir = os.path.sep.join(localdir_s[:-4]) - dirs.append(sdir(srcdir, '_debug')) - if dmatch('build-root/install-vpp-native/vpp/bin'): + dirs.append(sdir(srcdir, "_debug")) + if dmatch("build-root/install-vpp-native/vpp/bin"): srcdir = os.path.sep.join(localdir_s[:-4]) - dirs.append(sdir(srcdir, '')) + dirs.append(sdir(srcdir, "")) # finally, try the location system packages typically install into - dirs.append(os.path.sep.join(('', 'usr', 'share', 'vpp', 'api'))) + dirs.append(os.path.sep.join(("", "usr", "share", "vpp", "api"))) # check the directories for existence; first one wins for dir in dirs: @@ -226,7 +240,7 @@ class VPPApiJSONFiles: return None @classmethod - def find_api_files(cls, api_dir=None, patterns='*'): # -> list + def find_api_files(cls, api_dir=None, patterns="*"): # -> list """Find API definition files from the given directory tree with the given pattern. If no directory is given then find_api_dir() is used to locate one. If no pattern is given then all definition files found @@ -252,9 +266,9 @@ class VPPApiJSONFiles: raise VPPApiError("api_dir cannot be located") if isinstance(patterns, list) or isinstance(patterns, tuple): - patterns = [p.strip() + '.api.json' for p in patterns] + patterns = [p.strip() + ".api.json" for p in patterns] else: - patterns = [p.strip() + '.api.json' for p in patterns.split(",")] + patterns = [p.strip() + ".api.json" for p in patterns.split(",")] api_files = [] for root, dirnames, files in os.walk(api_dir): @@ -275,45 +289,57 @@ class VPPApiJSONFiles: api = json.loads(json_str) return self._process_json(api) + @classmethod + def process_json_array_str(self, json_str): + services = {} + messages = {} + + apis = json.loads(json_str) + for a in apis: + m, s = self._process_json(a) + messages.update(m) + services.update(s) + return messages, services + @staticmethod def _process_json(api): # -> Tuple[Dict, Dict] types = {} services = {} messages = {} try: - for t in api['enums']: - t[0] = 'vl_api_' + t[0] + '_t' - types[t[0]] = {'type': 'enum', 'data': t} + for t in api["enums"]: + t[0] = "vl_api_" + t[0] + "_t" + types[t[0]] = {"type": "enum", "data": t} except KeyError: pass try: - for t in api['enumflags']: - t[0] = 'vl_api_' + t[0] + '_t' - types[t[0]] = {'type': 'enum', 'data': t} + for t in api["enumflags"]: + t[0] = "vl_api_" + t[0] + "_t" + types[t[0]] = {"type": "enum", "data": t} except KeyError: pass try: - for t in api['unions']: - t[0] = 'vl_api_' + t[0] + '_t' - types[t[0]] = {'type': 'union', 'data': t} + for t in api["unions"]: + t[0] = "vl_api_" + t[0] + "_t" + types[t[0]] = {"type": "union", "data": t} except KeyError: pass try: - for t in api['types']: - t[0] = 'vl_api_' + t[0] + '_t' - types[t[0]] = {'type': 'type', 'data': t} + for t in api["types"]: + t[0] = "vl_api_" + t[0] + "_t" + types[t[0]] = {"type": "type", "data": t} except KeyError: pass try: - for t, v in api['aliases'].items(): - types['vl_api_' + t + '_t'] = {'type': 'alias', 'data': v} + for t, v in api["aliases"].items(): + types["vl_api_" + t + "_t"] = {"type": "alias", "data": v} except KeyError: pass try: - services.update(api['services']) + services.update(api["services"]) except KeyError: pass @@ -321,30 +347,30 @@ class VPPApiJSONFiles: while True: unresolved = {} for k, v in types.items(): - t = v['data'] + t = v["data"] if not vpp_get_type(k): - if v['type'] == 'enum': + if v["type"] == "enum": try: VPPEnumType(t[0], t[1:]) except ValueError: unresolved[k] = v if not vpp_get_type(k): - if v['type'] == 'enumflag': + if v["type"] == "enumflag": try: VPPEnumFlagType(t[0], t[1:]) except ValueError: unresolved[k] = v - elif v['type'] == 'union': + elif v["type"] == "union": try: VPPUnionType(t[0], t[1:]) except ValueError: unresolved[k] = v - elif v['type'] == 'type': + elif v["type"] == "type": try: VPPType(t[0], t[1:]) except ValueError: unresolved[k] = v - elif v['type'] == 'alias': + elif v["type"] == "alias": try: VPPTypeAlias(k, t) except ValueError: @@ -352,21 +378,43 @@ class VPPApiJSONFiles: if len(unresolved) == 0: break if i > 3: - raise VPPValueError('Unresolved type definitions {}' - .format(unresolved)) + raise VPPValueError("Unresolved type definitions {}".format(unresolved)) types = unresolved i += 1 try: - for m in api['messages']: + for m in api["messages"]: try: messages[m[0]] = VPPMessage(m[0], m[1:]) except VPPNotImplementedError: - ### OLE FIXME - logger.error('Not implemented error for {}'.format(m[0])) + logger.error("Not implemented error for {}".format(m[0])) except KeyError: pass return messages, services + @staticmethod + def load_api(apifiles=None, apidir=None): + messages = {} + services = {} + if not apifiles: + # Pick up API definitions from default directory + try: + if isinstance(apidir, list): + apifiles = [] + for d in apidir: + apifiles += VPPApiJSONFiles.find_api_files(d) + else: + apifiles = VPPApiJSONFiles.find_api_files(apidir) + except (RuntimeError, VPPApiError): + raise VPPRuntimeError + + for file in apifiles: + with open(file) as apidef_file: + m, s = VPPApiJSONFiles.process_json_file(apidef_file) + messages.update(m) + services.update(s) + + return apifiles, messages, services + class VPPApiClient: """VPP interface. @@ -380,18 +428,27 @@ class VPPApiClient: provides a means to register a callback function to receive these messages in a background thread. """ - apidir = None + VPPApiError = VPPApiError VPPRuntimeError = VPPRuntimeError VPPValueError = VPPValueError VPPNotImplementedError = VPPNotImplementedError VPPIOError = VPPIOError - - def __init__(self, *, apifiles=None, testmode=False, async_thread=True, - logger=None, loglevel=None, - read_timeout=5, use_socket=True, - server_address='/run/vpp/api.sock'): + def __init__( + self, + *, + apifiles=None, + apidir=None, + testmode=False, + async_thread=True, + logger=None, + loglevel=None, + read_timeout=5, + use_socket=True, + server_address="/run/vpp/api.sock", + bootstrapapi=False, + ): """Create a VPP API object. apifiles is a list of files containing API @@ -406,7 +463,8 @@ class VPPApiClient: """ if logger is None: logger = logging.getLogger( - "{}.{}".format(__name__, self.__class__.__name__)) + "{}.{}".format(__name__, self.__class__.__name__) + ) if loglevel is not None: logger.setLevel(loglevel) self.logger = logger @@ -415,9 +473,9 @@ class VPPApiClient: self.services = {} self.id_names = [] self.id_msgdef = [] - self.header = VPPType('header', [['u16', 'msgid'], - ['u32', 'client_index']]) + self.header = VPPType("header", [["u16", "msgid"], ["u32", "client_index"]]) self.apifiles = [] + self.apidir = apidir self.event_callback = None self.message_queue = queue.Queue() self.read_timeout = read_timeout @@ -427,35 +485,41 @@ class VPPApiClient: self.server_address = server_address self._apifiles = apifiles self.stats = {} + self.bootstrapapi = bootstrapapi - if not apifiles: - # Pick up API definitions from default directory + if not bootstrapapi: + if self.apidir is None and hasattr(self.__class__, "apidir"): + # Keep supporting the old style of providing apidir. + self.apidir = self.__class__.apidir try: - apifiles = VPPApiJSONFiles.find_api_files(self.apidir) - except (RuntimeError, VPPApiError): - # In test mode we don't care that we can't find the API files + self.apifiles, self.messages, self.services = VPPApiJSONFiles.load_api( + apifiles, self.apidir + ) + except VPPRuntimeError as e: if testmode: - apifiles = [] + self.apifiles = [] else: - raise VPPRuntimeError - - for file in apifiles: - with open(file) as apidef_file: - m, s = VPPApiJSONFiles.process_json_file(apidef_file) - self.messages.update(m) - self.services.update(s) - - self.apifiles = apifiles + raise e + else: + # Bootstrap the API (memclnt.api bundled with VPP PAPI) + resource_path = "/".join(("data", "memclnt.api.json")) + file_content = pkg_resources.resource_string(__name__, resource_path) + self.messages, self.services = VPPApiJSONFiles.process_json_str( + file_content + ) # Basic sanity check if len(self.messages) == 0 and not testmode: - raise VPPValueError(1, 'Missing JSON message definitions') - if not(verify_enum_hint(VppEnum.vl_api_address_family_t)): - raise VPPRuntimeError("Invalid address family hints. " - "Cannot continue.") - - self.transport = VppTransport(self, read_timeout=read_timeout, - server_address=server_address) + raise VPPValueError(1, "Missing JSON message definitions") + if not bootstrapapi: + if not (verify_enum_hint(VppEnum.vl_api_address_family_t)): + raise VPPRuntimeError( + "Invalid address family hints. " "Cannot continue." + ) + + self.transport = VppTransport( + self, read_timeout=read_timeout, server_address=server_address + ) # Make sure we allow VPP to clean up the message rings. atexit.register(vpp_atexit, weakref.ref(self)) @@ -466,6 +530,7 @@ class VPPApiClient: class ContextId: """Multiprocessing-safe provider of unique context IDs.""" + def __init__(self): self.context = mp.Value(ctypes.c_uint, 0) self.lock = mp.Lock() @@ -475,6 +540,7 @@ class VPPApiClient: with self.lock: self.context.value += 1 return self.context.value + get_context = ContextId() def get_type(self, name): @@ -487,27 +553,37 @@ class VPPApiClient: return self._api def make_function(self, msg, i, multipart, do_async): - if (do_async): + if do_async: + def f(**kwargs): return self._call_vpp_async(i, msg, **kwargs) + else: + def f(**kwargs): return self._call_vpp(i, msg, multipart, **kwargs) f.__name__ = str(msg.name) - f.__doc__ = ", ".join(["%s %s" % - (msg.fieldtypes[j], k) - for j, k in enumerate(msg.fields)]) + f.__doc__ = ", ".join( + ["%s %s" % (msg.fieldtypes[j], k) for j, k in enumerate(msg.fields)] + ) f.msg = msg return f + def make_pack_function(self, msg, i, multipart): + def f(**kwargs): + return self._call_vpp_pack(i, msg, **kwargs) + + f.msg = msg + return f + def _register_functions(self, do_async=False): self.id_names = [None] * (self.vpp_dictionary_maxid + 1) self.id_msgdef = [None] * (self.vpp_dictionary_maxid + 1) self._api = VppApiDynamicMethodHolder() for name, msg in self.messages.items(): - n = name + '_' + msg.crc[2:] + n = name + "_" + msg.crc[2:] i = self.transport.get_msg_index(n) if i > 0: self.id_msgdef[i] = msg @@ -516,30 +592,49 @@ class VPPApiClient: # Create function for client side messages. if name in self.services: f = self.make_function(msg, i, self.services[name], do_async) + f_pack = self.make_pack_function(msg, i, self.services[name]) setattr(self._api, name, FuncWrapper(f)) + setattr(self._api, name + "_pack", FuncWrapper(f_pack)) else: - self.logger.debug( - 'No such message type or failed CRC checksum: %s', n) + self.logger.debug("No such message type or failed CRC checksum: %s", n) + + def get_api_definitions(self): + """get_api_definition. Bootstrap from the embedded memclnt.api.json file.""" + + # Bootstrap so we can call the get_api_json function + self._register_functions(do_async=False) + + r = self.api.get_api_json() + if r.retval != 0: + raise VPPApiError("Failed to load API definitions from VPP") - def connect_internal(self, name, msg_handler, chroot_prefix, rx_qlen, - do_async): - pfx = chroot_prefix.encode('utf-8') if chroot_prefix else None + # Process JSON + m, s = VPPApiJSONFiles.process_json_array_str(r.json) + self.messages.update(m) + self.services.update(s) - rv = self.transport.connect(name, pfx, - msg_handler, rx_qlen) + def connect_internal(self, name, msg_handler, chroot_prefix, rx_qlen, do_async): + pfx = chroot_prefix.encode("utf-8") if chroot_prefix else None + + rv = self.transport.connect(name, pfx, msg_handler, rx_qlen, do_async) if rv != 0: - raise VPPIOError(2, 'Connect failed') + raise VPPIOError(2, "Connect failed") self.vpp_dictionary_maxid = self.transport.msg_table_max_index() + + # Register functions + if self.bootstrapapi: + self.get_api_definitions() self._register_functions(do_async=do_async) # Initialise control ping - crc = self.messages['control_ping'].crc + crc = self.messages["control_ping"].crc self.control_ping_index = self.transport.get_msg_index( - ('control_ping' + '_' + crc[2:])) - self.control_ping_msgdef = self.messages['control_ping'] + ("control_ping" + "_" + crc[2:]) + ) + self.control_ping_msgdef = self.messages["control_ping"] + if self.async_thread: - self.event_thread = threading.Thread( - target=self.thread_msg_handler) + self.event_thread = threading.Thread(target=self.thread_msg_handler) self.event_thread.daemon = True self.event_thread.start() else: @@ -556,8 +651,9 @@ class VPPApiClient: client and server. """ msg_handler = self.transport.get_callback(do_async) - return self.connect_internal(name, msg_handler, chroot_prefix, rx_qlen, - do_async) + return self.connect_internal( + name, msg_handler, chroot_prefix, rx_qlen, do_async + ) def connect_sync(self, name, chroot_prefix=None, rx_qlen=32): """Attach to VPP in synchronous mode. Application must poll for events. @@ -568,8 +664,7 @@ class VPPApiClient: client and server. """ - return self.connect_internal(name, None, chroot_prefix, rx_qlen, - do_async=False) + return self.connect_internal(name, None, chroot_prefix, rx_qlen, do_async=False) def disconnect(self): """Detach from VPP.""" @@ -590,42 +685,44 @@ class VPPApiClient: # If we have a context, then use the context to find any # request waiting for a reply context = 0 - if hasattr(r, 'context') and r.context > 0: + if hasattr(r, "context") and r.context > 0: context = r.context if context == 0: # No context -> async notification that we feed to the callback self.message_queue.put_nowait(r) else: - raise VPPIOError(2, 'RPC reply message received in event handler') + raise VPPIOError(2, "RPC reply message received in event handler") def has_context(self, msg): if len(msg) < 10: return False - header = VPPType('header_with_context', [['u16', 'msgid'], - ['u32', 'client_index'], - ['u32', 'context']]) + header = VPPType( + "header_with_context", + [["u16", "msgid"], ["u32", "client_index"], ["u32", "context"]], + ) (i, ci, context), size = header.unpack(msg, 0) - if self.id_names[i] == 'rx_thread_exit': + + if self.id_names[i] == "rx_thread_exit": return # # Decode message and returns a tuple. # msgobj = self.id_msgdef[i] - if 'context' in msgobj.field_by_name and context >= 0: + if "context" in msgobj.field_by_name and context >= 0: return True return False def decode_incoming_msg(self, msg, no_type_conversion=False): if not msg: - logger.warning('vpp_api.read failed') + logger.warning("vpp_api.read failed") return (i, ci), size = self.header.unpack(msg, 0) - if self.id_names[i] == 'rx_thread_exit': + if self.id_names[i] == "rx_thread_exit": return # @@ -633,7 +730,7 @@ class VPPApiClient: # msgobj = self.id_msgdef[i] if not msgobj: - raise VPPIOError(2, 'Reply message undefined') + raise VPPIOError(2, "Reply message undefined") r, size = msgobj.unpack(msg, ntc=no_type_conversion) return r @@ -654,41 +751,39 @@ class VPPApiClient: def _control_ping(self, context): """Send a ping command.""" - self._call_vpp_async(self.control_ping_index, - self.control_ping_msgdef, - context=context) + self._call_vpp_async( + self.control_ping_index, self.control_ping_msgdef, context=context + ) def validate_args(self, msg, kwargs): d = set(kwargs.keys()) - set(msg.field_by_name.keys()) if d: - raise VPPValueError('Invalid argument {} to {}' - .format(list(d), msg.name)) + raise VPPValueError("Invalid argument {} to {}".format(list(d), msg.name)) def _add_stat(self, name, ms): if not name in self.stats: - self.stats[name] = {'max': ms, 'count': 1, 'avg': ms} + self.stats[name] = {"max": ms, "count": 1, "avg": ms} else: - if ms > self.stats[name]['max']: - self.stats[name]['max'] = ms - self.stats[name]['count'] += 1 - n = self.stats[name]['count'] - self.stats[name]['avg'] = self.stats[name]['avg'] * (n - 1) / n + ms / n + if ms > self.stats[name]["max"]: + self.stats[name]["max"] = ms + self.stats[name]["count"] += 1 + n = self.stats[name]["count"] + self.stats[name]["avg"] = self.stats[name]["avg"] * (n - 1) / n + ms / n def get_stats(self): - s = '\n=== API PAPI STATISTICS ===\n' - s += '{:<30} {:>4} {:>6} {:>6}\n'.format('message', 'cnt', 'avg', 'max') - for n in sorted(self.stats.items(), key=lambda v: v[1]['avg'], reverse=True): - s += '{:<30} {:>4} {:>6.2f} {:>6.2f}\n'.format(n[0], n[1]['count'], - n[1]['avg'], n[1]['max']) + s = "\n=== API PAPI STATISTICS ===\n" + s += "{:<30} {:>4} {:>6} {:>6}\n".format("message", "cnt", "avg", "max") + for n in sorted(self.stats.items(), key=lambda v: v[1]["avg"], reverse=True): + s += "{:<30} {:>4} {:>6.2f} {:>6.2f}\n".format( + n[0], n[1]["count"], n[1]["avg"], n[1]["max"] + ) return s def get_field_options(self, msg, fld_name): # when there is an option, the msgdef has 3 elements. # ['u32', 'ring_size', {'default': 1024}] for _def in self.messages[msg].msgdef: - if isinstance(_def, list) and \ - len(_def) == 3 and \ - _def[1] == fld_name: + if isinstance(_def, list) and len(_def) == 3 and _def[1] == fld_name: return _def[2] def _call_vpp(self, i, msgdef, service, **kwargs): @@ -707,25 +802,26 @@ class VPPApiClient: no response within the timeout window. """ ts = time.time() - if 'context' not in kwargs: + if "context" not in kwargs: context = self.get_context() - kwargs['context'] = context + kwargs["context"] = context else: - context = kwargs['context'] - kwargs['_vl_msg_id'] = i + context = kwargs["context"] + kwargs["_vl_msg_id"] = i - no_type_conversion = kwargs.pop('_no_type_conversion', False) - timeout = kwargs.pop('_timeout', None) + no_type_conversion = kwargs.pop("_no_type_conversion", False) + timeout = kwargs.pop("_timeout", None) try: if self.transport.socket_index: - kwargs['client_index'] = self.transport.socket_index + kwargs["client_index"] = self.transport.socket_index except AttributeError: pass self.validate_args(msgdef, kwargs) - s = 'Calling {}({})'.format(msgdef.name, - ','.join(['{!r}:{!r}'.format(k, v) for k, v in kwargs.items()])) + s = "Calling {}({})".format( + msgdef.name, ",".join(["{!r}:{!r}".format(k, v) for k, v in kwargs.items()]) + ) self.logger.debug(s) b = msgdef.pack(kwargs) @@ -733,17 +829,17 @@ class VPPApiClient: self.transport.write(b) - msgreply = service['reply'] - stream = True if 'stream' in service else False + msgreply = service["reply"] + stream = True if "stream" in service else False if stream: - if 'stream_msg' in service: + if "stream_msg" in service: # New service['reply'] = _reply and service['stream_message'] = _details - stream_message = service['stream_msg'] - modern =True + stream_message = service["stream_msg"] + modern = True else: # Old service['reply'] = _details stream_message = msgreply - msgreply = 'control_ping_reply' + msgreply = "control_ping_reply" modern = False # Send a ping after the request - we use its response # to detect that we have seen all results. @@ -751,22 +847,22 @@ class VPPApiClient: # Block until we get a reply. rl = [] - while (True): + while True: r = self.read_blocking(no_type_conversion, timeout) if r is None: - raise VPPIOError(2, 'VPP API client: read failed') + raise VPPIOError(2, "VPP API client: read failed") msgname = type(r).__name__ if context not in r or r.context == 0 or context != r.context: # Message being queued self.message_queue.put_nowait(r) continue if msgname != msgreply and (stream and (msgname != stream_message)): - print('REPLY MISMATCH', msgreply, msgname, stream_message, stream) + print("REPLY MISMATCH", msgreply, msgname, stream_message, stream) if not stream: rl = r break if msgname == msgreply: - if modern: # Return both reply and list + if modern: # Return both reply and list rl = r, rl break @@ -774,7 +870,7 @@ class VPPApiClient: self.transport.resume() - s = 'Return value: {!r}'.format(r) + s = "Return value: {!r}".format(r) if len(s) > 80: s = s[:80] + "..." self.logger.debug(s) @@ -795,22 +891,29 @@ class VPPApiClient: The returned context will help with assigning which call the reply belongs to. """ - if 'context' not in kwargs: + if "context" not in kwargs: context = self.get_context() - kwargs['context'] = context + kwargs["context"] = context else: - context = kwargs['context'] + context = kwargs["context"] try: if self.transport.socket_index: - kwargs['client_index'] = self.transport.socket_index + kwargs["client_index"] = self.transport.socket_index except AttributeError: - kwargs['client_index'] = 0 - kwargs['_vl_msg_id'] = i + kwargs["client_index"] = 0 + kwargs["_vl_msg_id"] = i b = msg.pack(kwargs) self.transport.write(b) return context + def _call_vpp_pack(self, i, msg, **kwargs): + """Given a message, return the binary representation.""" + kwargs["_vl_msg_id"] = i + kwargs["client_index"] = 0 + kwargs["context"] = 0 + return msg.pack(kwargs) + def read_blocking(self, no_type_conversion=False, timeout=None): """Get next received message from transport within timeout, decoded. @@ -891,26 +994,34 @@ class VPPApiClient: """Return VPPs API message table as name_crc dictionary, filtered by message name list.""" - replies = [self.services[n]['reply'] for n in msglist] + replies = [self.services[n]["reply"] for n in msglist] message_table_filtered = {} for name in msglist + replies: - for k,v in self.transport.message_table.items(): + for k, v in self.transport.message_table.items(): if k.startswith(name): message_table_filtered[k] = v break return message_table_filtered def __repr__(self): - return "<VPPApiClient apifiles=%s, testmode=%s, async_thread=%s, " \ - "logger=%s, read_timeout=%s, " \ - "server_address='%s'>" % ( - self._apifiles, self.testmode, self.async_thread, - self.logger, self.read_timeout, self.server_address) + return ( + "<VPPApiClient apifiles=%s, testmode=%s, async_thread=%s, " + "logger=%s, read_timeout=%s, " + "server_address='%s'>" + % ( + self._apifiles, + self.testmode, + self.async_thread, + self.logger, + self.read_timeout, + self.server_address, + ) + ) def details_iter(self, f, **kwargs): cursor = 0 while True: - kwargs['cursor'] = cursor + kwargs["cursor"] = cursor rv, details = f(**kwargs) for d in details: yield d diff --git a/src/vpp-api/python/vpp_papi/vpp_serializer.py b/src/vpp-api/python/vpp_papi/vpp_serializer.py index 644aeac65c6..707bb03b790 100644 --- a/src/vpp-api/python/vpp_papi/vpp_serializer.py +++ b/src/vpp-api/python/vpp_papi/vpp_serializer.py @@ -27,7 +27,7 @@ from . import vpp_format # logger = logging.getLogger('vpp_serializer') # logger.setLevel(logging.DEBUG) # -logger = logging.getLogger('vpp_papi.serializer') +logger = logging.getLogger("vpp_papi.serializer") def check(d): @@ -46,8 +46,7 @@ def conversion_required(data, field_type): def conversion_packer(data, field_type): t = type(data).__name__ - return types[field_type].pack(vpp_format. - conversion_table[field_type][t](data)) + return types[field_type].pack(vpp_format.conversion_table[field_type][t](data)) def conversion_unpacker(data, field_type): @@ -77,30 +76,33 @@ class Packer: return c._get_packer_with_options(f_type, options) except IndexError: raise VPPSerializerValueError( - "Options not supported for {}{} ({})". - format(f_type, types[f_type].__class__, - options)) + "Options not supported for {}{} ({})".format( + f_type, types[f_type].__class__, options + ) + ) class BaseTypes(Packer): def __init__(self, type, elements=0, options=None): self._type = type self._elements = elements - base_types = {'u8': '>B', - 'i8': '>b', - 'string': '>s', - 'u16': '>H', - 'i16': '>h', - 'u32': '>I', - 'i32': '>i', - 'u64': '>Q', - 'i64': '>q', - 'f64': '=d', - 'bool': '>?', - 'header': '>HI'} - - if elements > 0 and (type == 'u8' or type == 'string'): - self.packer = struct.Struct('>%ss' % elements) + base_types = { + "u8": ">B", + "i8": ">b", + "string": ">s", + "u16": ">H", + "i16": ">h", + "u32": ">I", + "i32": ">i", + "u64": ">Q", + "i64": ">q", + "f64": "=d", + "bool": ">?", + "header": ">HI", + } + + if elements > 0 and (type == "u8" or type == "string"): + self.packer = struct.Struct(">%ss" % elements) else: self.packer = struct.Struct(base_types[type]) self.size = self.packer.size @@ -108,8 +110,8 @@ class BaseTypes(Packer): def pack(self, data, kwargs=None): if data is None: # Default to zero if not specified - if self.options and 'default' in self.options: - data = self.options['default'] + if self.options and "default" in self.options: + data = self.options["default"] else: data = 0 return self.packer.pack(data) @@ -122,23 +124,27 @@ class BaseTypes(Packer): return BaseTypes(f_type, options=options) def __repr__(self): - return "BaseTypes(type=%s, elements=%s, options=%s)" % (self._type, - self._elements, - self.options) + return "BaseTypes(type=%s, elements=%s, options=%s)" % ( + self._type, + self._elements, + self.options, + ) class String(Packer): def __init__(self, name, num, options): self.name = name self.num = num - self.size = 1 - self.length_field_packer = BaseTypes('u32') - self.limit = options['limit'] if 'limit' in options else num + self.size = num if num else 1 + self.length_field_packer = BaseTypes("u32") + self.limit = options["limit"] if "limit" in options else num self.fixed = True if num else False if self.fixed and not self.limit: raise VPPSerializerValueError( - "Invalid combination for: {}, {} fixed:{} limit:{}". - format(name, options, self.fixed, self.limit)) + "Invalid combination for: {}, {} fixed:{} limit:{}".format( + name, options, self.fixed, self.limit + ) + ) def pack(self, list, kwargs=None): if not list: @@ -147,34 +153,42 @@ class String(Packer): return self.length_field_packer.pack(0) + b"" if self.limit and len(list) > self.limit - 1: raise VPPSerializerValueError( - "Invalid argument length for: {}, {} maximum {}". - format(list, len(list), self.limit - 1)) + "Invalid argument length for: {}, {} maximum {}".format( + list, len(list), self.limit - 1 + ) + ) if self.fixed: - return list.encode('ascii').ljust(self.limit, b'\x00') - return self.length_field_packer.pack(len(list)) + list.encode('ascii') + return list.encode("ascii").ljust(self.limit, b"\x00") + return self.length_field_packer.pack(len(list)) + list.encode("ascii") def unpack(self, data, offset=0, result=None, ntc=False): if self.fixed: - p = BaseTypes('u8', self.num) + p = BaseTypes("u8", self.num) s = p.unpack(data, offset) - s2 = s[0].split(b'\0', 1)[0] - return (s2.decode('ascii'), self.num) + s2 = s[0].split(b"\0", 1)[0] + return (s2.decode("ascii"), self.num) - length, length_field_size = self.length_field_packer.unpack(data, - offset) + length, length_field_size = self.length_field_packer.unpack(data, offset) if length == 0: - return '', 0 - p = BaseTypes('u8', length) + return "", 0 + p = BaseTypes("u8", length) x, size = p.unpack(data, offset + length_field_size) - return (x.decode('ascii', errors='replace'), size + length_field_size) - - -types = {'u8': BaseTypes('u8'), 'i8': BaseTypes('i8'), - 'u16': BaseTypes('u16'), 'i16': BaseTypes('i16'), - 'u32': BaseTypes('u32'), 'i32': BaseTypes('i32'), - 'u64': BaseTypes('u64'), 'i64': BaseTypes('i64'), - 'f64': BaseTypes('f64'), - 'bool': BaseTypes('bool'), 'string': String} + return (x.decode("ascii", errors="replace"), size + length_field_size) + + +types = { + "u8": BaseTypes("u8"), + "i8": BaseTypes("i8"), + "u16": BaseTypes("u16"), + "i16": BaseTypes("i16"), + "u32": BaseTypes("u32"), + "i32": BaseTypes("i32"), + "u64": BaseTypes("u64"), + "i64": BaseTypes("i64"), + "f64": BaseTypes("f64"), + "bool": BaseTypes("bool"), + "string": String, +} class_types = {} @@ -202,32 +216,34 @@ class FixedList_u8(Packer): """Packs a fixed length bytestring. Left-pads with zeros if input data is too short.""" if not data: - return b'\x00' * self.size + return b"\x00" * self.size if len(data) > self.num: raise VPPSerializerValueError( 'Fixed list length error for "{}", got: {}' - ' expected: {}' - .format(self.name, len(data), self.num)) + " expected: {}".format(self.name, len(data), self.num) + ) try: return self.packer.pack(data) except struct.error: raise VPPSerializerValueError( - 'Packing failed for "{}" {}' - .format(self.name, kwargs)) + 'Packing failed for "{}" {}'.format(self.name, kwargs) + ) def unpack(self, data, offset=0, result=None, ntc=False): if len(data[offset:]) < self.num: raise VPPSerializerValueError( 'Invalid array length for "{}" got {}' - ' expected {}' - .format(self.name, len(data[offset:]), self.num)) + " expected {}".format(self.name, len(data[offset:]), self.num) + ) return self.packer.unpack(data, offset) def __repr__(self): return "FixedList_u8(name=%s, field_type=%s, num=%s)" % ( - self.name, self.field_type, self.num + self.name, + self.field_type, + self.num, ) @@ -242,12 +258,14 @@ class FixedList(Packer): def pack(self, list, kwargs): if len(list) != self.num: raise VPPSerializerValueError( - 'Fixed list length error, got: {} expected: {}' - .format(len(list), self.num)) - b = bytes() + "Fixed list length error, got: {} expected: {}".format( + len(list), self.num + ) + ) + b = bytearray() for e in list: b += self.packer.pack(e) - return b + return bytes(b) def unpack(self, data, offset=0, result=None, ntc=False): # Return a list of arguments @@ -262,7 +280,10 @@ class FixedList(Packer): def __repr__(self): return "FixedList(name=%s, field_type=%s, num=%s)" % ( - self.name, self.field_type, self.num) + self.name, + self.field_type, + self.num, + ) class VLAList(Packer): @@ -279,29 +300,30 @@ class VLAList(Packer): return b"" if len(lst) != kwargs[self.length_field]: raise VPPSerializerValueError( - 'Variable length error, got: {} expected: {}' - .format(len(lst), kwargs[self.length_field])) - + "Variable length error, got: {} expected: {}".format( + len(lst), kwargs[self.length_field] + ) + ) # u8 array - if self.packer.size == 1: + if self.packer.size == 1 and self.field_type == "u8": if isinstance(lst, list): - return b''.join(lst) + return b"".join(lst) return bytes(lst) - b = bytes() + b = bytearray() for e in lst: b += self.packer.pack(e) - return b + return bytes(b) def unpack(self, data, offset=0, result=None, ntc=False): # Return a list of arguments total = 0 # u8 array - if self.packer.size == 1: + if self.packer.size == 1 and self.field_type == "u8": if result[self.index] == 0: - return b'', 0 - p = BaseTypes('u8', result[self.index]) + return b"", 0 + p = BaseTypes("u8", result[self.index]) return p.unpack(data, offset, ntc=ntc) r = [] @@ -313,10 +335,12 @@ class VLAList(Packer): return r, total def __repr__(self): - return "VLAList(name=%s, field_type=%s, " \ - "len_field_name=%s, index=%s)" % ( - self.name, self.field_type, self.length_field, self.index - ) + return "VLAList(name=%s, field_type=%s, " "len_field_name=%s, index=%s)" % ( + self.name, + self.field_type, + self.length_field, + self.index, + ) class VLAList_legacy(Packer): @@ -330,17 +354,18 @@ class VLAList_legacy(Packer): if self.packer.size == 1: return bytes(list) - b = bytes() + b = bytearray() for e in list: b += self.packer.pack(e) - return b + return bytes(b) def unpack(self, data, offset=0, result=None, ntc=False): total = 0 # Return a list of arguments if (len(data) - offset) % self.packer.size: raise VPPSerializerValueError( - 'Legacy Variable Length Array length mismatch.') + "Legacy Variable Length Array length mismatch." + ) elements = int((len(data) - offset) / self.packer.size) r = [] for e in range(elements): @@ -351,9 +376,7 @@ class VLAList_legacy(Packer): return r, total def __repr__(self): - return "VLAList_legacy(name=%s, field_type=%s)" % ( - self.name, self.field_type - ) + return "VLAList_legacy(name=%s, field_type=%s)" % (self.name, self.field_type) # Will change to IntEnum after 21.04 release @@ -361,16 +384,16 @@ class VPPEnumType(Packer): output_class = IntFlag def __init__(self, name, msgdef, options=None): - self.size = types['u32'].size + self.size = types["u32"].size self.name = name - self.enumtype = 'u32' + self.enumtype = "u32" self.msgdef = msgdef e_hash = {} for f in msgdef: - if type(f) is dict and 'enumtype' in f: - if f['enumtype'] != 'u32': - self.size = types[f['enumtype']].size - self.enumtype = f['enumtype'] + if type(f) is dict and "enumtype" in f: + if f["enumtype"] != "u32": + self.size = types[f["enumtype"]].size + self.enumtype = f["enumtype"] continue ename, evalue = f e_hash[ename] = evalue @@ -387,8 +410,8 @@ class VPPEnumType(Packer): def pack(self, data, kwargs=None): if data is None: # Default to zero if not specified - if self.options and 'default' in self.options: - data = self.options['default'] + if self.options and "default" in self.options: + data = self.options["default"] else: data = 0 @@ -404,7 +427,10 @@ class VPPEnumType(Packer): def __repr__(self): return "%s(name=%s, msgdef=%s, options=%s)" % ( - self.__class__.__name__, self.name, self.msgdef, self.options + self.__class__.__name__, + self.name, + self.msgdef, + self.options, ) @@ -424,14 +450,13 @@ class VPPUnionType(Packer): fields = [] self.packers = collections.OrderedDict() for i, f in enumerate(msgdef): - if type(f) is dict and 'crc' in f: - self.crc = f['crc'] + if type(f) is dict and "crc" in f: + self.crc = f["crc"] continue f_type, f_name = f if f_type not in types: - logger.debug('Unknown union type {}'.format(f_type)) - raise VPPSerializerValueError( - 'Unknown message type {}'.format(f_type)) + logger.debug("Unknown union type {}".format(f_type)) + raise VPPSerializerValueError("Unknown message type {}".format(f_type)) fields.append(f_name) size = types[f_type].size self.packers[f_name] = types[f_type] @@ -445,14 +470,14 @@ class VPPUnionType(Packer): # Union of variable length? def pack(self, data, kwargs=None): if not data: - return b'\x00' * self.size + return b"\x00" * self.size for k, v in data.items(): logger.debug("Key: {} Value: {}".format(k, v)) b = self.packers[k].pack(v, kwargs) break r = bytearray(self.size) - r[:len(b)] = b + r[: len(b)] = b return r def unpack(self, data, offset=0, result=None, ntc=False): @@ -466,25 +491,24 @@ class VPPUnionType(Packer): return self.tuple._make(r), maxsize def __repr__(self): - return"VPPUnionType(name=%s, msgdef=%r)" % (self.name, self.msgdef) + return "VPPUnionType(name=%s, msgdef=%r)" % (self.name, self.msgdef) class VPPTypeAlias(Packer): def __init__(self, name, msgdef, options=None): self.name = name self.msgdef = msgdef - t = vpp_get_type(msgdef['type']) + t = vpp_get_type(msgdef["type"]) if not t: - raise ValueError('No such type: {}'.format(msgdef['type'])) - if 'length' in msgdef: - if msgdef['length'] == 0: + raise ValueError("No such type: {}".format(msgdef["type"])) + if "length" in msgdef: + if msgdef["length"] == 0: raise ValueError() - if msgdef['type'] == 'u8': - self.packer = FixedList_u8(name, msgdef['type'], - msgdef['length']) + if msgdef["type"] == "u8": + self.packer = FixedList_u8(name, msgdef["type"], msgdef["length"]) self.size = self.packer.size else: - self.packer = FixedList(name, msgdef['type'], msgdef['length']) + self.packer = FixedList(name, msgdef["type"], msgdef["length"]) else: self.packer = t self.size = t.size @@ -498,11 +522,11 @@ class VPPTypeAlias(Packer): try: return conversion_packer(data, self.name) # Python 2 and 3 raises different exceptions from inet_pton - except(OSError, socket.error, TypeError): + except (OSError, socket.error, TypeError): pass if data is None: # Default to zero if not specified - if self.options and 'default' in self.options: - data = self.options['default'] + if self.options and "default" in self.options: + data = self.options["default"] else: data = 0 @@ -525,7 +549,10 @@ class VPPTypeAlias(Packer): def __repr__(self): return "VPPTypeAlias(name=%s, msgdef=%s, options=%s)" % ( - self.name, self.msgdef, self.options) + self.name, + self.msgdef, + self.options, + ) class VPPType(Packer): @@ -539,17 +566,16 @@ class VPPType(Packer): self.field_by_name = {} size = 0 for i, f in enumerate(msgdef): - if type(f) is dict and 'crc' in f: - self.crc = f['crc'] + if type(f) is dict and "crc" in f: + self.crc = f["crc"] continue f_type, f_name = f[:2] self.fields.append(f_name) self.field_by_name[f_name] = None self.fieldtypes.append(f_type) if f_type not in types: - logger.debug('Unknown type {}'.format(f_type)) - raise VPPSerializerValueError( - 'Unknown message type {}'.format(f_type)) + logger.debug("Unknown type {}".format(f_type)) + raise VPPSerializerValueError("Unknown message type {}".format(f_type)) fieldlen = len(f) options = [x for x in f if type(x) is dict] @@ -561,16 +587,16 @@ class VPPType(Packer): if fieldlen == 3: # list list_elements = f[2] if list_elements == 0: - if f_type == 'string': + if f_type == "string": p = String(f_name, 0, self.options) else: p = VLAList_legacy(f_name, f_type) self.packers.append(p) - elif f_type == 'u8': + elif f_type == "u8": p = FixedList_u8(f_name, f_type, list_elements) self.packers.append(p) size += p.size - elif f_type == 'string': + elif f_type == "string": p = String(f_name, list_elements, self.options) self.packers.append(p) size += p.size @@ -584,14 +610,13 @@ class VPPType(Packer): self.packers.append(p) else: # default support for types that decay to basetype - if 'default' in self.options: + if "default" in self.options: p = self.get_packer_with_options(f_type, self.options) else: p = types[f_type] self.packers.append(p) size += p.size - self.size = size self.tuple = collections.namedtuple(name, self.fields, rename=True) types[name] = self @@ -600,7 +625,7 @@ class VPPType(Packer): def pack(self, data, kwargs=None): if not kwargs: kwargs = data - b = bytes() + b = bytearray() # Try one of the format functions if data and conversion_required(data, self.name): @@ -609,8 +634,8 @@ class VPPType(Packer): for i, a in enumerate(self.fields): if data and type(data) is not dict and a not in data: raise VPPSerializerValueError( - "Invalid argument: {} expected {}.{}". - format(data, self.name, a)) + "Invalid argument: {} expected {}.{}".format(data, self.name, a) + ) # Defaulting to zero. if not data or a not in data: # Default to 0 @@ -619,12 +644,17 @@ class VPPType(Packer): else: arg = data[a] kwarg = kwargs[a] if a in kwargs else None - if isinstance(self.packers[i], VPPType): - b += self.packers[i].pack(arg, kwarg) - else: - b += self.packers[i].pack(arg, kwargs) + try: + if isinstance(self.packers[i], VPPType): + b += self.packers[i].pack(arg, kwarg) + else: + b += self.packers[i].pack(arg, kwargs) + except Exception as e: + raise VPPSerializerValueError( + f"Exception while packing {data} for {self.name}.{a}." + ) from e - return b + return bytes(b) def unpack(self, data, offset=0, result=None, ntc=False): # Return a list of arguments @@ -651,7 +681,9 @@ class VPPType(Packer): def __repr__(self): return "%s(name=%s, msgdef=%s)" % ( - self.__class__.__name__, self.name, self.msgdef + self.__class__.__name__, + self.name, + self.msgdef, ) diff --git a/src/vpp-api/python/vpp_papi/vpp_stats.py b/src/vpp-api/python/vpp_papi/vpp_stats.py index b9b23b52d66..aa9ff85b3c7 100755 --- a/src/vpp-api/python/vpp_papi/vpp_stats.py +++ b/src/vpp-api/python/vpp_papi/vpp_stats.py @@ -14,7 +14,7 @@ # limitations under the License. # -''' +""" This module implement Python access to the VPP statistics segment. It accesses the data structures directly in shared memory. VPP uses optimistic locking, so data structures may change underneath @@ -39,7 +39,7 @@ stat['/if/rx'][:, 1].sum_packets() - returns the sum of packet counters for interface 1 on all threads stat['/if/rx-miss'][:, 1].sum() - returns the sum of packet counters for interface 1 on all threads for simple counters -''' +""" import os import socket @@ -50,31 +50,36 @@ import time import unittest import re + def recv_fd(sock): - '''Get file descriptor for memory map''' - fds = array.array("i") # Array of ints - _, ancdata, _, _ = sock.recvmsg(0, socket.CMSG_LEN(4)) + """Get file descriptor for memory map""" + fds = array.array("i") # Array of ints + _, ancdata, _, _ = sock.recvmsg(0, socket.CMSG_SPACE(4)) for cmsg_level, cmsg_type, cmsg_data in ancdata: if cmsg_level == socket.SOL_SOCKET and cmsg_type == socket.SCM_RIGHTS: - fds.frombytes(cmsg_data[:len(cmsg_data) - (len(cmsg_data) % fds.itemsize)]) + fds.frombytes(cmsg_data[: len(cmsg_data) - (len(cmsg_data) % fds.itemsize)]) return list(fds)[0] -VEC_LEN_FMT = Struct('I') + +VEC_LEN_FMT = Struct("I") + + def get_vec_len(stats, vector_offset): - '''Equivalent to VPP vec_len()''' + """Equivalent to VPP vec_len()""" return VEC_LEN_FMT.unpack_from(stats.statseg, vector_offset - 8)[0] + def get_string(stats, ptr): - '''Get a string from a VPP vector''' + """Get a string from a VPP vector""" namevector = ptr - stats.base namevectorlen = get_vec_len(stats, namevector) if namevector + namevectorlen >= stats.size: - raise IOError('String overruns stats segment') - return stats.statseg[namevector:namevector+namevectorlen-1].decode('ascii') + raise IOError("String overruns stats segment") + return stats.statseg[namevector : namevector + namevectorlen - 1].decode("ascii") class StatsVector: - '''A class representing a VPP vector''' + """A class representing a VPP vector""" def __init__(self, stats, ptr, fmt): self.vec_start = ptr - stats.base @@ -86,28 +91,35 @@ class StatsVector: self.stats = stats if self.vec_start + self.vec_len * self.elementsize >= stats.size: - raise IOError('Vector overruns stats segment') + raise IOError("Vector overruns stats segment") def __iter__(self): with self.stats.lock: - return self.struct.iter_unpack(self.statseg[self.vec_start:self.vec_start + - self.elementsize*self.vec_len]) + return self.struct.iter_unpack( + self.statseg[ + self.vec_start : self.vec_start + self.elementsize * self.vec_len + ] + ) def __getitem__(self, index): if index > self.vec_len: - raise IOError('Index beyond end of vector') + raise IOError("Index beyond end of vector") with self.stats.lock: if self.fmtlen == 1: - return self.struct.unpack_from(self.statseg, self.vec_start + - (index * self.elementsize))[0] - return self.struct.unpack_from(self.statseg, self.vec_start + - (index * self.elementsize)) + return self.struct.unpack_from( + self.statseg, self.vec_start + (index * self.elementsize) + )[0] + return self.struct.unpack_from( + self.statseg, self.vec_start + (index * self.elementsize) + ) + + +class VPPStats: + """Main class implementing Python access to the VPP statistics segment""" -class VPPStats(): - '''Main class implementing Python access to the VPP statistics segment''' # pylint: disable=too-many-instance-attributes - shared_headerfmt = Struct('QPQQPP') - default_socketname = '/run/vpp/stats.sock' + shared_headerfmt = Struct("QPQQPP") + default_socketname = "/run/vpp/stats.sock" def __init__(self, socketname=default_socketname, timeout=10): self.socketname = socketname @@ -117,89 +129,87 @@ class VPPStats(): self.connected = False self.size = 0 self.last_epoch = 0 - self.error_vectors = 0 self.statseg = 0 def connect(self): - '''Connect to stats segment''' + """Connect to stats segment""" if self.connected: return sock = socket.socket(socket.AF_UNIX, socket.SOCK_SEQPACKET) + + # Our connect races the corresponding recv_fds call in VPP, if we beat + # VPP then we will try (unsuccessfully) to receive file descriptors and + # will have gone away before VPP can respond to our connect. A short + # timeout here stops this error occurring. + sock.settimeout(1) sock.connect(self.socketname) mfd = recv_fd(sock) sock.close() stat_result = os.fstat(mfd) - self.statseg = mmap.mmap(mfd, stat_result.st_size, mmap.PROT_READ, mmap.MAP_SHARED) + self.statseg = mmap.mmap( + mfd, stat_result.st_size, mmap.PROT_READ, mmap.MAP_SHARED + ) os.close(mfd) self.size = stat_result.st_size if self.version != 2: - raise Exception('Incompatbile stat segment version {}' - .format(self.version)) + raise Exception("Incompatbile stat segment version {}".format(self.version)) self.refresh() self.connected = True def disconnect(self): - '''Disconnect from stats segment''' + """Disconnect from stats segment""" if self.connected: self.statseg.close() self.connected = False @property def version(self): - '''Get version of stats segment''' + """Get version of stats segment""" return self.shared_headerfmt.unpack_from(self.statseg)[0] @property def base(self): - '''Get base pointer of stats segment''' + """Get base pointer of stats segment""" return self.shared_headerfmt.unpack_from(self.statseg)[1] @property def epoch(self): - '''Get current epoch value from stats segment''' + """Get current epoch value from stats segment""" return self.shared_headerfmt.unpack_from(self.statseg)[2] @property def in_progress(self): - '''Get value of in_progress from stats segment''' + """Get value of in_progress from stats segment""" return self.shared_headerfmt.unpack_from(self.statseg)[3] @property def directory_vector(self): - '''Get pointer of directory vector''' + """Get pointer of directory vector""" return self.shared_headerfmt.unpack_from(self.statseg)[4] - @property - def error_vector(self): - '''Get pointer of error vector''' - return self.shared_headerfmt.unpack_from(self.statseg)[5] - - elementfmt = 'IQ128s' + elementfmt = "IQ128s" def refresh(self, blocking=True): - '''Refresh directory vector cache (epoch changed)''' + """Refresh directory vector cache (epoch changed)""" directory = {} directory_by_idx = {} while True: try: with self.lock: self.last_epoch = self.epoch - for i, direntry in enumerate(StatsVector(self, self.directory_vector, self.elementfmt)): - path_raw = direntry[2].find(b'\x00') - path = direntry[2][:path_raw].decode('ascii') + for i, direntry in enumerate( + StatsVector(self, self.directory_vector, self.elementfmt) + ): + path_raw = direntry[2].find(b"\x00") + path = direntry[2][:path_raw].decode("ascii") directory[path] = StatsEntry(direntry[0], direntry[1]) directory_by_idx[i] = path self.directory = directory self.directory_by_idx = directory_by_idx - - # Cache the error index vectors - self.error_vectors = [] - for threads in StatsVector(self, self.error_vector, 'P'): - self.error_vectors.append(StatsVector(self, threads[0], 'Q')) return except IOError: if not blocking: @@ -222,78 +232,66 @@ class VPPStats(): return iter(self.directory.items()) def set_errors(self, blocking=True): - '''Return dictionary of error counters > 0''' + """Return dictionary of error counters > 0""" if not self.connected: self.connect() - errors = {k:v for k, v in self.directory.items() if k.startswith("/err/")} + errors = {k: v for k, v in self.directory.items() if k.startswith("/err/")} result = {} - while True: + for k in errors: try: - if self.last_epoch != self.epoch: - self.refresh(blocking) - with self.lock: - for k, entry in errors.items(): - total = 0 - i = entry.value - for per_thread in self.error_vectors: - total += per_thread[i] - if total: - result[k] = total - return result - except IOError: - if not blocking: - raise + total = self[k].sum() + if total: + result[k] = total + except KeyError: + pass + return result def set_errors_str(self, blocking=True): - '''Return all errors counters > 0 pretty printed''' - error_string = ['ERRORS:'] + """Return all errors counters > 0 pretty printed""" + error_string = ["ERRORS:"] error_counters = self.set_errors(blocking) for k in sorted(error_counters): - error_string.append('{:<60}{:>10}'.format(k, error_counters[k])) - return '%s\n' % '\n'.join(error_string) + error_string.append("{:<60}{:>10}".format(k, error_counters[k])) + return "%s\n" % "\n".join(error_string) def get_counter(self, name, blocking=True): - '''Alternative call to __getitem__''' + """Alternative call to __getitem__""" return self.__getitem__(name, blocking) def get_err_counter(self, name, blocking=True): - '''Return a single value (sum of all threads)''' - if not self.connected: - self.connect() - if name.startswith("/err/"): - while True: - try: - if self.last_epoch != self.epoch: - self.refresh(blocking) - with self.lock: - return sum(self.directory[name].get_counter(self)) - except IOError: - if not blocking: - raise + """Alternative call to __getitem__""" + return self.__getitem__(name, blocking).sum() def ls(self, patterns): - '''Returns list of counters matching pattern''' + """Returns list of counters matching pattern""" # pylint: disable=invalid-name if not self.connected: self.connect() if not isinstance(patterns, list): patterns = [patterns] regex = [re.compile(i) for i in patterns] - return [k for k, v in self.directory.items() - if any(re.match(pattern, k) for pattern in regex)] + if self.last_epoch != self.epoch: + self.refresh() + + return [ + k + for k, v in self.directory.items() + if any(re.match(pattern, k) for pattern in regex) + ] def dump(self, counters, blocking=True): - '''Given a list of counters return a dictionary of results''' + """Given a list of counters return a dictionary of results""" if not self.connected: self.connect() result = {} for cnt in counters: - result[cnt] = self.__getitem__(cnt,blocking) + result[cnt] = self.__getitem__(cnt, blocking) return result -class StatsLock(): - '''Stat segment optimistic locking''' + +class StatsLock: + """Stat segment optimistic locking""" def __init__(self, stats): self.stats = stats @@ -308,7 +306,7 @@ class StatsLock(): self.release() def acquire(self, blocking=True, timeout=-1): - '''Acquire the lock. Await in progress to go false. Record epoch.''' + """Acquire the lock. Await in progress to go false. Record epoch.""" self.epoch = self.stats.epoch if timeout > 0: start = time.monotonic() @@ -321,46 +319,49 @@ class StatsLock(): return True def release(self): - '''Check if data read while locked is valid''' + """Check if data read while locked is valid""" if self.stats.in_progress or self.stats.epoch != self.epoch: - raise IOError('Optimistic lock failed, retry') + raise IOError("Optimistic lock failed, retry") def locked(self): - '''Not used''' + """Not used""" class StatsCombinedList(list): - '''Column slicing for Combined counters list''' + """Column slicing for Combined counters list""" def __getitem__(self, item): - '''Supports partial numpy style 2d support. Slice by column [:,1]''' + """Supports partial numpy style 2d support. Slice by column [:,1]""" if isinstance(item, int): return list.__getitem__(self, item) return CombinedList([row[item[1]] for row in self]) + class CombinedList(list): - '''Combined Counters 2-dimensional by thread by index of packets/octets''' + """Combined Counters 2-dimensional by thread by index of packets/octets""" def packets(self): - '''Return column (2nd dimension). Packets for all threads''' + """Return column (2nd dimension). Packets for all threads""" return [pair[0] for pair in self] def octets(self): - '''Return column (2nd dimension). Octets for all threads''' + """Return column (2nd dimension). Octets for all threads""" return [pair[1] for pair in self] def sum_packets(self): - '''Return column (2nd dimension). Sum of all packets for all threads''' + """Return column (2nd dimension). Sum of all packets for all threads""" return sum(self.packets()) def sum_octets(self): - '''Return column (2nd dimension). Sum of all octets for all threads''' + """Return column (2nd dimension). Sum of all octets for all threads""" return sum(self.octets()) + class StatsTuple(tuple): - '''A Combined vector tuple (packets, octets)''' + """A Combined vector tuple (packets, octets)""" + def __init__(self, data): - self.dictionary = {'packets': data[0], 'bytes': data[1]} + self.dictionary = {"packets": data[0], "bytes": data[1]} super().__init__() def __repr__(self): @@ -369,28 +370,32 @@ class StatsTuple(tuple): def __getitem__(self, item): if isinstance(item, int): return tuple.__getitem__(self, item) - if item == 'packets': + if item == "packets": return tuple.__getitem__(self, 0) return tuple.__getitem__(self, 1) + class StatsSimpleList(list): - '''Simple Counters 2-dimensional by thread by index of packets''' + """Simple Counters 2-dimensional by thread by index of packets""" def __getitem__(self, item): - '''Supports partial numpy style 2d support. Slice by column [:,1]''' + """Supports partial numpy style 2d support. Slice by column [:,1]""" if isinstance(item, int): return list.__getitem__(self, item) return SimpleList([row[item[1]] for row in self]) + class SimpleList(list): - '''Simple counter''' + """Simple counter""" def sum(self): - '''Sum the vector''' + """Sum the vector""" return sum(self) -class StatsEntry(): - '''An individual stats entry''' + +class StatsEntry: + """An individual stats entry""" + # pylint: disable=unused-argument,no-self-use def __init__(self, stattype, statvalue): @@ -404,140 +409,135 @@ class StatsEntry(): elif stattype == 3: self.function = self.combined elif stattype == 4: - self.function = self.error - elif stattype == 5: self.function = self.name - elif stattype == 7: + elif stattype == 6: self.function = self.symlink else: self.function = self.illegal def illegal(self, stats): - '''Invalid or unknown counter type''' + """Invalid or unknown counter type""" return None def scalar(self, stats): - '''Scalar counter''' + """Scalar counter""" return self.value def simple(self, stats): - '''Simple counter''' + """Simple counter""" counter = StatsSimpleList() - for threads in StatsVector(stats, self.value, 'P'): - clist = [v[0] for v in StatsVector(stats, threads[0], 'Q')] + for threads in StatsVector(stats, self.value, "P"): + clist = [v[0] for v in StatsVector(stats, threads[0], "Q")] counter.append(clist) return counter def combined(self, stats): - '''Combined counter''' + """Combined counter""" counter = StatsCombinedList() - for threads in StatsVector(stats, self.value, 'P'): - clist = [StatsTuple(cnt) for cnt in StatsVector(stats, threads[0], 'QQ')] + for threads in StatsVector(stats, self.value, "P"): + clist = [StatsTuple(cnt) for cnt in StatsVector(stats, threads[0], "QQ")] counter.append(clist) return counter - def error(self, stats): - '''Error counter''' - counter = SimpleList() - for clist in stats.error_vectors: - counter.append(clist[self.value]) - return counter - def name(self, stats): - '''Name counter''' + """Name counter""" counter = [] - for name in StatsVector(stats, self.value, 'P'): + for name in StatsVector(stats, self.value, "P"): if name[0]: counter.append(get_string(stats, name[0])) return counter - SYMLINK_FMT1 = Struct('II') - SYMLINK_FMT2 = Struct('Q') + SYMLINK_FMT1 = Struct("II") + SYMLINK_FMT2 = Struct("Q") + def symlink(self, stats): - '''Symlink counter''' + """Symlink counter""" b = self.SYMLINK_FMT2.pack(self.value) index1, index2 = self.SYMLINK_FMT1.unpack(b) name = stats.directory_by_idx[index1] - return stats[name][:,index2] + return stats[name][:, index2] def get_counter(self, stats): - '''Return a list of counters''' + """Return a list of counters""" if stats: return self.function(stats) + class TestStats(unittest.TestCase): - '''Basic statseg tests''' + """Basic statseg tests""" def setUp(self): - '''Connect to statseg''' + """Connect to statseg""" self.stat = VPPStats() self.stat.connect() self.profile = cProfile.Profile() self.profile.enable() def tearDown(self): - '''Disconnect from statseg''' + """Disconnect from statseg""" self.stat.disconnect() profile = Stats(self.profile) profile.strip_dirs() - profile.sort_stats('cumtime') + profile.sort_stats("cumtime") profile.print_stats() print("\n--->>>") def test_counters(self): - '''Test access to statseg''' - - print('/err/abf-input-ip4/missed', self.stat['/err/abf-input-ip4/missed']) - print('/sys/heartbeat', self.stat['/sys/heartbeat']) - print('/if/names', self.stat['/if/names']) - print('/if/rx-miss', self.stat['/if/rx-miss']) - print('/if/rx-miss', self.stat['/if/rx-miss'][1]) - print('/nat44-ed/out2in/slowpath/drops', self.stat['/nat44-ed/out2in/slowpath/drops']) - print('Set Errors', self.stat.set_errors()) + """Test access to statseg""" + + print("/err/abf-input-ip4/missed", self.stat["/err/abf-input-ip4/missed"]) + print("/sys/heartbeat", self.stat["/sys/heartbeat"]) + print("/if/names", self.stat["/if/names"]) + print("/if/rx-miss", self.stat["/if/rx-miss"]) + print("/if/rx-miss", self.stat["/if/rx-miss"][1]) + print( + "/nat44-ed/out2in/slowpath/drops", + self.stat["/nat44-ed/out2in/slowpath/drops"], + ) with self.assertRaises(KeyError): - print('NO SUCH COUNTER', self.stat['foobar']) - print('/if/rx', self.stat.get_counter('/if/rx')) - print('/err/ethernet-input/no error', - self.stat.get_err_counter('/err/ethernet-input/no error')) + print("NO SUCH COUNTER", self.stat["foobar"]) + print("/if/rx", self.stat.get_counter("/if/rx")) + print( + "/err/ethernet-input/no_error", + self.stat.get_counter("/err/ethernet-input/no_error"), + ) def test_column(self): - '''Test column slicing''' - - print('/if/rx-miss', self.stat['/if/rx-miss']) - print('/if/rx', self.stat['/if/rx']) # All interfaces for thread #1 - print('/if/rx thread #1', self.stat['/if/rx'][0]) # All interfaces for thread #1 - print('/if/rx thread #1, interface #1', - self.stat['/if/rx'][0][1]) # All interfaces for thread #1 - print('/if/rx if_index #1', self.stat['/if/rx'][:, 1]) - print('/if/rx if_index #1 packets', self.stat['/if/rx'][:, 1].packets()) - print('/if/rx if_index #1 packets', self.stat['/if/rx'][:, 1].sum_packets()) - print('/if/rx if_index #1 packets', self.stat['/if/rx'][:, 1].octets()) - print('/if/rx-miss', self.stat['/if/rx-miss']) - print('/if/rx-miss if_index #1 packets', self.stat['/if/rx-miss'][:, 1].sum()) - print('/if/rx if_index #1 packets', self.stat['/if/rx'][0][1]['packets']) - - def test_error(self): - '''Test the error vector''' - - print('/err/ethernet-input', self.stat['/err/ethernet-input/no error']) - print('/err/nat44-ei-ha/pkts-processed', self.stat['/err/nat44-ei-ha/pkts-processed']) - print('/err/ethernet-input', self.stat.get_err_counter('/err/ethernet-input/no error')) - print('/err/ethernet-input', self.stat['/err/ethernet-input/no error'].sum()) + """Test column slicing""" + + print("/if/rx-miss", self.stat["/if/rx-miss"]) + print("/if/rx", self.stat["/if/rx"]) # All interfaces for thread #1 + print( + "/if/rx thread #1", self.stat["/if/rx"][0] + ) # All interfaces for thread #1 + print( + "/if/rx thread #1, interface #1", self.stat["/if/rx"][0][1] + ) # All interfaces for thread #1 + print("/if/rx if_index #1", self.stat["/if/rx"][:, 1]) + print("/if/rx if_index #1 packets", self.stat["/if/rx"][:, 1].packets()) + print("/if/rx if_index #1 packets", self.stat["/if/rx"][:, 1].sum_packets()) + print("/if/rx if_index #1 packets", self.stat["/if/rx"][:, 1].octets()) + print("/if/rx-miss", self.stat["/if/rx-miss"]) + print("/if/rx-miss if_index #1 packets", self.stat["/if/rx-miss"][:, 1].sum()) + print("/if/rx if_index #1 packets", self.stat["/if/rx"][0][1]["packets"]) def test_nat44(self): - '''Test the nat counters''' + """Test the nat counters""" - print('/nat44-ei/ha/del-event-recv', self.stat['/nat44-ei/ha/del-event-recv']) - print('/err/nat44-ei-ha/pkts-processed', self.stat['/err/nat44-ei-ha/pkts-processed'].sum()) + print("/nat44-ei/ha/del-event-recv", self.stat["/nat44-ei/ha/del-event-recv"]) + print( + "/err/nat44-ei-ha/pkts-processed", + self.stat["/err/nat44-ei-ha/pkts-processed"].sum(), + ) def test_legacy(self): - '''Legacy interface''' + """Legacy interface""" directory = self.stat.ls(["^/if", "/err/ip4-input", "/sys/node/ip4-input"]) data = self.stat.dump(directory) print(data) - print('Looking up sys node') + print("Looking up sys node") directory = self.stat.ls(["^/sys/node"]) - print('Dumping sys node') + print("Dumping sys node") data = self.stat.dump(directory) print(data) directory = self.stat.ls(["^/foobar"]) @@ -545,18 +545,19 @@ class TestStats(unittest.TestCase): print(data) def test_sys_nodes(self): - '''Test /sys/nodes''' - counters = self.stat.ls('^/sys/node') - print('COUNTERS:', counters) - print('/sys/node', self.stat.dump(counters)) - print('/net/route/to', self.stat['/net/route/to']) + """Test /sys/nodes""" + counters = self.stat.ls("^/sys/node") + print("COUNTERS:", counters) + print("/sys/node", self.stat.dump(counters)) + print("/net/route/to", self.stat["/net/route/to"]) def test_symlink(self): - '''Symbolic links''' - print('/interface/local0/rx', self.stat['/interfaces/local0/rx']) - print('/sys/nodes/unix-epoll-input', self.stat['/nodes/unix-epoll-input/calls']) + """Symbolic links""" + print("/interface/local0/rx", self.stat["/interfaces/local0/rx"]) + print("/sys/nodes/unix-epoll-input", self.stat["/nodes/unix-epoll-input/calls"]) + -if __name__ == '__main__': +if __name__ == "__main__": import cProfile from pstats import Stats diff --git a/src/vpp-api/python/vpp_papi/vpp_transport_socket.py b/src/vpp-api/python/vpp_papi/vpp_transport_socket.py index c82b8c365a1..174ab74d0b8 100644 --- a/src/vpp-api/python/vpp_papi/vpp_transport_socket.py +++ b/src/vpp-api/python/vpp_papi/vpp_transport_socket.py @@ -9,7 +9,7 @@ import multiprocessing import queue import logging -logger = logging.getLogger('vpp_papi.transport') +logger = logging.getLogger("vpp_papi.transport") logger.addHandler(logging.NullHandler()) @@ -26,7 +26,7 @@ class VppTransport: self.read_timeout = read_timeout if read_timeout > 0 else None self.parent = parent self.server_address = server_address - self.header = struct.Struct('>QII') + self.header = struct.Struct(">QII") self.message_table = {} # These queues can be accessed async. # They are always up, but replaced on connect. @@ -41,11 +41,10 @@ class VppTransport: def msg_thread_func(self): while True: try: - rlist, _, _ = select.select([self.socket, - self.sque._reader], [], []) - except socket.error: + rlist, _, _ = select.select([self.socket, self.sque._reader], [], []) + except (socket.error, ValueError): # Terminate thread - logging.error('select failed') + logging.error("select failed") self.q.put(None) return @@ -66,21 +65,21 @@ class VppTransport: return # Put either to local queue or if context == 0 # callback queue - if self.parent.has_context(msg): + if not self.do_async and self.parent.has_context(msg): self.q.put(msg) else: self.parent.msg_handler_async(msg) else: - raise VppTransportSocketIOError( - 2, 'Unknown response from select') + raise VppTransportSocketIOError(2, "Unknown response from select") - def connect(self, name, pfx, msg_handler, rx_qlen): + def connect(self, name, pfx, msg_handler, rx_qlen, do_async=False): # TODO: Reorder the actions and add "roll-backs", # to restore clean disconnect state when failure happens durng connect. if self.message_thread is not None: raise VppTransportSocketIOError( - 1, "PAPI socket transport connect: Need to disconnect first.") + 1, "PAPI socket transport connect: Need to disconnect first." + ) # Create a UDS socket self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) @@ -107,19 +106,17 @@ class VppTransport: self.message_thread = threading.Thread(target=self.msg_thread_func) # Initialise sockclnt_create - sockclnt_create = self.parent.messages['sockclnt_create'] - sockclnt_create_reply = self.parent.messages['sockclnt_create_reply'] + sockclnt_create = self.parent.messages["sockclnt_create"] + sockclnt_create_reply = self.parent.messages["sockclnt_create_reply"] - args = {'_vl_msg_id': 15, - 'name': name, - 'context': 124} + args = {"_vl_msg_id": 15, "name": name, "context": 124} b = sockclnt_create.pack(args) self.write(b) msg = self._read() hdr, length = self.parent.header.unpack(msg, 0) if hdr.msgid != 16: # TODO: Add first numeric argument. - raise VppTransportSocketIOError('Invalid reply message') + raise VppTransportSocketIOError("Invalid reply message") r, length = sockclnt_create_reply.unpack(msg) self.socket_index = r.index @@ -128,6 +125,7 @@ class VppTransport: self.message_table[n] = m.index self.message_thread.daemon = True + self.do_async = do_async self.message_thread.start() return 0 @@ -184,7 +182,7 @@ class VppTransport: def write(self, buf): """Send a binary-packed message to VPP.""" if not self.connected: - raise VppTransportSocketIOError(1, 'Not connected') + raise VppTransportSocketIOError(1, "Not connected") # Send header header = self.header.pack(0, len(buf), 0) @@ -192,8 +190,7 @@ class VppTransport: self.socket.sendall(header) self.socket.sendall(buf) except socket.error as err: - raise VppTransportSocketIOError(1, 'Sendall error: {err!r}'.format( - err=err)) + raise VppTransportSocketIOError(1, "Sendall error: {err!r}".format(err=err)) def _read_fixed(self, size): """Repeat receive until fixed size is read. Return empty on error.""" @@ -223,11 +220,11 @@ class VppTransport: msg = self._read_fixed(hdrlen) if hdrlen == len(msg): return msg - raise VppTransportSocketIOError(1, 'Unknown socket read error') + raise VppTransportSocketIOError(1, "Unknown socket read error") def read(self, timeout=None): if not self.connected: - raise VppTransportSocketIOError(1, 'Not connected') + raise VppTransportSocketIOError(1, "Not connected") if timeout is None: timeout = self.read_timeout try: diff --git a/src/vpp-api/vapi/CMakeLists.txt b/src/vpp-api/vapi/CMakeLists.txt index 53034bd27b8..e53d3e8b238 100644 --- a/src/vpp-api/vapi/CMakeLists.txt +++ b/src/vpp-api/vapi/CMakeLists.txt @@ -34,7 +34,7 @@ install( vapi.hpp vapi_internal.h DESTINATION - include/vapi + ${CMAKE_INSTALL_INCLUDEDIR}/vapi COMPONENT vpp-dev ) @@ -45,7 +45,7 @@ install( vapi_json_parser.py vapi_cpp_gen.py DESTINATION - share/vpp + ${CMAKE_INSTALL_DATADIR}/vpp COMPONENT vpp-dev ) @@ -94,6 +94,7 @@ if(SUBUNIT_INCLUDE_DIR AND SUBUNIT_LIB) vapi_c_test.c DEPENDS fake_api_vapi_h LINK_LIBRARIES ${libs} + NO_INSTALL ) enable_language(CXX) @@ -102,6 +103,7 @@ if(SUBUNIT_INCLUDE_DIR AND SUBUNIT_LIB) vapi_cpp_test.cpp DEPENDS fake_api_vapi_hpp LINK_LIBRARIES ${libs} + NO_INSTALL ) else() diff --git a/src/vpp-api/vapi/fake.api.json b/src/vpp-api/vapi/fake.api.json index 24c9f4dbfa1..f7238c468fa 100644 --- a/src/vpp-api/vapi/fake.api.json +++ b/src/vpp-api/vapi/fake.api.json @@ -10,6 +10,8 @@ }, "enums" : [ ], + "enumflags" : [ + ], "unions" : [ ], "types" : [ diff --git a/src/vpp-api/vapi/vapi.c b/src/vpp-api/vapi/vapi.c index ec87e7b7b72..26c5708342f 100644 --- a/src/vpp-api/vapi/vapi.c +++ b/src/vpp-api/vapi/vapi.c @@ -30,8 +30,17 @@ #include <vlib/vlib.h> #include <vlibapi/api_common.h> #include <vlibmemory/memory_client.h> +#include <vlibmemory/memory_api.h> +#include <vlibmemory/api.h> #include <vapi/memclnt.api.vapi.h> +#include <vapi/vlib.api.vapi.h> + +#include <vlibmemory/vl_memory_msg_enum.h> + +#define vl_typedefs /* define message structures */ +#include <vlibmemory/vl_memory_api_h.h> +#undef vl_typedefs /* we need to use control pings for some stuff and because we're forced to put * the code in headers, we need a way to be able to grab the ids of these @@ -40,7 +49,7 @@ vapi_msg_id_t vapi_msg_id_control_ping = 0; vapi_msg_id_t vapi_msg_id_control_ping_reply = 0; DEFINE_VAPI_MSG_IDS_MEMCLNT_API_JSON; -DEFINE_VAPI_MSG_IDS_VPE_API_JSON; +DEFINE_VAPI_MSG_IDS_VLIB_API_JSON; struct { @@ -54,7 +63,8 @@ typedef struct u32 context; vapi_cb_t callback; void *callback_ctx; - bool is_dump; + vapi_msg_id_t response_id; + enum vapi_request_type type; } vapi_req_t; static const u32 context_counter_mask = (1 << 31); @@ -88,6 +98,14 @@ struct vapi_ctx_s bool connected; bool handle_keepalives; pthread_mutex_t requests_mutex; + bool use_uds; + + svm_queue_t *vl_input_queue; + clib_socket_t client_socket; + clib_time_t time; + u32 my_client_index; + /** client message index hash table */ + uword *msg_index_by_name_and_crc; }; u32 @@ -123,15 +141,17 @@ vapi_requests_end (vapi_ctx_t ctx) } void -vapi_store_request (vapi_ctx_t ctx, u32 context, bool is_dump, - vapi_cb_t callback, void *callback_ctx) +vapi_store_request (vapi_ctx_t ctx, u32 context, vapi_msg_id_t response_id, + enum vapi_request_type request_type, vapi_cb_t callback, + void *callback_ctx) { assert (!vapi_requests_full (ctx)); /* if the mutex is not held, bad things will happen */ assert (0 != pthread_mutex_trylock (&ctx->requests_mutex)); const int requests_end = vapi_requests_end (ctx); vapi_req_t *slot = &ctx->requests[requests_end]; - slot->is_dump = is_dump; + slot->type = request_type; + slot->response_id = response_id; slot->context = context; slot->callback = callback; slot->callback_ctx = callback_ctx; @@ -213,14 +233,14 @@ vapi_to_be_freed_validate () #endif -void * -vapi_msg_alloc (vapi_ctx_t ctx, size_t size) +static void * +vapi_shm_msg_alloc (vapi_ctx_t ctx, size_t size) { if (!ctx->connected) { return NULL; } - void *rv = vl_msg_api_alloc_or_null (size); + void *rv = vl_msg_api_alloc_as_if_client_or_null (size); if (rv) { clib_memset (rv, 0, size); @@ -228,6 +248,23 @@ vapi_msg_alloc (vapi_ctx_t ctx, size_t size) return rv; } +static void * +vapi_sock_msg_alloc (size_t size) +{ + u8 *rv = 0; + vec_validate_init_empty (rv, size - 1, 0); + return rv; +} + +void * +vapi_msg_alloc (vapi_ctx_t ctx, size_t size) +{ + if (ctx->use_uds) + return vapi_sock_msg_alloc (size); + + return vapi_shm_msg_alloc (ctx, size); +} + void vapi_msg_free (vapi_ctx_t ctx, void *msg) { @@ -235,10 +272,19 @@ vapi_msg_free (vapi_ctx_t ctx, void *msg) { return; } + #if VAPI_DEBUG_ALLOC vapi_trace_free (msg); #endif - vl_msg_api_free (msg); + + if (ctx->use_uds) + { + vec_free (msg); + } + else + { + vl_msg_api_free (msg); + } } vapi_msg_id_t @@ -277,6 +323,7 @@ vapi_ctx_alloc (vapi_ctx_t * result) } pthread_mutex_init (&ctx->requests_mutex, NULL); *result = ctx; + clib_time_init (&ctx->time); return VAPI_OK; fail: vapi_ctx_free (ctx); @@ -301,21 +348,630 @@ vapi_is_msg_available (vapi_ctx_t ctx, vapi_msg_id_t id) return vapi_lookup_vl_msg_id (ctx, id) != UINT16_MAX; } +/* Cut and paste to avoid adding dependency to client library */ +__clib_nosanitize_addr static void +VL_API_VEC_UNPOISON (const void *v) +{ + const vec_header_t *vh = &((vec_header_t *) v)[-1]; + clib_mem_unpoison (vh, sizeof (*vh) + vec_len (v)); +} + +static void +vapi_api_name_and_crc_free (vapi_ctx_t ctx) +{ + int i; + u8 **keys = 0; + hash_pair_t *hp; + + if (!ctx->msg_index_by_name_and_crc) + return; + hash_foreach_pair (hp, ctx->msg_index_by_name_and_crc, + ({ vec_add1 (keys, (u8 *) hp->key); })); + for (i = 0; i < vec_len (keys); i++) + vec_free (keys[i]); + vec_free (keys); + hash_free (ctx->msg_index_by_name_and_crc); +} + +static vapi_error_e +vapi_sock_get_errno (int err) +{ + switch (err) + { + case ENOTSOCK: + return VAPI_ENOTSOCK; + case EACCES: + return VAPI_EACCES; + case ECONNRESET: + return VAPI_ECONNRESET; + default: + break; + } + return VAPI_ESOCK_FAILURE; +} + +static vapi_error_e +vapi_sock_send (vapi_ctx_t ctx, u8 *msg) +{ + size_t n; + struct msghdr hdr; + + const size_t len = vec_len (msg); + const size_t total_len = len + sizeof (msgbuf_t); + + msgbuf_t msgbuf1 = { + .q = 0, + .gc_mark_timestamp = 0, + .data_len = htonl (len), + }; + + struct iovec bufs[2] = { + [0] = { .iov_base = &msgbuf1, .iov_len = sizeof (msgbuf1) }, + [1] = { .iov_base = msg, .iov_len = len }, + }; + + clib_memset (&hdr, 0, sizeof (hdr)); + hdr.msg_iov = bufs; + hdr.msg_iovlen = 2; + + n = sendmsg (ctx->client_socket.fd, &hdr, 0); + if (n < 0) + { + return vapi_sock_get_errno (errno); + } + + if (n < total_len) + { + return VAPI_EAGAIN; + } + + vec_free (msg); + + return VAPI_OK; +} + +static vapi_error_e +vapi_sock_send2 (vapi_ctx_t ctx, u8 *msg1, u8 *msg2) +{ + size_t n; + struct msghdr hdr; + + const size_t len1 = vec_len (msg1); + const size_t len2 = vec_len (msg2); + const size_t total_len = len1 + len2 + 2 * sizeof (msgbuf_t); + + msgbuf_t msgbuf1 = { + .q = 0, + .gc_mark_timestamp = 0, + .data_len = htonl (len1), + }; + + msgbuf_t msgbuf2 = { + .q = 0, + .gc_mark_timestamp = 0, + .data_len = htonl (len2), + }; + + struct iovec bufs[4] = { + [0] = { .iov_base = &msgbuf1, .iov_len = sizeof (msgbuf1) }, + [1] = { .iov_base = msg1, .iov_len = len1 }, + [2] = { .iov_base = &msgbuf2, .iov_len = sizeof (msgbuf2) }, + [3] = { .iov_base = msg2, .iov_len = len2 }, + }; + + clib_memset (&hdr, 0, sizeof (hdr)); + hdr.msg_iov = bufs; + hdr.msg_iovlen = 4; + + n = sendmsg (ctx->client_socket.fd, &hdr, 0); + if (n < 0) + { + return vapi_sock_get_errno (errno); + } + + if (n < total_len) + { + return VAPI_EAGAIN; + } + + vec_free (msg1); + vec_free (msg2); + + return VAPI_OK; +} + +static vapi_error_e +vapi_sock_recv_internal (vapi_ctx_t ctx, u8 **vec_msg, u32 timeout) +{ + clib_socket_t *sock = &ctx->client_socket; + u32 data_len = 0, msg_size; + msgbuf_t *mbp = 0; + ssize_t n, current_rx_index; + f64 deadline; + vapi_error_e rv = VAPI_EAGAIN; + + if (ctx->client_socket.fd == 0) + return VAPI_ENOTSOCK; + + deadline = clib_time_now (&ctx->time) + timeout; + + while (1) + { + current_rx_index = vec_len (sock->rx_buffer); + while (current_rx_index < sizeof (*mbp)) + { + vec_validate (sock->rx_buffer, sizeof (*mbp) - 1); + n = recv (sock->fd, sock->rx_buffer + current_rx_index, + sizeof (*mbp) - current_rx_index, MSG_DONTWAIT); + if (n < 0) + { + if (errno == EAGAIN && clib_time_now (&ctx->time) >= deadline) + return VAPI_EAGAIN; + + if (errno == EAGAIN) + continue; + + clib_unix_warning ("socket_read"); + vec_set_len (sock->rx_buffer, current_rx_index); + return vapi_sock_get_errno (errno); + } + current_rx_index += n; + } + vec_set_len (sock->rx_buffer, current_rx_index); + + mbp = (msgbuf_t *) (sock->rx_buffer); + data_len = ntohl (mbp->data_len); + current_rx_index = vec_len (sock->rx_buffer); + vec_validate (sock->rx_buffer, current_rx_index + data_len); + mbp = (msgbuf_t *) (sock->rx_buffer); + msg_size = data_len + sizeof (*mbp); + + while (current_rx_index < msg_size) + { + n = recv (sock->fd, sock->rx_buffer + current_rx_index, + msg_size - current_rx_index, MSG_DONTWAIT); + if (n < 0) + { + if (errno == EAGAIN && clib_time_now (&ctx->time) >= deadline) + return VAPI_EAGAIN; + + if (errno == EAGAIN) + continue; + + clib_unix_warning ("socket_read"); + vec_set_len (sock->rx_buffer, current_rx_index); + return vapi_sock_get_errno (errno); + } + current_rx_index += n; + } + vec_set_len (sock->rx_buffer, current_rx_index); + + if (vec_len (sock->rx_buffer) >= data_len + sizeof (*mbp)) + { + if (data_len) + { + vec_add (*vec_msg, mbp->data, data_len); + rv = VAPI_OK; + } + else + { + *vec_msg = 0; + } + + if (vec_len (sock->rx_buffer) == data_len + sizeof (*mbp)) + vec_set_len (sock->rx_buffer, 0); + else + vec_delete (sock->rx_buffer, data_len + sizeof (*mbp), 0); + mbp = 0; + + /* Quit if we're out of data, and not expecting a ping reply */ + if (vec_len (sock->rx_buffer) == 0) + break; + } + } + return rv; +} + +static void +vapi_memclnt_create_v2_reply_t_handler (vapi_ctx_t ctx, + vl_api_memclnt_create_v2_reply_t *mp) +{ + serialize_main_t _sm, *sm = &_sm; + u8 *tblv; + u32 nmsgs; + int i; + u8 *name_and_crc; + u32 msg_index; + + ctx->my_client_index = mp->index; + + /* Clean out any previous hash table (unlikely) */ + vapi_api_name_and_crc_free (ctx); + + ctx->msg_index_by_name_and_crc = hash_create_string (0, sizeof (uword)); + + /* Recreate the vnet-side API message handler table */ + tblv = uword_to_pointer (mp->message_table, u8 *); + unserialize_open_data (sm, tblv, vec_len (tblv)); + unserialize_integer (sm, &nmsgs, sizeof (u32)); + + VL_API_VEC_UNPOISON (tblv); + + for (i = 0; i < nmsgs; i++) + { + msg_index = unserialize_likely_small_unsigned_integer (sm); + unserialize_cstring (sm, (char **) &name_and_crc); + hash_set_mem (ctx->msg_index_by_name_and_crc, name_and_crc, msg_index); + } +} + +static void +vapi_sockclnt_create_reply_t_handler (vapi_ctx_t ctx, + vl_api_sockclnt_create_reply_t *mp) +{ + int i; + u8 *name_and_crc; + + ctx->my_client_index = mp->index; + + /* Clean out any previous hash table (unlikely) */ + vapi_api_name_and_crc_free (ctx); + + ctx->msg_index_by_name_and_crc = hash_create_string (0, sizeof (uword)); + + for (i = 0; i < be16toh (mp->count); i++) + { + name_and_crc = format (0, "%s%c", mp->message_table[i].name, 0); + hash_set_mem (ctx->msg_index_by_name_and_crc, name_and_crc, + be16toh (mp->message_table[i].index)); + } +} + +static void +vapi_memclnt_delete_reply_t_handler (vapi_ctx_t ctx, + vl_api_memclnt_delete_reply_t *mp) +{ + void *oldheap; + oldheap = vl_msg_push_heap (); + svm_queue_free (ctx->vl_input_queue); + vl_msg_pop_heap (oldheap); + + ctx->my_client_index = ~0; + ctx->vl_input_queue = 0; +} + +static void +vapi_sockclnt_delete_reply_t_handler (vapi_ctx_t ctx, + vl_api_sockclnt_delete_reply_t *mp) +{ + ctx->my_client_index = ~0; + ctx->vl_input_queue = 0; +} + +static int +vapi_shm_client_connect (vapi_ctx_t ctx, const char *name, int ctx_quota, + int input_queue_size, bool keepalive) +{ + vl_api_memclnt_create_v2_t *mp; + vl_api_memclnt_create_v2_reply_t *rp; + svm_queue_t *vl_input_queue; + vl_shmem_hdr_t *shmem_hdr; + int rv = 0; + void *oldheap; + api_main_t *am = vlibapi_get_main (); + + shmem_hdr = am->shmem_hdr; + + if (shmem_hdr == 0 || shmem_hdr->vl_input_queue == 0) + { + clib_warning ("shmem_hdr / input queue NULL"); + return VAPI_ECON_FAIL; + } + + clib_mem_unpoison (shmem_hdr, sizeof (*shmem_hdr)); + VL_MSG_API_SVM_QUEUE_UNPOISON (shmem_hdr->vl_input_queue); + + oldheap = vl_msg_push_heap (); + vl_input_queue = + svm_queue_alloc_and_init (input_queue_size, sizeof (uword), getpid ()); + vl_msg_pop_heap (oldheap); + + ctx->my_client_index = ~0; + ctx->vl_input_queue = vl_input_queue; + + mp = vl_msg_api_alloc_as_if_client (sizeof (vl_api_memclnt_create_v2_t)); + clib_memset (mp, 0, sizeof (*mp)); + mp->_vl_msg_id = ntohs (VL_API_MEMCLNT_CREATE_V2); + mp->ctx_quota = ctx_quota; + mp->input_queue = (uword) vl_input_queue; + strncpy ((char *) mp->name, name, sizeof (mp->name) - 1); + mp->keepalive = keepalive; + + vl_msg_api_send_shmem (shmem_hdr->vl_input_queue, (u8 *) &mp); + + while (1) + { + int qstatus; + struct timespec ts, tsrem; + int i; + + /* Wait up to 10 seconds */ + for (i = 0; i < 1000; i++) + { + qstatus = + svm_queue_sub (vl_input_queue, (u8 *) &rp, SVM_Q_NOWAIT, 0); + if (qstatus == 0) + goto read_one_msg; + ts.tv_sec = 0; + ts.tv_nsec = 10000 * 1000; /* 10 ms */ + while (nanosleep (&ts, &tsrem) < 0) + ts = tsrem; + } + /* Timeout... */ + return VAPI_ECON_FAIL; + + read_one_msg: + VL_MSG_API_UNPOISON (rp); + if (ntohs (rp->_vl_msg_id) != VL_API_MEMCLNT_CREATE_V2_REPLY) + { + clib_warning ("unexpected reply: id %d", ntohs (rp->_vl_msg_id)); + continue; + } + rv = clib_net_to_host_u32 (rp->response); + vapi_memclnt_create_v2_reply_t_handler (ctx, rp); + break; + } + return (rv); +} + +static int +vapi_sock_client_connect (vapi_ctx_t ctx, char *path, const char *name) +{ + clib_error_t *error; + clib_socket_t *sock; + vl_api_sockclnt_create_t *mp; + vl_api_sockclnt_create_reply_t *rp; + int rv = 0; + u8 *msg = 0; + + ctx->my_client_index = ~0; + + if (ctx->client_socket.fd) + return VAPI_EINVAL; + + if (name == 0) + return VAPI_EINVAL; + + sock = &ctx->client_socket; + sock->config = path ? path : API_SOCKET_FILE; + sock->flags = CLIB_SOCKET_F_IS_CLIENT; + + if ((error = clib_socket_init (sock))) + { + clib_error_report (error); + return VAPI_ECON_FAIL; + } + + mp = vapi_sock_msg_alloc (sizeof (vl_api_sockclnt_create_t)); + mp->_vl_msg_id = ntohs (VL_API_SOCKCLNT_CREATE); + strncpy ((char *) mp->name, name, sizeof (mp->name) - 1); + + if (vapi_sock_send (ctx, (void *) mp) != VAPI_OK) + { + return VAPI_ECON_FAIL; + } + + while (1) + { + int qstatus; + struct timespec ts, tsrem; + int i; + + /* Wait up to 10 seconds */ + for (i = 0; i < 1000; i++) + { + qstatus = vapi_sock_recv_internal (ctx, &msg, 0); + + if (qstatus == 0) + goto read_one_msg; + ts.tv_sec = 0; + ts.tv_nsec = 10000 * 1000; /* 10 ms */ + while (nanosleep (&ts, &tsrem) < 0) + ts = tsrem; + } + /* Timeout... */ + return -1; + + read_one_msg: + if (vec_len (msg) == 0) + continue; + + rp = (void *) msg; + if (ntohs (rp->_vl_msg_id) != VL_API_SOCKCLNT_CREATE_REPLY) + { + clib_warning ("unexpected reply: id %d", ntohs (rp->_vl_msg_id)); + continue; + } + rv = clib_net_to_host_u32 (rp->response); + vapi_sockclnt_create_reply_t_handler (ctx, rp); + break; + } + return (rv); +} + +static void +vapi_shm_client_send_disconnect (vapi_ctx_t ctx, u8 do_cleanup) +{ + vl_api_memclnt_delete_t *mp; + vl_shmem_hdr_t *shmem_hdr; + api_main_t *am = vlibapi_get_main (); + + ASSERT (am->vlib_rp); + shmem_hdr = am->shmem_hdr; + ASSERT (shmem_hdr && shmem_hdr->vl_input_queue); + + mp = vl_msg_api_alloc (sizeof (vl_api_memclnt_delete_t)); + clib_memset (mp, 0, sizeof (*mp)); + mp->_vl_msg_id = ntohs (VL_API_MEMCLNT_DELETE); + mp->index = ctx->my_client_index; + mp->do_cleanup = do_cleanup; + + vl_msg_api_send_shmem (shmem_hdr->vl_input_queue, (u8 *) &mp); +} + +static vapi_error_e +vapi_sock_client_send_disconnect (vapi_ctx_t ctx) +{ + vl_api_sockclnt_delete_t *mp; + + mp = vapi_msg_alloc (ctx, sizeof (vl_api_sockclnt_delete_t)); + clib_memset (mp, 0, sizeof (*mp)); + mp->_vl_msg_id = ntohs (VL_API_SOCKCLNT_DELETE); + mp->client_index = ctx->my_client_index; + + return vapi_sock_send (ctx, (void *) mp); +} + +static int +vapi_shm_client_disconnect (vapi_ctx_t ctx) +{ + vl_api_memclnt_delete_reply_t *rp; + svm_queue_t *vl_input_queue; + time_t begin; + msgbuf_t *msgbuf; + + vl_input_queue = ctx->vl_input_queue; + vapi_shm_client_send_disconnect (ctx, 0 /* wait for reply */); + + /* + * Have to be careful here, in case the client is disconnecting + * because e.g. the vlib process died, or is unresponsive. + */ + begin = time (0); + while (1) + { + time_t now; + + now = time (0); + + if (now >= (begin + 2)) + { + clib_warning ("peer unresponsive, give up"); + ctx->my_client_index = ~0; + return VAPI_ENORESP; + } + if (svm_queue_sub (vl_input_queue, (u8 *) &rp, SVM_Q_NOWAIT, 0) < 0) + continue; + + VL_MSG_API_UNPOISON (rp); + + /* drain the queue */ + if (ntohs (rp->_vl_msg_id) != VL_API_MEMCLNT_DELETE_REPLY) + { + clib_warning ("queue drain: %d", ntohs (rp->_vl_msg_id)); + msgbuf = (msgbuf_t *) ((u8 *) rp - offsetof (msgbuf_t, data)); + vl_msg_api_handler ((void *) rp, ntohl (msgbuf->data_len)); + continue; + } + msgbuf = (msgbuf_t *) ((u8 *) rp - offsetof (msgbuf_t, data)); + vl_msg_api_handler ((void *) rp, ntohl (msgbuf->data_len)); + break; + } + + vapi_api_name_and_crc_free (ctx); + return 0; +} + +static vapi_error_e +vapi_sock_client_disconnect (vapi_ctx_t ctx) +{ + vl_api_sockclnt_delete_reply_t *rp; + u8 *msg = 0; + msgbuf_t *msgbuf; + int rv; + f64 deadline; + + deadline = clib_time_now (&ctx->time) + 2; + + do + { + rv = vapi_sock_client_send_disconnect (ctx); + } + while (clib_time_now (&ctx->time) < deadline && rv != VAPI_OK); + + while (1) + { + if (clib_time_now (&ctx->time) >= deadline) + { + clib_warning ("peer unresponsive, give up"); + ctx->my_client_index = ~0; + return VAPI_ENORESP; + } + + if (vapi_sock_recv_internal (ctx, &msg, 0) != VAPI_OK) + continue; + + msgbuf = (void *) msg; + rp = (void *) msgbuf->data; + /* drain the queue */ + if (ntohs (rp->_vl_msg_id) != VL_API_SOCKCLNT_DELETE_REPLY) + { + clib_warning ("queue drain: %d", ntohs (rp->_vl_msg_id)); + msgbuf = (msgbuf_t *) ((u8 *) rp - offsetof (msgbuf_t, data)); + vl_msg_api_handler ((void *) rp, ntohl (msgbuf->data_len)); + continue; + } + msgbuf = (msgbuf_t *) ((u8 *) rp - offsetof (msgbuf_t, data)); + vl_msg_api_handler ((void *) rp, ntohl (msgbuf->data_len)); + break; + } + + clib_socket_close (&ctx->client_socket); + vapi_api_name_and_crc_free (ctx); + return VAPI_OK; +} + +int +vapi_client_disconnect (vapi_ctx_t ctx) +{ + if (ctx->use_uds) + { + return vapi_sock_client_disconnect (ctx); + } + return vapi_shm_client_disconnect (ctx); +} + +u32 +vapi_api_get_msg_index (vapi_ctx_t ctx, u8 *name_and_crc) +{ + uword *p; + + if (ctx->msg_index_by_name_and_crc) + { + p = hash_get_mem (ctx->msg_index_by_name_and_crc, name_and_crc); + if (p) + return p[0]; + } + return ~0; +} + vapi_error_e -vapi_connect (vapi_ctx_t ctx, const char *name, - const char *chroot_prefix, - int max_outstanding_requests, - int response_queue_size, vapi_mode_e mode, - bool handle_keepalives) +vapi_connect_ex (vapi_ctx_t ctx, const char *name, const char *path, + int max_outstanding_requests, int response_queue_size, + vapi_mode_e mode, bool handle_keepalives, bool use_uds) { + int rv; + if (response_queue_size <= 0 || max_outstanding_requests <= 0) { return VAPI_EINVAL; } - if (!clib_mem_get_per_cpu_heap () && !clib_mem_init (0, 1024 * 1024 * 32)) + + if (!clib_mem_get_per_cpu_heap () && !clib_mem_init (0, 1024L * 1024 * 32)) { return VAPI_ENOMEM; } + ctx->requests_size = max_outstanding_requests; const size_t size = ctx->requests_size * sizeof (*ctx->requests); void *tmp = realloc (ctx->requests, size); @@ -327,34 +983,48 @@ vapi_connect (vapi_ctx_t ctx, const char *name, clib_memset (ctx->requests, 0, size); /* coverity[MISSING_LOCK] - 177211 requests_mutex is not needed here */ ctx->requests_start = ctx->requests_count = 0; - if (chroot_prefix) - { - VAPI_DBG ("set memory root path `%s'", chroot_prefix); - vl_set_memory_root_path ((char *) chroot_prefix); - } - static char api_map[] = "/vpe-api"; - VAPI_DBG ("client api map `%s'", api_map); - if ((vl_client_api_map (api_map)) < 0) + ctx->use_uds = use_uds; + + if (use_uds) { - return VAPI_EMAP_FAIL; + if (vapi_sock_client_connect (ctx, (char *) path, name) < 0) + { + return VAPI_ECON_FAIL; + } } - VAPI_DBG ("connect client `%s'", name); - if (vl_client_connect ((char *) name, 0, response_queue_size) < 0) + else { - vl_client_api_unmap (); - return VAPI_ECON_FAIL; - } + if (path) + { + VAPI_DBG ("set memory root path `%s'", path); + vl_set_memory_root_path ((char *) path); + } + static char api_map[] = "/vpe-api"; + VAPI_DBG ("client api map `%s'", api_map); + if ((rv = vl_map_shmem (api_map, 0 /* is_vlib */)) < 0) + { + return VAPI_EMAP_FAIL; + } + VAPI_DBG ("connect client `%s'", name); + if (vapi_shm_client_connect (ctx, (char *) name, 0, response_queue_size, + true) < 0) + { + vl_client_api_unmap (); + return VAPI_ECON_FAIL; + } #if VAPI_DEBUG_CONNECT VAPI_DBG ("start probing messages"); #endif - int rv; + } + int i; for (i = 0; i < __vapi_metadata.count; ++i) { vapi_message_desc_t *m = __vapi_metadata.msgs[i]; u8 scratch[m->name_with_crc_len + 1]; memcpy (scratch, m->name_with_crc, m->name_with_crc_len + 1); - u32 id = vl_msg_api_get_msg_index (scratch); + u32 id = vapi_api_get_msg_index (ctx, scratch); + if (VAPI_INVALID_MSG_ID != id) { if (id > UINT16_MAX) @@ -366,10 +1036,9 @@ vapi_connect (vapi_ctx_t ctx, const char *name, } if (id > ctx->vl_msg_id_max) { - vapi_msg_id_t *tmp = realloc (ctx->vl_msg_id_to_vapi_msg_t, - sizeof - (*ctx->vl_msg_id_to_vapi_msg_t) * - (id + 1)); + vapi_msg_id_t *tmp = + realloc (ctx->vl_msg_id_to_vapi_msg_t, + sizeof (*ctx->vl_msg_id_to_vapi_msg_t) * (id + 1)); if (!tmp) { rv = VAPI_ENOMEM; @@ -397,8 +1066,8 @@ vapi_connect (vapi_ctx_t ctx, const char *name, if (!vapi_is_msg_available (ctx, vapi_msg_id_control_ping) || !vapi_is_msg_available (ctx, vapi_msg_id_control_ping_reply)) { - VAPI_ERR - ("control ping or control ping reply not available, cannot connect"); + VAPI_ERR ( + "control ping or control ping reply not available, cannot connect"); rv = VAPI_EINCOMPATIBLE; goto fail; } @@ -414,111 +1083,393 @@ vapi_connect (vapi_ctx_t ctx, const char *name, } return VAPI_OK; fail: - vl_client_disconnect (); + vapi_client_disconnect (ctx); vl_client_api_unmap (); return rv; } vapi_error_e -vapi_disconnect (vapi_ctx_t ctx) +vapi_connect (vapi_ctx_t ctx, const char *name, const char *chroot_prefix, + int max_outstanding_requests, int response_queue_size, + vapi_mode_e mode, bool handle_keepalives) +{ + return vapi_connect_ex (ctx, name, chroot_prefix, max_outstanding_requests, + response_queue_size, mode, handle_keepalives, false); +} + +/* + * API client running in the same process as VPP + */ +vapi_error_e +vapi_connect_from_vpp (vapi_ctx_t ctx, const char *name, + int max_outstanding_requests, int response_queue_size, + vapi_mode_e mode, bool handle_keepalives) +{ + int rv; + + if (ctx->use_uds) + { + return VAPI_ENOTSUP; + } + + if (response_queue_size <= 0 || max_outstanding_requests <= 0) + { + return VAPI_EINVAL; + } + + ctx->requests_size = max_outstanding_requests; + const size_t size = ctx->requests_size * sizeof (*ctx->requests); + void *tmp = realloc (ctx->requests, size); + if (!tmp) + { + return VAPI_ENOMEM; + } + ctx->requests = tmp; + clib_memset (ctx->requests, 0, size); + /* coverity[MISSING_LOCK] - 177211 requests_mutex is not needed here */ + ctx->requests_start = ctx->requests_count = 0; + + VAPI_DBG ("connect client `%s'", name); + if (vapi_shm_client_connect (ctx, (char *) name, 0, response_queue_size, + handle_keepalives) < 0) + { + return VAPI_ECON_FAIL; + } + + int i; + for (i = 0; i < __vapi_metadata.count; ++i) + { + vapi_message_desc_t *m = __vapi_metadata.msgs[i]; + u8 scratch[m->name_with_crc_len + 1]; + memcpy (scratch, m->name_with_crc, m->name_with_crc_len + 1); + u32 id = vapi_api_get_msg_index (ctx, scratch); + if (VAPI_INVALID_MSG_ID != id) + { + if (id > UINT16_MAX) + { + VAPI_ERR ("Returned vl_msg_id `%u' > UINT16MAX `%u'!", id, + UINT16_MAX); + rv = VAPI_EINVAL; + goto fail; + } + if (id > ctx->vl_msg_id_max) + { + vapi_msg_id_t *tmp = + realloc (ctx->vl_msg_id_to_vapi_msg_t, + sizeof (*ctx->vl_msg_id_to_vapi_msg_t) * (id + 1)); + if (!tmp) + { + rv = VAPI_ENOMEM; + goto fail; + } + ctx->vl_msg_id_to_vapi_msg_t = tmp; + ctx->vl_msg_id_max = id; + } + ctx->vl_msg_id_to_vapi_msg_t[id] = m->id; + ctx->vapi_msg_id_t_to_vl_msg_id[m->id] = id; + } + else + { + ctx->vapi_msg_id_t_to_vl_msg_id[m->id] = UINT16_MAX; + VAPI_DBG ("Message `%s' not available", m->name_with_crc); + } + } + if (!vapi_is_msg_available (ctx, vapi_msg_id_control_ping) || + !vapi_is_msg_available (ctx, vapi_msg_id_control_ping_reply)) + { + VAPI_ERR ( + "control ping or control ping reply not available, cannot connect"); + rv = VAPI_EINCOMPATIBLE; + goto fail; + } + ctx->mode = mode; + ctx->connected = true; + if (vapi_is_msg_available (ctx, vapi_msg_id_memclnt_keepalive)) + { + ctx->handle_keepalives = handle_keepalives; + } + else + { + ctx->handle_keepalives = false; + } + return VAPI_OK; +fail: + vapi_client_disconnect (ctx); + return rv; +} + +vapi_error_e +vapi_disconnect_from_vpp (vapi_ctx_t ctx) { if (!ctx->connected) { return VAPI_EINVAL; } - vl_client_disconnect (); + + if (ctx->use_uds) + { + return VAPI_ENOTSUP; + } + + vl_api_memclnt_delete_reply_t *rp; + svm_queue_t *vl_input_queue; + time_t begin; + vl_input_queue = ctx->vl_input_queue; + vapi_shm_client_send_disconnect (ctx, 0 /* wait for reply */); + + /* + * Have to be careful here, in case the client is disconnecting + * because e.g. the vlib process died, or is unresponsive. + */ + begin = time (0); + vapi_error_e rv = VAPI_OK; + while (1) + { + time_t now; + + now = time (0); + + if (now >= (begin + 2)) + { + clib_warning ("peer unresponsive, give up"); + ctx->my_client_index = ~0; + rv = VAPI_ENORESP; + goto fail; + } + if (svm_queue_sub (vl_input_queue, (u8 *) &rp, SVM_Q_NOWAIT, 0) < 0) + continue; + + VL_MSG_API_UNPOISON (rp); + + /* drain the queue */ + if (ntohs (rp->_vl_msg_id) != VL_API_MEMCLNT_DELETE_REPLY) + { + clib_warning ("queue drain: %d", ntohs (rp->_vl_msg_id)); + vl_msg_api_free (rp); + continue; + } + vapi_memclnt_delete_reply_t_handler ( + ctx, (void *) rp /*, ntohl (msgbuf->data_len)*/); + break; + } +fail: + vapi_api_name_and_crc_free (ctx); + + ctx->connected = false; + return rv; +} + +static vapi_error_e +vapi_shm_disconnect (vapi_ctx_t ctx) +{ + vl_api_memclnt_delete_reply_t *rp; + svm_queue_t *vl_input_queue; + time_t begin; + vl_input_queue = ctx->vl_input_queue; + vapi_shm_client_send_disconnect (ctx, 0 /* wait for reply */); + + /* + * Have to be careful here, in case the client is disconnecting + * because e.g. the vlib process died, or is unresponsive. + */ + begin = time (0); + vapi_error_e rv = VAPI_OK; + while (1) + { + time_t now; + + now = time (0); + + if (now >= (begin + 2)) + { + clib_warning ("peer unresponsive, give up"); + ctx->my_client_index = ~0; + rv = VAPI_ENORESP; + goto fail; + } + if (svm_queue_sub (vl_input_queue, (u8 *) &rp, SVM_Q_NOWAIT, 0) < 0) + continue; + + VL_MSG_API_UNPOISON (rp); + + /* drain the queue */ + if (ntohs (rp->_vl_msg_id) != VL_API_MEMCLNT_DELETE_REPLY) + { + clib_warning ("queue drain: %d", ntohs (rp->_vl_msg_id)); + vl_msg_api_free (rp); + continue; + } + vapi_memclnt_delete_reply_t_handler ( + ctx, (void *) rp /*, ntohl (msgbuf->data_len)*/); + break; + } +fail: + vapi_api_name_and_crc_free (ctx); + vl_client_api_unmap (); #if VAPI_DEBUG_ALLOC vapi_to_be_freed_validate (); #endif ctx->connected = false; - return VAPI_OK; + return rv; +} + +static vapi_error_e +vapi_sock_disconnect (vapi_ctx_t ctx) +{ + vl_api_sockclnt_delete_reply_t *rp; + time_t begin; + u8 *msg = 0; + + vapi_sock_client_send_disconnect (ctx); + + begin = time (0); + vapi_error_e rv = VAPI_OK; + while (1) + { + time_t now; + + now = time (0); + + if (now >= (begin + 2)) + { + clib_warning ("peer unresponsive, give up"); + ctx->my_client_index = ~0; + rv = VAPI_ENORESP; + goto fail; + } + if (vapi_sock_recv_internal (ctx, &msg, 0) < 0) + continue; + + if (vec_len (msg) == 0) + continue; + + rp = (void *) msg; + + /* drain the queue */ + if (ntohs (rp->_vl_msg_id) != VL_API_SOCKCLNT_DELETE_REPLY) + { + clib_warning ("queue drain: %d", ntohs (rp->_vl_msg_id)); + continue; + } + vapi_sockclnt_delete_reply_t_handler ( + ctx, (void *) rp /*, ntohl (msgbuf->data_len)*/); + break; + } +fail: + clib_socket_close (&ctx->client_socket); + vapi_api_name_and_crc_free (ctx); + + ctx->connected = false; + return rv; } vapi_error_e -vapi_get_fd (vapi_ctx_t ctx, int *fd) +vapi_disconnect (vapi_ctx_t ctx) { - return VAPI_ENOTSUP; + if (!ctx->connected) + { + return VAPI_EINVAL; + } + + if (ctx->use_uds) + { + return vapi_sock_disconnect (ctx); + } + return vapi_shm_disconnect (ctx); } vapi_error_e -vapi_send (vapi_ctx_t ctx, void *msg) +vapi_get_fd (vapi_ctx_t ctx, int *fd) { - vapi_error_e rv = VAPI_OK; - if (!ctx || !msg || !ctx->connected) + if (ctx->use_uds && fd) { - rv = VAPI_EINVAL; - goto out; + *fd = ctx->client_socket.fd; + return VAPI_OK; } - int tmp; - svm_queue_t *q = vlibapi_get_main ()->shmem_hdr->vl_input_queue; + return VAPI_ENOTSUP; +} + #if VAPI_DEBUG +static void +vapi_debug_log (vapi_ctx_t ctx, void *msg, const char *fun) +{ unsigned msgid = be16toh (*(u16 *) msg); if (msgid <= ctx->vl_msg_id_max) { vapi_msg_id_t id = ctx->vl_msg_id_to_vapi_msg_t[msgid]; if (id < __vapi_metadata.count) { - VAPI_DBG ("send msg@%p:%u[%s]", msg, msgid, + VAPI_DBG ("%s msg@%p:%u[%s]", fun, msg, msgid, __vapi_metadata.msgs[id]->name); } else { - VAPI_DBG ("send msg@%p:%u[UNKNOWN]", msg, msgid); + VAPI_DBG ("%s msg@%p:%u[UNKNOWN]", fun, msg, msgid); } } else { - VAPI_DBG ("send msg@%p:%u[UNKNOWN]", msg, msgid); + VAPI_DBG ("%s msg@%p:%u[UNKNOWN]", fun, msg, msgid); } +} +#endif + +static vapi_error_e +vapi_shm_send (vapi_ctx_t ctx, void *msg) +{ + int rv = VAPI_OK; + int tmp; + svm_queue_t *q = vlibapi_get_main ()->shmem_hdr->vl_input_queue; +#if VAPI_DEBUG + vapi_debug_log (ctx, msg, "send"); #endif - tmp = svm_queue_add (q, (u8 *) & msg, - VAPI_MODE_BLOCKING == ctx->mode ? 0 : 1); + tmp = + svm_queue_add (q, (u8 *) &msg, VAPI_MODE_BLOCKING == ctx->mode ? 0 : 1); if (tmp < 0) { rv = VAPI_EAGAIN; } else VL_MSG_API_POISON (msg); -out: - VAPI_DBG ("vapi_send() rv = %d", rv); + return rv; } vapi_error_e -vapi_send2 (vapi_ctx_t ctx, void *msg1, void *msg2) +vapi_send (vapi_ctx_t ctx, void *msg) { vapi_error_e rv = VAPI_OK; - if (!ctx || !msg1 || !msg2 || !ctx->connected) + if (!ctx || !msg || !ctx->connected) { rv = VAPI_EINVAL; goto out; } - svm_queue_t *q = vlibapi_get_main ()->shmem_hdr->vl_input_queue; -#if VAPI_DEBUG - unsigned msgid1 = be16toh (*(u16 *) msg1); - unsigned msgid2 = be16toh (*(u16 *) msg2); - const char *name1 = "UNKNOWN"; - const char *name2 = "UNKNOWN"; - if (msgid1 <= ctx->vl_msg_id_max) + + if (ctx->use_uds) { - vapi_msg_id_t id = ctx->vl_msg_id_to_vapi_msg_t[msgid1]; - if (id < __vapi_metadata.count) - { - name1 = __vapi_metadata.msgs[id]->name; - } + rv = vapi_sock_send (ctx, msg); } - if (msgid2 <= ctx->vl_msg_id_max) + else { - vapi_msg_id_t id = ctx->vl_msg_id_to_vapi_msg_t[msgid2]; - if (id < __vapi_metadata.count) - { - name2 = __vapi_metadata.msgs[id]->name; - } + rv = vapi_shm_send (ctx, msg); } - VAPI_DBG ("send two: %u[%s], %u[%s]", msgid1, name1, msgid2, name2); + +out: + VAPI_DBG ("vapi_send() rv = %d", rv); + return rv; +} + +static vapi_error_e +vapi_shm_send2 (vapi_ctx_t ctx, void *msg1, void *msg2) +{ + vapi_error_e rv = VAPI_OK; + svm_queue_t *q = vlibapi_get_main ()->shmem_hdr->vl_input_queue; +#if VAPI_DEBUG + vapi_debug_log (ctx, msg1, "send2"); + vapi_debug_log (ctx, msg2, "send2"); #endif - int tmp = svm_queue_add2 (q, (u8 *) & msg1, (u8 *) & msg2, + int tmp = svm_queue_add2 (q, (u8 *) &msg1, (u8 *) &msg2, VAPI_MODE_BLOCKING == ctx->mode ? 0 : 1); if (tmp < 0) { @@ -526,36 +1477,52 @@ vapi_send2 (vapi_ctx_t ctx, void *msg1, void *msg2) } else VL_MSG_API_POISON (msg1); -out: - VAPI_DBG ("vapi_send() rv = %d", rv); + return rv; } vapi_error_e -vapi_recv (vapi_ctx_t ctx, void **msg, size_t * msg_size, - svm_q_conditional_wait_t cond, u32 time) +vapi_send2 (vapi_ctx_t ctx, void *msg1, void *msg2) { - if (!ctx || !ctx->connected || !msg || !msg_size) + vapi_error_e rv = VAPI_OK; + if (!ctx || !msg1 || !msg2 || !ctx->connected) { - return VAPI_EINVAL; + rv = VAPI_EINVAL; + goto out; } - vapi_error_e rv = VAPI_OK; - api_main_t *am = vlibapi_get_main (); - uword data; - if (am->our_pid == 0) + if (ctx->use_uds) { - return VAPI_EINVAL; + rv = vapi_sock_send2 (ctx, msg1, msg2); + } + else + { + rv = vapi_shm_send2 (ctx, msg1, msg2); } - svm_queue_t *q = am->vl_input_queue; -again: +out: + VAPI_DBG ("vapi_send() rv = %d", rv); + return rv; +} + +static vapi_error_e +vapi_shm_recv (vapi_ctx_t ctx, void **msg, size_t *msg_size, + svm_q_conditional_wait_t cond, u32 time) +{ + vapi_error_e rv = VAPI_OK; + uword data; + + svm_queue_t *q = ctx->vl_input_queue; + VAPI_DBG ("doing shm queue sub"); int tmp = svm_queue_sub (q, (u8 *) & data, cond, time); - if (tmp == 0) + if (tmp != 0) { + return VAPI_EAGAIN; + } + VL_MSG_API_UNPOISON ((void *) data); #if VAPI_DEBUG_ALLOC vapi_add_to_be_freed ((void *) data); @@ -569,62 +1536,99 @@ again: } *msg = (u8 *) data; *msg_size = ntohl (msgbuf->data_len); + #if VAPI_DEBUG - unsigned msgid = be16toh (*(u16 *) * msg); - if (msgid <= ctx->vl_msg_id_max) - { - vapi_msg_id_t id = ctx->vl_msg_id_to_vapi_msg_t[msgid]; - if (id < __vapi_metadata.count) - { - VAPI_DBG ("recv msg@%p:%u[%s]", *msg, msgid, - __vapi_metadata.msgs[id]->name); - } - else - { - VAPI_DBG ("recv msg@%p:%u[UNKNOWN]", *msg, msgid); - } - } - else - { - VAPI_DBG ("recv msg@%p:%u[UNKNOWN]", *msg, msgid); - } + vapi_debug_log (ctx, msg, "recv"); +#endif + + return rv; +} + +static vapi_error_e +vapi_sock_recv (vapi_ctx_t ctx, void **msg, size_t *msg_size, u32 time) +{ + vapi_error_e rv = VAPI_OK; + u8 *data = 0; + if (time == 0 && ctx->mode == VAPI_MODE_BLOCKING) + time = 1; + + rv = vapi_sock_recv_internal (ctx, &data, time); + + if (rv != VAPI_OK) + { + return rv; + } + + *msg = data; + *msg_size = vec_len (data); + +#if VAPI_DEBUG + vapi_debug_log (ctx, msg, "recv"); #endif - if (ctx->handle_keepalives) + + return rv; +} + +vapi_error_e +vapi_recv (vapi_ctx_t ctx, void **msg, size_t *msg_size, + svm_q_conditional_wait_t cond, u32 time) +{ + if (!ctx || !ctx->connected || !msg || !msg_size) + { + return VAPI_EINVAL; + } + vapi_error_e rv = VAPI_OK; + +again: + if (ctx->use_uds) + { + rv = vapi_sock_recv (ctx, msg, msg_size, time); + } + else + { + rv = vapi_shm_recv (ctx, msg, msg_size, cond, time); + } + + if (rv != VAPI_OK) + return rv; + + if (ctx->handle_keepalives) + { + unsigned msgid = be16toh (*(u16 *) *msg); + if (msgid == vapi_lookup_vl_msg_id (ctx, vapi_msg_id_memclnt_keepalive)) { - unsigned msgid = be16toh (*(u16 *) * msg); - if (msgid == - vapi_lookup_vl_msg_id (ctx, vapi_msg_id_memclnt_keepalive)) + vapi_msg_memclnt_keepalive_reply *reply = NULL; + do { - vapi_msg_memclnt_keepalive_reply *reply = NULL; - do - { - reply = vapi_msg_alloc (ctx, sizeof (*reply)); - } - while (!reply); - reply->header.context = vapi_get_client_index (ctx); - reply->header._vl_msg_id = - vapi_lookup_vl_msg_id (ctx, - vapi_msg_id_memclnt_keepalive_reply); - reply->payload.retval = 0; - vapi_msg_memclnt_keepalive_reply_hton (reply); - while (VAPI_EAGAIN == vapi_send (ctx, reply)); - vapi_msg_free (ctx, *msg); - VAPI_DBG ("autohandled memclnt_keepalive"); - goto again; + reply = vapi_msg_alloc (ctx, sizeof (*reply)); } + while (!reply); + reply->header.context = vapi_get_client_index (ctx); + reply->header._vl_msg_id = + vapi_lookup_vl_msg_id (ctx, vapi_msg_id_memclnt_keepalive_reply); + reply->payload.retval = 0; + vapi_msg_memclnt_keepalive_reply_hton (reply); + while (VAPI_EAGAIN == vapi_send (ctx, reply)) + ; + vapi_msg_free (ctx, *msg); + goto again; } } - else - { - rv = VAPI_EAGAIN; - } + return rv; } vapi_error_e -vapi_wait (vapi_ctx_t ctx, vapi_wait_mode_e mode) +vapi_wait (vapi_ctx_t ctx) { - return VAPI_ENOTSUP; + if (ctx->use_uds) + return VAPI_ENOTSUP; + + svm_queue_lock (ctx->vl_input_queue); + svm_queue_wait (ctx->vl_input_queue); + svm_queue_unlock (ctx->vl_input_queue); + + return VAPI_OK; } static vapi_error_e @@ -675,8 +1679,34 @@ vapi_dispatch_response (vapi_ctx_t ctx, vapi_msg_id_t id, int payload_offset = vapi_get_payload_offset (id); void *payload = ((u8 *) msg) + payload_offset; bool is_last = true; - if (ctx->requests[tmp].is_dump) + switch (ctx->requests[tmp].type) { + case VAPI_REQUEST_STREAM: + if (ctx->requests[tmp].response_id == id) + { + is_last = false; + } + else + { + VAPI_DBG ("Stream response ID doesn't match current ID, move to " + "next ID"); + clib_memset (&ctx->requests[tmp], 0, + sizeof (ctx->requests[tmp])); + ++ctx->requests_start; + --ctx->requests_count; + if (ctx->requests_start == ctx->requests_size) + { + ctx->requests_start = 0; + } + tmp = ctx->requests_start; + if (ctx->requests[tmp].context != context) + { + VAPI_ERR ("Unexpected context %u, expected context %u!", + ctx->requests[tmp].context, context); + } + } + break; + case VAPI_REQUEST_DUMP: if (vapi_msg_id_control_ping_reply == id) { payload = NULL; @@ -685,12 +1715,14 @@ vapi_dispatch_response (vapi_ctx_t ctx, vapi_msg_id_t id, { is_last = false; } + break; + case VAPI_REQUEST_REG: + break; } if (payload_offset != -1) { - rv = - ctx->requests[tmp].callback (ctx, ctx->requests[tmp].callback_ctx, - VAPI_OK, is_last, payload); + rv = ctx->requests[tmp].callback ( + ctx, ctx->requests[tmp].callback_ctx, VAPI_OK, is_last, payload); } else { @@ -752,13 +1784,23 @@ vapi_msg_is_with_context (vapi_msg_id_t id) return __vapi_metadata.msgs[id]->has_context; } +static int +vapi_verify_msg_size (vapi_msg_id_t id, void *buf, uword buf_size) +{ + assert (id < __vapi_metadata.count); + return __vapi_metadata.msgs[id]->verify_msg_size (buf, buf_size); +} + vapi_error_e -vapi_dispatch_one (vapi_ctx_t ctx) +vapi_dispatch_one_timedwait (vapi_ctx_t ctx, u32 wait_time) { VAPI_DBG ("vapi_dispatch_one()"); void *msg; - size_t size; - vapi_error_e rv = vapi_recv (ctx, &msg, &size, SVM_Q_WAIT, 0); + uword size; + svm_q_conditional_wait_t cond = + vapi_is_nonblocking (ctx) ? (wait_time ? SVM_Q_TIMEDWAIT : SVM_Q_NOWAIT) : + SVM_Q_WAIT; + vapi_error_e rv = vapi_recv (ctx, &msg, &size, cond, wait_time); if (VAPI_OK != rv) { VAPI_DBG ("vapi_recv failed with rv=%d", rv); @@ -780,17 +1822,13 @@ vapi_dispatch_one (vapi_ctx_t ctx) return VAPI_EINVAL; } const vapi_msg_id_t id = ctx->vl_msg_id_to_vapi_msg_t[vpp_id]; - const size_t expect_size = vapi_get_message_size (id); - if (size < expect_size) + vapi_get_swap_to_host_func (id) (msg); + if (vapi_verify_msg_size (id, msg, size)) { - VAPI_ERR - ("Invalid msg received, unexpected size `%zu' < expected min `%zu'", - size, expect_size); vapi_msg_free (ctx, msg); return VAPI_EINVAL; } u32 context; - vapi_get_swap_to_host_func (id) (msg); if (vapi_msg_is_with_context (id)) { context = *(u32 *) (((u8 *) msg) + vapi_get_context_offset (id)); @@ -810,6 +1848,12 @@ done: } vapi_error_e +vapi_dispatch_one (vapi_ctx_t ctx) +{ + return vapi_dispatch_one_timedwait (ctx, 0); +} + +vapi_error_e vapi_dispatch (vapi_ctx_t ctx) { vapi_error_e rv = VAPI_OK; @@ -864,7 +1908,7 @@ vapi_lookup_vl_msg_id (vapi_ctx_t ctx, vapi_msg_id_t id) int vapi_get_client_index (vapi_ctx_t ctx) { - return vlibapi_get_main ()->my_client_index; + return ctx->my_client_index; } bool @@ -899,13 +1943,6 @@ void (*vapi_get_swap_to_be_func (vapi_msg_id_t id)) (void *msg) } size_t -vapi_get_message_size (vapi_msg_id_t id) -{ - assert (id < __vapi_metadata.count); - return __vapi_metadata.msgs[id]->size; -} - -size_t vapi_get_context_offset (vapi_msg_id_t id) { assert (id < __vapi_metadata.count); @@ -982,6 +2019,16 @@ vapi_get_msg_name (vapi_msg_id_t id) return __vapi_metadata.msgs[id]->name; } +void +vapi_stop_rx_thread (vapi_ctx_t ctx) +{ + if (!ctx || !ctx->connected || !ctx->vl_input_queue) + { + return; + } + + vl_client_stop_rx_thread (ctx->vl_input_queue); +} /* * fd.io coding-style-patch-verification: ON * diff --git a/src/vpp-api/vapi/vapi.h b/src/vpp-api/vapi/vapi.h index 08d016b0dd7..8f092eed1c2 100644 --- a/src/vpp-api/vapi/vapi.h +++ b/src/vpp-api/vapi/vapi.h @@ -44,7 +44,7 @@ extern "C" * process). It's not recommended to mix the higher and lower level APIs. Due * to version issues, the higher-level APIs are not part of the shared library. */ - typedef struct vapi_ctx_s *vapi_ctx_t; +typedef struct vapi_ctx_s *vapi_ctx_t; /** * @brief allocate vapi message of given size @@ -56,7 +56,7 @@ extern "C" * * @return pointer to message or NULL if out of memory */ - void *vapi_msg_alloc (vapi_ctx_t ctx, size_t size); +void *vapi_msg_alloc (vapi_ctx_t ctx, size_t size); /** * @brief free a vapi message @@ -66,7 +66,7 @@ extern "C" * @param ctx opaque vapi context * @param msg message to be freed */ - void vapi_msg_free (vapi_ctx_t ctx, void *msg); +void vapi_msg_free (vapi_ctx_t ctx, void *msg); /** * @brief allocate vapi context @@ -75,18 +75,18 @@ extern "C" * * @return VAPI_OK on success, other error code on error */ - vapi_error_e vapi_ctx_alloc (vapi_ctx_t * result); +vapi_error_e vapi_ctx_alloc (vapi_ctx_t *result); /** * @brief free vapi context */ - void vapi_ctx_free (vapi_ctx_t ctx); +void vapi_ctx_free (vapi_ctx_t ctx); /** * @brief check if message identified by it's message id is known by the vpp to * which the connection is open */ - bool vapi_is_msg_available (vapi_ctx_t ctx, vapi_msg_id_t type); +bool vapi_is_msg_available (vapi_ctx_t ctx, vapi_msg_id_t type); /** * @brief connect to vpp @@ -101,11 +101,49 @@ extern "C" * * @return VAPI_OK on success, other error code on error */ - vapi_error_e vapi_connect (vapi_ctx_t ctx, const char *name, - const char *chroot_prefix, - int max_outstanding_requests, - int response_queue_size, vapi_mode_e mode, - bool handle_keepalives); +vapi_error_e vapi_connect (vapi_ctx_t ctx, const char *name, + const char *chroot_prefix, + int max_outstanding_requests, + int response_queue_size, vapi_mode_e mode, + bool handle_keepalives); + +/** + * @brief connect to vpp + * + * @param ctx opaque vapi context, must be allocated using vapi_ctx_alloc first + * @param name application name + * @param path shared memory prefix or path to unix socket + * @param max_outstanding_requests max number of outstanding requests queued + * @param response_queue_size size of the response queue + * @param mode mode of operation - blocking or nonblocking + * @param handle_keepalives - if true, automatically handle memclnt_keepalive + * @param use_uds - if true, use unix domain socket transport + * + * @return VAPI_OK on success, other error code on error + */ +vapi_error_e vapi_connect_ex (vapi_ctx_t ctx, const char *name, + const char *path, int max_outstanding_requests, + int response_queue_size, vapi_mode_e mode, + bool handle_keepalives, bool use_uds); + +/** + * @brief connect to vpp from a client in same process + * @remark This MUST be called from a separate thread. If called + * from the main thread, it will deadlock. + * + * @param ctx opaque vapi context, must be allocated using vapi_ctx_alloc first + * @param name application name + * @param max_outstanding_requests max number of outstanding requests queued + * @param response_queue_size size of the response queue + * @param mode mode of operation - blocking or nonblocking + * @param handle_keepalives - if true, automatically handle memclnt_keepalive + * + * @return VAPI_OK on success, other error code on error + */ +vapi_error_e vapi_connect_from_vpp (vapi_ctx_t ctx, const char *name, + int max_outstanding_requests, + int response_queue_size, vapi_mode_e mode, + bool handle_keepalives); /** * @brief disconnect from vpp @@ -114,7 +152,8 @@ extern "C" * * @return VAPI_OK on success, other error code on error */ - vapi_error_e vapi_disconnect (vapi_ctx_t ctx); +vapi_error_e vapi_disconnect (vapi_ctx_t ctx); +vapi_error_e vapi_disconnect_from_vpp (vapi_ctx_t ctx); /** * @brief get event file descriptor @@ -127,7 +166,7 @@ extern "C" * * @return VAPI_OK on success, other error code on error */ - vapi_error_e vapi_get_fd (vapi_ctx_t ctx, int *fd); +vapi_error_e vapi_get_fd (vapi_ctx_t ctx, int *fd); /** * @brief low-level api for sending messages to vpp @@ -140,7 +179,7 @@ extern "C" * * @return VAPI_OK on success, other error code on error */ - vapi_error_e vapi_send (vapi_ctx_t ctx, void *msg); +vapi_error_e vapi_send (vapi_ctx_t ctx, void *msg); /** * @brief low-level api for atomically sending two messages to vpp - either @@ -155,7 +194,7 @@ extern "C" * * @return VAPI_OK on success, other error code on error */ - vapi_error_e vapi_send2 (vapi_ctx_t ctx, void *msg1, void *msg2); +vapi_error_e vapi_send2 (vapi_ctx_t ctx, void *msg1, void *msg2); /** * @brief low-level api for reading messages from vpp @@ -171,25 +210,36 @@ extern "C" * * @return VAPI_OK on success, other error code on error */ - vapi_error_e vapi_recv (vapi_ctx_t ctx, void **msg, size_t * msg_size, - svm_q_conditional_wait_t cond, u32 time); +vapi_error_e vapi_recv (vapi_ctx_t ctx, void **msg, size_t *msg_size, + svm_q_conditional_wait_t cond, u32 time); /** - * @brief wait for connection to become readable or writable + * @brief wait for connection to become readable * * @param ctx opaque vapi context - * @param mode type of property to wait for - readability, writability or both * * @return VAPI_OK on success, other error code on error */ - vapi_error_e vapi_wait (vapi_ctx_t ctx, vapi_wait_mode_e mode); +vapi_error_e vapi_wait (vapi_ctx_t ctx); /** * @brief pick next message sent by vpp and call the appropriate callback * + * @note if using block mode, it will be blocked indefinitely until the next + * msg available. If using non-blocking mode, it will block for time_wait + * seconds until the next msg available if time_wait > 0, or does not block if + * time_wait == 0. + * * @return VAPI_OK on success, other error code on error */ - vapi_error_e vapi_dispatch_one (vapi_ctx_t ctx); +vapi_error_e vapi_dispatch_one_timedwait (vapi_ctx_t ctx, u32 wait_time); + +/** + * @brief pick next message sent by vpp and call the appropriate callback + * + * @return VAPI_OK on success, other error code on error + */ +vapi_error_e vapi_dispatch_one (vapi_ctx_t ctx); /** * @brief loop vapi_dispatch_one until responses to all currently outstanding @@ -197,19 +247,19 @@ extern "C" * * @note the dispatch loop is interrupted if any error is encountered or * returned from the callback, in which case this error is returned as the - * result of vapi_dispatch. In this case it might be necessary to call dispatch - * again to process the remaining messages. Returning VAPI_EUSER from - * a callback allows the user to break the dispatch loop (and distinguish - * this case in the calling code from other failures). VAPI never returns - * VAPI_EUSER on its own. + * result of vapi_dispatch. In this case it might be necessary to call + * dispatch again to process the remaining messages. Returning VAPI_EUSER + * from a callback allows the user to break the dispatch loop (and + * distinguish this case in the calling code from other failures). VAPI never + * returns VAPI_EUSER on its own. * * @return VAPI_OK on success, other error code on error */ - vapi_error_e vapi_dispatch (vapi_ctx_t ctx); +vapi_error_e vapi_dispatch (vapi_ctx_t ctx); /** generic vapi event callback */ - typedef vapi_error_e (*vapi_event_cb) (vapi_ctx_t ctx, void *callback_ctx, - void *payload); +typedef vapi_error_e (*vapi_event_cb) (vapi_ctx_t ctx, void *callback_ctx, + void *payload); /** * @brief set event callback to call when message with given id is dispatched @@ -219,8 +269,8 @@ extern "C" * @param callback callback * @param callback_ctx context pointer stored and passed to callback */ - void vapi_set_event_cb (vapi_ctx_t ctx, vapi_msg_id_t id, - vapi_event_cb callback, void *callback_ctx); +void vapi_set_event_cb (vapi_ctx_t ctx, vapi_msg_id_t id, + vapi_event_cb callback, void *callback_ctx); /** * @brief clear event callback for given message id @@ -228,12 +278,12 @@ extern "C" * @param ctx opaque vapi context * @param id message id */ - void vapi_clear_event_cb (vapi_ctx_t ctx, vapi_msg_id_t id); +void vapi_clear_event_cb (vapi_ctx_t ctx, vapi_msg_id_t id); /** generic vapi event callback */ - typedef vapi_error_e (*vapi_generic_event_cb) (vapi_ctx_t ctx, - void *callback_ctx, - vapi_msg_id_t id, void *msg); +typedef vapi_error_e (*vapi_generic_event_cb) (vapi_ctx_t ctx, + void *callback_ctx, + vapi_msg_id_t id, void *msg); /** * @brief set generic event callback * @@ -244,16 +294,29 @@ extern "C" * @param callback callback * @param callback_ctx context pointer stored and passed to callback */ - void vapi_set_generic_event_cb (vapi_ctx_t ctx, - vapi_generic_event_cb callback, - void *callback_ctx); +void vapi_set_generic_event_cb (vapi_ctx_t ctx, vapi_generic_event_cb callback, + void *callback_ctx); /** * @brief clear generic event callback * * @param ctx opaque vapi context */ - void vapi_clear_generic_event_cb (vapi_ctx_t ctx); +void vapi_clear_generic_event_cb (vapi_ctx_t ctx); + +/** + * @brief signal RX thread to exit + * + * @note This adds a message to the client input queue that indicates that + * an RX thread should stop processing incoming messages and exit. If an + * application has an RX thread which sleeps while waiting for incoming + * messages using vapi_wait(), this call will allow the application to + * wake up from the vapi_wait() call and figure out that it should stop + * running. + * + * @param ctx opaque vapi context + */ +void vapi_stop_rx_thread (vapi_ctx_t ctx); #ifdef __cplusplus } diff --git a/src/vpp-api/vapi/vapi.hpp b/src/vpp-api/vapi/vapi.hpp index a1e33a93fd4..34d8f97ad89 100644 --- a/src/vpp-api/vapi/vapi.hpp +++ b/src/vpp-api/vapi/vapi.hpp @@ -140,6 +140,10 @@ private: template <typename Req, typename Resp, typename... Args> friend class Dump; + template <typename Req, typename Resp, typename StreamMessage, + typename... Args> + friend class Stream; + template <typename M> friend class Event_registration; }; @@ -199,13 +203,14 @@ public: * * @return VAPI_OK on success, other error code on error */ - vapi_error_e connect (const char *name, const char *chroot_prefix, - int max_outstanding_requests, int response_queue_size, - bool handle_keepalives = true) + vapi_error_e + connect (const char *name, const char *chroot_prefix, + int max_outstanding_requests, int response_queue_size, + bool handle_keepalives = true, bool use_uds = false) { - return vapi_connect (vapi_ctx, name, chroot_prefix, - max_outstanding_requests, response_queue_size, - VAPI_MODE_BLOCKING, handle_keepalives); + return vapi_connect_ex (vapi_ctx, name, chroot_prefix, + max_outstanding_requests, response_queue_size, + VAPI_MODE_BLOCKING, handle_keepalives, use_uds); } /** @@ -417,7 +422,7 @@ private: void unregister_request (Common_req *request) { std::lock_guard<std::recursive_mutex> lock (requests_mutex); - std::remove (requests.begin (), requests.end (), request); + requests.erase (std::remove (requests.begin (), requests.end (), request)); } template <typename M> void register_event (Event_registration<M> *event) @@ -451,6 +456,10 @@ private: template <typename Req, typename Resp, typename... Args> friend class Dump; + template <typename Req, typename Resp, typename StreamMessage, + typename... Args> + friend class Stream; + template <typename M> friend class Result_set; template <typename M> friend class Event_registration; @@ -497,6 +506,10 @@ template <typename Req, typename Resp, typename... Args> class Request; template <typename Req, typename Resp, typename... Args> class Dump; +template <typename Req, typename Resp, typename StreamMessage, + typename... Args> +class Stream; + template <class, class = void> struct vapi_has_payload_trait : std::false_type { }; @@ -627,6 +640,10 @@ private: template <typename Req, typename Resp, typename... Args> friend class Dump; + template <typename Req, typename Resp, typename StreamMessage, + typename... Args> + friend class Stream; + template <typename X> friend class Event_registration; template <typename X> friend class Result_set; @@ -644,10 +661,11 @@ class Request : public Common_req { public: Request (Connection &con, Args... args, - std::function<vapi_error_e (Request<Req, Resp, Args...> &)> - callback = nullptr) - : Common_req{con}, callback{callback}, - request{con, vapi_alloc<Req> (con, args...)}, response{con, nullptr} + std::function<vapi_error_e (Request<Req, Resp, Args...> &)> + callback = nullptr) + : Common_req{ con }, callback{ std::move (callback) }, + request{ con, vapi_alloc<Req> (con, args...) }, response{ con, + nullptr } { } @@ -772,12 +790,96 @@ private: bool complete; std::vector<Msg<M>, typename Msg<M>::Msg_allocator> set; + template <typename Req, typename Resp, typename StreamMessage, + typename... Args> + friend class Stream; + template <typename Req, typename Resp, typename... Args> friend class Dump; template <typename X> friend class Event_registration; }; /** + * Class representing a RPC request - zero or more identical responses to a + * single request message with a response + */ +template <typename Req, typename Resp, typename StreamMessage, + typename... Args> +class Stream : public Common_req +{ +public: + Stream ( + Connection &con, Args... args, + std::function<vapi_error_e (Stream<Req, Resp, StreamMessage, Args...> &)> + cb = nullptr) + : Common_req{ con }, request{ con, vapi_alloc<Req> (con, args...) }, + response{ con, nullptr }, result_set{ con }, callback{ std::move (cb) } + { + } + + Stream (const Stream &) = delete; + + virtual ~Stream () {} + + virtual std::tuple<vapi_error_e, bool> + assign_response (vapi_msg_id_t id, void *shm_data) + { + if (id == response.get_msg_id ()) + { + response.assign_response (id, shm_data); + result_set.mark_complete (); + set_response_state (RESPONSE_READY); + if (nullptr != callback) + { + return std::make_pair (callback (*this), true); + } + return std::make_pair (VAPI_OK, true); + } + else + { + result_set.assign_response (id, shm_data); + } + return std::make_pair (VAPI_OK, false); + } + + vapi_error_e + execute () + { + return con.send (this); + } + + const Msg<Req> & + get_request (void) + { + return request; + } + + const Msg<Resp> & + get_response (void) + { + return response; + } + + using resp_type = typename Msg<StreamMessage>::shm_data_type; + + const Result_set<StreamMessage> & + get_result_set (void) const + { + return result_set; + } + +private: + Msg<Req> request; + Msg<Resp> response; + Result_set<StreamMessage> result_set; + std::function<vapi_error_e (Stream<Req, Resp, StreamMessage, Args...> &)> + callback; + + friend class Connection; + friend class Result_set<StreamMessage>; +}; + +/** * Class representing a dump request - zero or more identical responses to a * single request message */ @@ -786,10 +888,10 @@ class Dump : public Common_req { public: Dump (Connection &con, Args... args, - std::function<vapi_error_e (Dump<Req, Resp, Args...> &)> callback = - nullptr) - : Common_req{con}, request{con, vapi_alloc<Req> (con, args...)}, - result_set{con}, callback{callback} + std::function<vapi_error_e (Dump<Req, Resp, Args...> &)> callback = + nullptr) + : Common_req{ con }, request{ con, vapi_alloc<Req> (con, args...) }, + result_set{ con }, callback{ std::move (callback) } { } @@ -853,9 +955,9 @@ template <typename M> class Event_registration : public Common_req { public: Event_registration ( - Connection &con, - std::function<vapi_error_e (Event_registration<M> &)> callback = nullptr) - : Common_req{con}, result_set{con}, callback{callback} + Connection &con, + std::function<vapi_error_e (Event_registration<M> &)> callback = nullptr) + : Common_req{ con }, result_set{ con }, callback{ std::move (callback) } { if (!con.is_msg_available (M::get_msg_id ())) { diff --git a/src/vpp-api/vapi/vapi_c_gen.py b/src/vpp-api/vapi/vapi_c_gen.py index f0a284ccbc1..9d1efb5e438 100755 --- a/src/vpp-api/vapi/vapi_c_gen.py +++ b/src/vpp-api/vapi/vapi_c_gen.py @@ -1,11 +1,21 @@ #!/usr/bin/env python3 import argparse +import inspect import os import sys import logging -from vapi_json_parser import Field, Struct, Enum, Union, Message, JsonParser,\ - SimpleType, StructType, Alias +from vapi_json_parser import ( + Field, + Struct, + Enum, + Union, + Message, + JsonParser, + SimpleType, + StructType, + Alias, +) class CField(Field): @@ -13,7 +23,7 @@ class CField(Field): return "vapi_type_%s" % self.name def get_c_def(self): - if self.type.get_c_name() == 'vl_api_string_t': + if self.type.get_c_name() == "string": if self.len: return "u8 %s[%d];" % (self.name, self.len) else: @@ -27,50 +37,63 @@ class CField(Field): def get_swap_to_be_code(self, struct, var): if self.len is not None and type(self.len) != dict: if self.len > 0: - return "do { unsigned i; for (i = 0; i < %d; ++i) { %s } }"\ - " while(0);" % ( - self.len, - self.type.get_swap_to_be_code(struct, "%s[i]" % var)) + return ( + "do { unsigned i; for (i = 0; i < %d; ++i) { %s } }" + " while(0);" + % (self.len, self.type.get_swap_to_be_code(struct, "%s[i]" % var)) + ) else: if self.nelem_field.needs_byte_swap(): nelem_field = "%s(%s%s)" % ( self.nelem_field.type.get_swap_to_host_func_name(), - struct, self.nelem_field.name) + struct, + self.nelem_field.name, + ) else: nelem_field = "%s%s" % (struct, self.nelem_field.name) return ( "do { unsigned i; for (i = 0; i < %s; ++i) { %s } }" - " while(0);" % - (nelem_field, self.type.get_swap_to_be_code( - struct, "%s[i]" % var))) + " while(0);" + % ( + nelem_field, + self.type.get_swap_to_be_code(struct, "%s[i]" % var), + ) + ) return self.type.get_swap_to_be_code(struct, "%s" % var) def get_swap_to_host_code(self, struct, var): if self.len is not None and type(self.len) != dict: if self.len > 0: - return "do { unsigned i; for (i = 0; i < %d; ++i) { %s } }"\ - " while(0);" % ( - self.len, - self.type.get_swap_to_host_code(struct, "%s[i]" % var)) + return ( + "do { unsigned i; for (i = 0; i < %d; ++i) { %s } }" + " while(0);" + % (self.len, self.type.get_swap_to_host_code(struct, "%s[i]" % var)) + ) else: # nelem_field already swapped to host here... return ( "do { unsigned i; for (i = 0; i < %s%s; ++i) { %s } }" - " while(0);" % - (struct, self.nelem_field.name, - self.type.get_swap_to_host_code( - struct, "%s[i]" % var))) + " while(0);" + % ( + struct, + self.nelem_field.name, + self.type.get_swap_to_host_code(struct, "%s[i]" % var), + ) + ) return self.type.get_swap_to_host_code(struct, "%s" % var) def needs_byte_swap(self): return self.type.needs_byte_swap() - def get_vla_field_length_name(self, path): + def get_vla_parameter_name(self, path): return "%s_%s_array_size" % ("_".join(path), self.name) + def get_vla_field_name(self, path): + return ".".join(path + [self.nelem_field.name]) + def get_alloc_vla_param_names(self, path): if self.is_vla(): - result = [self.get_vla_field_length_name(path)] + result = [self.get_vla_parameter_name(path)] else: result = [] if self.type.has_vla(): @@ -78,26 +101,38 @@ class CField(Field): result.extend(t) return result - def get_vla_calc_size_code(self, prefix, path): + def get_vla_calc_size_code(self, prefix, path, is_alloc): if self.is_vla(): - result = ["sizeof(%s.%s[0]) * %s" % ( - ".".join([prefix] + path), - self.name, - self.get_vla_field_length_name(path))] + result = [ + "sizeof(%s.%s[0]) * %s" + % ( + ".".join([prefix] + path), + self.name, + ( + self.get_vla_parameter_name(path) + if is_alloc + else "%s.%s" % (prefix, self.get_vla_field_name(path)) + ), + ) + ] else: result = [] if self.type.has_vla(): - t = self.type.get_vla_calc_size_code(prefix, path + [self.name]) + t = self.type.get_vla_calc_size_code(prefix, path + [self.name], is_alloc) result.extend(t) return result def get_vla_assign_code(self, prefix, path): result = [] if self.is_vla(): - result.append("%s.%s = %s" % ( - ".".join([prefix] + path), - self.nelem_field.name, - self.get_vla_field_length_name(path))) + result.append( + "%s.%s = %s" + % ( + ".".join([prefix] + path), + self.nelem_field.name, + self.get_vla_parameter_name(path), + ) + ) if self.type.has_vla(): t = self.type.get_vla_assign_code(prefix, path + [self.name]) result.extend(t) @@ -111,52 +146,74 @@ class CAlias(CField): def get_c_def(self): if self.len is not None: return "typedef %s vapi_type_%s[%d];" % ( - self.type.get_c_name(), self.name, self.len) + self.type.get_c_name(), + self.name, + self.len, + ) else: - return "typedef %s vapi_type_%s;" % ( - self.type.get_c_name(), self.name) + return "typedef %s vapi_type_%s;" % (self.type.get_c_name(), self.name) class CStruct(Struct): def get_c_def(self): - return "\n".join([ - "typedef struct __attribute__((__packed__)) {\n%s" % ( - "\n".join([" %s" % x.get_c_def() - for x in self.fields])), - "} %s;" % self.get_c_name()]) + return "\n".join( + [ + "typedef struct __attribute__((__packed__)) {\n%s" + % ("\n".join([" %s" % x.get_c_def() for x in self.fields])), + "} %s;" % self.get_c_name(), + ] + ) def get_vla_assign_code(self, prefix, path): - return [x for f in self.fields if f.has_vla() - for x in f.get_vla_assign_code(prefix, path)] + return [ + x + for f in self.fields + if f.has_vla() + for x in f.get_vla_assign_code(prefix, path) + ] def get_alloc_vla_param_names(self, path): - return [x for f in self.fields - if f.has_vla() - for x in f.get_alloc_vla_param_names(path)] - - def get_vla_calc_size_code(self, prefix, path): - return [x for f in self.fields if f.has_vla() - for x in f.get_vla_calc_size_code(prefix, path)] + return [ + x + for f in self.fields + if f.has_vla() + for x in f.get_alloc_vla_param_names(path) + ] + def get_vla_calc_size_code(self, prefix, path, is_alloc): + return [ + x + for f in self.fields + if f.has_vla() + for x in f.get_vla_calc_size_code(prefix, path, is_alloc) + ] -class CSimpleType (SimpleType): +class CSimpleType(SimpleType): swap_to_be_dict = { - 'i16': 'htobe16', 'u16': 'htobe16', - 'i32': 'htobe32', 'u32': 'htobe32', - 'i64': 'htobe64', 'u64': 'htobe64', + "i16": "htobe16", + "u16": "htobe16", + "i32": "htobe32", + "u32": "htobe32", + "i64": "htobe64", + "u64": "htobe64", } swap_to_host_dict = { - 'i16': 'be16toh', 'u16': 'be16toh', - 'i32': 'be32toh', 'u32': 'be32toh', - 'i64': 'be64toh', 'u64': 'be64toh', + "i16": "be16toh", + "u16": "be16toh", + "i32": "be32toh", + "u32": "be32toh", + "i64": "be64toh", + "u64": "be64toh", } __packed = "__attribute__((packed))" pack_dict = { - 'i8': __packed, 'u8': __packed, - 'i16': __packed, 'u16': __packed, + "i8": __packed, + "u8": __packed, + "i16": __packed, + "u16": __packed, } def get_c_name(self): @@ -173,15 +230,21 @@ class CSimpleType (SimpleType): def get_swap_to_be_code(self, struct, var, cast=None): x = "%s%s" % (struct, var) - return "%s = %s%s(%s);" % (x, - "(%s)" % cast if cast else "", - self.get_swap_to_be_func_name(), x) + return "%s = %s%s(%s);" % ( + x, + "(%s)" % cast if cast else "", + self.get_swap_to_be_func_name(), + x, + ) def get_swap_to_host_code(self, struct, var, cast=None): x = "%s%s" % (struct, var) - return "%s = %s%s(%s);" % (x, - "(%s)" % cast if cast else "", - self.get_swap_to_host_func_name(), x) + return "%s = %s%s(%s);" % ( + x, + "(%s)" % cast if cast else "", + self.get_swap_to_host_func_name(), + x, + ) def needs_byte_swap(self): try: @@ -203,7 +266,7 @@ class CEnum(Enum): return "typedef enum {\n%s\n} %s %s;" % ( "\n".join([" %s = %s," % (i, j) for i, j in self.value_pairs]), self.type.get_packed(), - self.get_c_name() + self.get_c_name(), ) def needs_byte_swap(self): @@ -222,17 +285,18 @@ class CUnion(Union): def get_c_def(self): return "typedef union {\n%s\n} %s;" % ( - "\n".join([" %s %s;" % (i.get_c_name(), j) - for i, j in self.type_pairs]), - self.get_c_name() + "\n".join([" %s %s;" % (i.get_c_name(), j) for i, j in self.type_pairs]), + self.get_c_name(), ) def needs_byte_swap(self): return False -class CStructType (StructType, CStruct): +class CStructType(StructType, CStruct): def get_c_name(self): + if self.name == "vl_api_string_t": + return "vl_api_string_t" return "vapi_type_%s" % self.name def get_swap_to_be_func_name(self): @@ -242,27 +306,36 @@ class CStructType (StructType, CStruct): return "%s_ntoh" % self.get_c_name() def get_swap_to_be_func_decl(self): - return "void %s(%s *msg)" % ( - self.get_swap_to_be_func_name(), self.get_c_name()) + return "void %s(%s *msg)" % (self.get_swap_to_be_func_name(), self.get_c_name()) def get_swap_to_be_func_def(self): return "%s\n{\n%s\n}" % ( self.get_swap_to_be_func_decl(), - "\n".join([ - " %s" % p.get_swap_to_be_code("msg->", "%s" % p.name) - for p in self.fields if p.needs_byte_swap()]), + "\n".join( + [ + " %s" % p.get_swap_to_be_code("msg->", "%s" % p.name) + for p in self.fields + if p.needs_byte_swap() + ] + ), ) def get_swap_to_host_func_decl(self): return "void %s(%s *msg)" % ( - self.get_swap_to_host_func_name(), self.get_c_name()) + self.get_swap_to_host_func_name(), + self.get_c_name(), + ) def get_swap_to_host_func_def(self): return "%s\n{\n%s\n}" % ( self.get_swap_to_host_func_decl(), - "\n".join([ - " %s" % p.get_swap_to_host_code("msg->", "%s" % p.name) - for p in self.fields if p.needs_byte_swap()]), + "\n".join( + [ + " %s" % p.get_swap_to_host_code("msg->", "%s" % p.name) + for p in self.fields + if p.needs_byte_swap() + ] + ), ) def get_swap_to_be_code(self, struct, var): @@ -278,13 +351,11 @@ class CStructType (StructType, CStruct): return False -class CMessage (Message): +class CMessage(Message): def __init__(self, logger, definition, json_parser): super(CMessage, self).__init__(logger, definition, json_parser) self.payload_members = [ - " %s" % p.get_c_def() - for p in self.fields - if p.type != self.header + " %s" % p.get_c_def() for p in self.fields if p.type != self.header ] def has_payload(self): @@ -303,46 +374,67 @@ class CMessage (Message): return "vapi_alloc_%s" % self.name def get_alloc_vla_param_names(self): - return [x for f in self.fields - if f.has_vla() - for x in f.get_alloc_vla_param_names([])] + return [ + x + for f in self.fields + if f.has_vla() + for x in f.get_alloc_vla_param_names([]) + ] def get_alloc_func_decl(self): return "%s* %s(struct vapi_ctx_s *ctx%s)" % ( self.get_c_name(), self.get_alloc_func_name(), - "".join([", size_t %s" % n for n in - self.get_alloc_vla_param_names()])) + "".join([", size_t %s" % n for n in self.get_alloc_vla_param_names()]), + ) def get_alloc_func_def(self): extra = [] - if self.header.has_field('client_index'): - extra.append( - " msg->header.client_index = vapi_get_client_index(ctx);") - if self.header.has_field('context'): + if self.header.has_field("client_index"): + extra.append(" msg->header.client_index = vapi_get_client_index(ctx);") + if self.header.has_field("context"): extra.append(" msg->header.context = 0;") - return "\n".join([ - "%s" % self.get_alloc_func_decl(), - "{", - " %s *msg = NULL;" % self.get_c_name(), - " const size_t size = sizeof(%s)%s;" % ( - self.get_c_name(), - "".join([" + %s" % x for f in self.fields if f.has_vla() - for x in f.get_vla_calc_size_code("msg->payload", - [])])), - " /* cast here required to play nicely with C++ world ... */", - " msg = (%s*)vapi_msg_alloc(ctx, size);" % self.get_c_name(), - " if (!msg) {", - " return NULL;", - " }", - ] + extra + [ - " msg->header._vl_msg_id = vapi_lookup_vl_msg_id(ctx, %s);" % - self.get_msg_id_name(), - "".join([" %s;\n" % line - for f in self.fields if f.has_vla() - for line in f.get_vla_assign_code("msg->payload", [])]), - " return msg;", - "}"]) + return "\n".join( + [ + "%s" % self.get_alloc_func_decl(), + "{", + " %s *msg = NULL;" % self.get_c_name(), + " const size_t size = sizeof(%s)%s;" + % ( + self.get_c_name(), + "".join( + [ + " + %s" % x + for f in self.fields + if f.has_vla() + for x in f.get_vla_calc_size_code( + "msg->payload", [], is_alloc=True + ) + ] + ), + ), + " /* cast here required to play nicely with C++ world ... */", + " msg = (%s*)vapi_msg_alloc(ctx, size);" % self.get_c_name(), + " if (!msg) {", + " return NULL;", + " }", + ] + + extra + + [ + " msg->header._vl_msg_id = vapi_lookup_vl_msg_id(ctx, %s);" + % self.get_msg_id_name(), + "".join( + [ + " %s;\n" % line + for f in self.fields + if f.has_vla() + for line in f.get_vla_assign_code("msg->payload", []) + ] + ), + " return msg;", + "}", + ] + ) def get_calc_msg_size_func_name(self): return "vapi_calc_%s_msg_size" % self.name @@ -350,43 +442,92 @@ class CMessage (Message): def get_calc_msg_size_func_decl(self): return "uword %s(%s *msg)" % ( self.get_calc_msg_size_func_name(), - self.get_c_name()) + self.get_c_name(), + ) def get_calc_msg_size_func_def(self): - return "\n".join([ - "%s" % self.get_calc_msg_size_func_decl(), - "{", - " return sizeof(*msg)%s;" % - "".join(["+ msg->payload.%s * sizeof(msg->payload.%s[0])" % ( - f.nelem_field.name, - f.name) - for f in self.fields - if f.nelem_field is not None - ]), - "}", - ]) + return "\n".join( + [ + "%s" % self.get_calc_msg_size_func_decl(), + "{", + " return sizeof(*msg)%s;" + % "".join( + [ + " + %s" % x + for f in self.fields + if f.has_vla() + for x in f.get_vla_calc_size_code( + "msg->payload", [], is_alloc=False + ) + ] + ), + "}", + ] + ) + + def get_verify_msg_size_func_name(self): + return f"vapi_verify_{self.name}_msg_size" + + def get_verify_msg_size_func_decl(self): + return "int %s(%s *msg, uword buf_size)" % ( + self.get_verify_msg_size_func_name(), + self.get_c_name(), + ) + + def get_verify_msg_size_func_def(self): + return inspect.cleandoc( + f""" + {self.get_verify_msg_size_func_decl()} + {{ + if (sizeof({self.get_c_name()}) > buf_size) + {{ + VAPI_ERR("Truncated '{self.name}' msg received, received %lu" + "bytes, expected %lu bytes.", buf_size, + sizeof({self.get_c_name()})); + return -1; + }} + if ({self.get_calc_msg_size_func_name()}(msg) > buf_size) + {{ + VAPI_ERR("Truncated '{self.name}' msg received, received %lu" + "bytes, expected %lu bytes.", buf_size, + {self.get_calc_msg_size_func_name()}(msg)); + return -1; + }} + return 0; + }} + """ + ) def get_c_def(self): if self.has_payload(): - return "\n".join([ - "typedef struct __attribute__ ((__packed__)) {", - "%s " % - "\n".join(self.payload_members), - "} %s;" % self.get_payload_struct_name(), - "", - "typedef struct __attribute__ ((__packed__)) {", - (" %s %s;" % (self.header.get_c_name(), - self.fields[0].name) - if self.header is not None else ""), - " %s payload;" % self.get_payload_struct_name(), - "} %s;" % self.get_c_name(), ]) + return "\n".join( + [ + "typedef struct __attribute__ ((__packed__)) {", + "%s " % "\n".join(self.payload_members), + "} %s;" % self.get_payload_struct_name(), + "", + "typedef struct __attribute__ ((__packed__)) {", + ( + " %s %s;" % (self.header.get_c_name(), self.fields[0].name) + if self.header is not None + else "" + ), + " %s payload;" % self.get_payload_struct_name(), + "} %s;" % self.get_c_name(), + ] + ) else: - return "\n".join([ - "typedef struct __attribute__ ((__packed__)) {", - (" %s %s;" % (self.header.get_c_name(), - self.fields[0].name) - if self.header is not None else ""), - "} %s;" % self.get_c_name(), ]) + return "\n".join( + [ + "typedef struct __attribute__ ((__packed__)) {", + ( + " %s %s;" % (self.header.get_c_name(), self.fields[0].name) + if self.header is not None + else "" + ), + "} %s;" % self.get_c_name(), + ] + ) def get_swap_payload_to_host_func_name(self): return "%s_payload_ntoh" % self.get_c_name() @@ -397,29 +538,37 @@ class CMessage (Message): def get_swap_payload_to_host_func_decl(self): return "void %s(%s *payload)" % ( self.get_swap_payload_to_host_func_name(), - self.get_payload_struct_name()) + self.get_payload_struct_name(), + ) def get_swap_payload_to_be_func_decl(self): return "void %s(%s *payload)" % ( self.get_swap_payload_to_be_func_name(), - self.get_payload_struct_name()) + self.get_payload_struct_name(), + ) def get_swap_payload_to_be_func_def(self): return "%s\n{\n%s\n}" % ( self.get_swap_payload_to_be_func_decl(), - "\n".join([ - " %s" % p.get_swap_to_be_code("payload->", "%s" % p.name) - for p in self.fields - if p.needs_byte_swap() and p.type != self.header]), + "\n".join( + [ + " %s" % p.get_swap_to_be_code("payload->", "%s" % p.name) + for p in self.fields + if p.needs_byte_swap() and p.type != self.header + ] + ), ) def get_swap_payload_to_host_func_def(self): return "%s\n{\n%s\n}" % ( self.get_swap_payload_to_host_func_decl(), - "\n".join([ - " %s" % p.get_swap_to_host_code("payload->", "%s" % p.name) - for p in self.fields - if p.needs_byte_swap() and p.type != self.header]), + "\n".join( + [ + " %s" % p.get_swap_to_host_code("payload->", "%s" % p.name) + for p in self.fields + if p.needs_byte_swap() and p.type != self.header + ] + ), ) def get_swap_to_host_func_name(self): @@ -430,150 +579,205 @@ class CMessage (Message): def get_swap_to_host_func_decl(self): return "void %s(%s *msg)" % ( - self.get_swap_to_host_func_name(), self.get_c_name()) + self.get_swap_to_host_func_name(), + self.get_c_name(), + ) def get_swap_to_be_func_decl(self): - return "void %s(%s *msg)" % ( - self.get_swap_to_be_func_name(), self.get_c_name()) + return "void %s(%s *msg)" % (self.get_swap_to_be_func_name(), self.get_c_name()) def get_swap_to_be_func_def(self): - return "\n".join([ - "%s" % self.get_swap_to_be_func_decl(), - "{", - (" VAPI_DBG(\"Swapping `%s'@%%p to big endian\", msg);" % - self.get_c_name()), - " %s(&msg->header);" % self.header.get_swap_to_be_func_name() - if self.header is not None else "", - " %s(&msg->payload);" % self.get_swap_payload_to_be_func_name() - if self.has_payload() else "", - "}", - ]) + return "\n".join( + [ + "%s" % self.get_swap_to_be_func_decl(), + "{", + ( + ' VAPI_DBG("Swapping `%s\'@%%p to big endian", msg);' + % self.get_c_name() + ), + ( + " %s(&msg->header);" % self.header.get_swap_to_be_func_name() + if self.header is not None + else "" + ), + ( + " %s(&msg->payload);" % self.get_swap_payload_to_be_func_name() + if self.has_payload() + else "" + ), + "}", + ] + ) def get_swap_to_host_func_def(self): - return "\n".join([ - "%s" % self.get_swap_to_host_func_decl(), - "{", - (" VAPI_DBG(\"Swapping `%s'@%%p to host byte order\", msg);" % - self.get_c_name()), - " %s(&msg->header);" % self.header.get_swap_to_host_func_name() - if self.header is not None else "", - " %s(&msg->payload);" % self.get_swap_payload_to_host_func_name() - if self.has_payload() else "", - "}", - ]) + return "\n".join( + [ + "%s" % self.get_swap_to_host_func_decl(), + "{", + ( + ' VAPI_DBG("Swapping `%s\'@%%p to host byte order", msg);' + % self.get_c_name() + ), + ( + " %s(&msg->header);" % self.header.get_swap_to_host_func_name() + if self.header is not None + else "" + ), + ( + " %s(&msg->payload);" % self.get_swap_payload_to_host_func_name() + if self.has_payload() + else "" + ), + "}", + ] + ) def get_op_func_name(self): return "vapi_%s" % self.name def get_op_func_decl(self): - if self.reply.has_payload(): - return "vapi_error_e %s(%s)" % ( - self.get_op_func_name(), - ",\n ".join([ - 'struct vapi_ctx_s *ctx', - '%s *msg' % self.get_c_name(), - 'vapi_error_e (*callback)(struct vapi_ctx_s *ctx', - ' void *callback_ctx', - ' vapi_error_e rv', - ' bool is_last', - ' %s *reply)' % - self.reply.get_payload_struct_name(), - 'void *callback_ctx', - ]) - ) - else: - return "vapi_error_e %s(%s)" % ( - self.get_op_func_name(), - ",\n ".join([ - 'struct vapi_ctx_s *ctx', - '%s *msg' % self.get_c_name(), - 'vapi_error_e (*callback)(struct vapi_ctx_s *ctx', - ' void *callback_ctx', - ' vapi_error_e rv', - ' bool is_last)', - 'void *callback_ctx', - ]) - ) + stream_param_lines = [] + if self.has_stream_msg: + stream_param_lines = [ + "vapi_error_e (*details_callback)(struct vapi_ctx_s *ctx", + " void *callback_ctx", + " vapi_error_e rv", + " bool is_last", + " %s *details)" + % self.stream_msg.get_payload_struct_name(), + "void *details_callback_ctx", + ] + + return "vapi_error_e %s(%s)" % ( + self.get_op_func_name(), + ",\n ".join( + [ + "struct vapi_ctx_s *ctx", + "%s *msg" % self.get_c_name(), + "vapi_error_e (*reply_callback)(struct vapi_ctx_s *ctx", + " void *callback_ctx", + " vapi_error_e rv", + " bool is_last", + " %s *reply)" + % self.reply.get_payload_struct_name(), + ] + + [ + "void *reply_callback_ctx", + ] + + stream_param_lines + ), + ) def get_op_func_def(self): - return "\n".join([ - "%s" % self.get_op_func_decl(), - "{", - " if (!msg || !callback) {", - " return VAPI_EINVAL;", - " }", - " if (vapi_is_nonblocking(ctx) && vapi_requests_full(ctx)) {", - " return VAPI_EAGAIN;", - " }", - " vapi_error_e rv;", - " if (VAPI_OK != (rv = vapi_producer_lock (ctx))) {", - " return rv;", - " }", - " u32 req_context = vapi_gen_req_context(ctx);", - " msg->header.context = req_context;", - " %s(msg);" % self.get_swap_to_be_func_name(), - (" if (VAPI_OK == (rv = vapi_send_with_control_ping " - "(ctx, msg, req_context))) {" - if self.reply_is_stream else - " if (VAPI_OK == (rv = vapi_send (ctx, msg))) {" - ), - (" vapi_store_request(ctx, req_context, %s, " - "(vapi_cb_t)callback, callback_ctx);" % - ("true" if self.reply_is_stream else "false")), - " if (VAPI_OK != vapi_producer_unlock (ctx)) {", - " abort (); /* this really shouldn't happen */", - " }", - " if (vapi_is_nonblocking(ctx)) {", - " rv = VAPI_OK;", - " } else {", - " rv = vapi_dispatch(ctx);", - " }", - " } else {", - " %s(msg);" % self.get_swap_to_host_func_name(), - " if (VAPI_OK != vapi_producer_unlock (ctx)) {", - " abort (); /* this really shouldn't happen */", - " }", - " }", - " return rv;", - "}", - "", - ]) + param_check_lines = [" if (!msg || !reply_callback) {"] + store_request_lines = [ + " vapi_store_request(ctx, req_context, %s, %s, " + % ( + self.reply.get_msg_id_name(), + "VAPI_REQUEST_DUMP" if self.reply_is_stream else "VAPI_REQUEST_REG", + ), + " (vapi_cb_t)reply_callback, reply_callback_ctx);", + ] + if self.has_stream_msg: + param_check_lines = [ + " if (!msg || !reply_callback || !details_callback) {" + ] + store_request_lines = [ + f" vapi_store_request(ctx, req_context, {self.stream_msg.get_msg_id_name()}, VAPI_REQUEST_STREAM, ", + " (vapi_cb_t)details_callback, details_callback_ctx);", + f" vapi_store_request(ctx, req_context, {self.reply.get_msg_id_name()}, VAPI_REQUEST_REG, ", + " (vapi_cb_t)reply_callback, reply_callback_ctx);", + ] + + return "\n".join( + [ + "%s" % self.get_op_func_decl(), + "{", + ] + + param_check_lines + + [ + " return VAPI_EINVAL;", + " }", + " if (vapi_is_nonblocking(ctx) && vapi_requests_full(ctx)) {", + " return VAPI_EAGAIN;", + " }", + " vapi_error_e rv;", + " if (VAPI_OK != (rv = vapi_producer_lock (ctx))) {", + " return rv;", + " }", + " u32 req_context = vapi_gen_req_context(ctx);", + " msg->header.context = req_context;", + " %s(msg);" % self.get_swap_to_be_func_name(), + ( + " if (VAPI_OK == (rv = vapi_send_with_control_ping " + "(ctx, msg, req_context))) {" + if (self.reply_is_stream and not self.has_stream_msg) + else " if (VAPI_OK == (rv = vapi_send (ctx, msg))) {" + ), + ] + + store_request_lines + + [ + " if (VAPI_OK != vapi_producer_unlock (ctx)) {", + " abort (); /* this really shouldn't happen */", + " }", + " if (vapi_is_nonblocking(ctx)) {", + " rv = VAPI_OK;", + " } else {", + " rv = vapi_dispatch(ctx);", + " }", + " } else {", + " %s(msg);" % self.get_swap_to_host_func_name(), + " if (VAPI_OK != vapi_producer_unlock (ctx)) {", + " abort (); /* this really shouldn't happen */", + " }", + " }", + " return rv;", + "}", + "", + ] + ) def get_event_cb_func_decl(self): if not self.is_reply and not self.is_event: - raise Exception( - "Cannot register event callback for non-reply message") + raise Exception("Cannot register event callback for non-reply message") if self.has_payload(): - return "\n".join([ - "void vapi_set_%s_event_cb (" % - self.get_c_name(), - " struct vapi_ctx_s *ctx, ", - (" vapi_error_e (*callback)(struct vapi_ctx_s *ctx, " - "void *callback_ctx, %s *payload)," % - self.get_payload_struct_name()), - " void *callback_ctx)", - ]) + return "\n".join( + [ + "void vapi_set_%s_event_cb (" % self.get_c_name(), + " struct vapi_ctx_s *ctx, ", + ( + " vapi_error_e (*callback)(struct vapi_ctx_s *ctx, " + "void *callback_ctx, %s *payload)," + % self.get_payload_struct_name() + ), + " void *callback_ctx)", + ] + ) else: - return "\n".join([ - "void vapi_set_%s_event_cb (" % - self.get_c_name(), - " struct vapi_ctx_s *ctx, ", - " vapi_error_e (*callback)(struct vapi_ctx_s *ctx, " - "void *callback_ctx),", - " void *callback_ctx)", - ]) + return "\n".join( + [ + "void vapi_set_%s_event_cb (" % self.get_c_name(), + " struct vapi_ctx_s *ctx, ", + " vapi_error_e (*callback)(struct vapi_ctx_s *ctx, " + "void *callback_ctx),", + " void *callback_ctx)", + ] + ) def get_event_cb_func_def(self): if not self.is_reply and not self.is_event: - raise Exception( - "Cannot register event callback for non-reply function") - return "\n".join([ - "%s" % self.get_event_cb_func_decl(), - "{", - (" vapi_set_event_cb(ctx, %s, (vapi_event_cb)callback, " - "callback_ctx);" % - self.get_msg_id_name()), - "}"]) + raise Exception("Cannot register event callback for non-reply function") + return "\n".join( + [ + "%s" % self.get_event_cb_func_decl(), + "{", + ( + " vapi_set_event_cb(ctx, %s, (vapi_event_cb)callback, " + "callback_ctx);" % self.get_msg_id_name() + ), + "}", + ] + ) def get_c_metadata_struct_name(self): return "__vapi_metadata_%s" % self.name @@ -581,53 +785,45 @@ class CMessage (Message): def get_c_constructor(self): has_context = False if self.header is not None: - has_context = self.header.has_field('context') - return '\n'.join([ - 'static void __attribute__((constructor)) __vapi_constructor_%s()' - % self.name, - '{', - ' static const char name[] = "%s";' % self.name, - ' static const char name_with_crc[] = "%s_%s";' - % (self.name, self.crc[2:]), - ' static vapi_message_desc_t %s = {' % - self.get_c_metadata_struct_name(), - ' name,', - ' sizeof(name) - 1,', - ' name_with_crc,', - ' sizeof(name_with_crc) - 1,', - ' true,' if has_context else ' false,', - ' offsetof(%s, context),' % self.header.get_c_name() - if has_context else ' 0,', - (' offsetof(%s, payload),' % self.get_c_name()) - if self.has_payload() else ' VAPI_INVALID_MSG_ID,', - ' sizeof(%s),' % self.get_c_name(), - ' (generic_swap_fn_t)%s,' % self.get_swap_to_be_func_name(), - ' (generic_swap_fn_t)%s,' % self.get_swap_to_host_func_name(), - ' VAPI_INVALID_MSG_ID,', - ' };', - '', - ' %s = vapi_register_msg(&%s);' % - (self.get_msg_id_name(), self.get_c_metadata_struct_name()), - ' VAPI_DBG("Assigned msg id %%d to %s", %s);' % - (self.name, self.get_msg_id_name()), - '}', - ]) - - -vapi_send_with_control_ping = """ -static inline vapi_error_e -vapi_send_with_control_ping (vapi_ctx_t ctx, void *msg, u32 context) -{ - vapi_msg_control_ping *ping = vapi_alloc_control_ping (ctx); - if (!ping) - { - return VAPI_ENOMEM; - } - ping->header.context = context; - vapi_msg_control_ping_hton (ping); - return vapi_send2 (ctx, msg, ping); -} -""" + has_context = self.header.has_field("context") + return "\n".join( + [ + "static void __attribute__((constructor)) __vapi_constructor_%s()" + % self.name, + "{", + ' static const char name[] = "%s";' % self.name, + ' static const char name_with_crc[] = "%s_%s";' + % (self.name, self.crc[2:]), + " static vapi_message_desc_t %s = {" + % self.get_c_metadata_struct_name(), + " name,", + " sizeof(name) - 1,", + " name_with_crc,", + " sizeof(name_with_crc) - 1,", + " true," if has_context else " false,", + ( + " offsetof(%s, context)," % self.header.get_c_name() + if has_context + else " 0," + ), + ( + (" offsetof(%s, payload)," % self.get_c_name()) + if self.has_payload() + else " VAPI_INVALID_MSG_ID," + ), + " (verify_msg_size_fn_t)%s," % self.get_verify_msg_size_func_name(), + " (generic_swap_fn_t)%s," % self.get_swap_to_be_func_name(), + " (generic_swap_fn_t)%s," % self.get_swap_to_host_func_name(), + " VAPI_INVALID_MSG_ID,", + " };", + "", + " %s = vapi_register_msg(&%s);" + % (self.get_msg_id_name(), self.get_c_metadata_struct_name()), + ' VAPI_DBG("Assigned msg id %%d to %s", %s);' + % (self.name, self.get_msg_id_name()), + "}", + ] + ) def emit_definition(parser, json_file, emitted, o): @@ -640,12 +836,16 @@ def emit_definition(parser, json_file, emitted, o): emit_definition(parser, json_file, emitted, x) if hasattr(o, "reply"): emit_definition(parser, json_file, emitted, o.reply) + if hasattr(o, "stream_msg"): + emit_definition(parser, json_file, emitted, o.stream_msg) if hasattr(o, "get_c_def"): - if (o not in parser.enums_by_json[json_file] and - o not in parser.types_by_json[json_file] and - o not in parser.unions_by_json[json_file] and - o.name not in parser.messages_by_json[json_file] and - o not in parser.aliases_by_json[json_file]): + if ( + o not in parser.enums_by_json[json_file] + and o not in parser.types_by_json[json_file] + and o not in parser.unions_by_json[json_file] + and o.name not in parser.messages_by_json[json_file] + and o not in parser.aliases_by_json[json_file] + ): return guard = "defined_%s" % o.get_c_name() print("#ifndef %s" % guard) @@ -655,25 +855,25 @@ def emit_definition(parser, json_file, emitted, o): function_attrs = "static inline " if o.name in parser.messages_by_json[json_file]: if o.has_payload(): - print("%s%s" % (function_attrs, - o.get_swap_payload_to_be_func_def())) + print("%s%s" % (function_attrs, o.get_swap_payload_to_be_func_def())) print("") - print("%s%s" % (function_attrs, - o.get_swap_payload_to_host_func_def())) + print("%s%s" % (function_attrs, o.get_swap_payload_to_host_func_def())) print("") print("%s%s" % (function_attrs, o.get_swap_to_be_func_def())) print("") print("%s%s" % (function_attrs, o.get_swap_to_host_func_def())) print("") print("%s%s" % (function_attrs, o.get_calc_msg_size_func_def())) - if not o.is_reply and not o.is_event: + print("") + print("%s%s" % (function_attrs, o.get_verify_msg_size_func_def())) + if not o.is_reply and not o.is_event and not o.is_stream: print("") print("%s%s" % (function_attrs, o.get_alloc_func_def())) print("") print("%s%s" % (function_attrs, o.get_op_func_def())) print("") print("%s" % o.get_c_constructor()) - if o.is_reply or o.is_event: + if (o.is_reply or o.is_event) and not o.is_stream: print("") print("%s%s;" % (function_attrs, o.get_event_cb_func_def())) elif hasattr(o, "get_swap_to_be_func_def"): @@ -691,7 +891,12 @@ def gen_json_unified_header(parser, logger, j, io, name): orig_stdout = sys.stdout sys.stdout = io include_guard = "__included_%s" % ( - j.replace(".", "_").replace("/", "_").replace("-", "_").replace("+", "_")) + j.replace(".", "_") + .replace("/", "_") + .replace("-", "_") + .replace("+", "_") + .replace("@", "_") + ) print("#ifndef %s" % include_guard) print("#define %s" % include_guard) print("") @@ -703,24 +908,48 @@ def gen_json_unified_header(parser, logger, j, io, name): print("#include <vapi/vapi_dbg.h>") print("") print("#ifdef __cplusplus") - print("extern \"C\" {") + print('extern "C" {') print("#endif") - if name == "vpe.api.vapi.h": + + print("#ifndef __vl_api_string_swap_fns_defined__") + print("#define __vl_api_string_swap_fns_defined__") + print("") + print("#include <vlibapi/api_types.h>") + print("") + function_attrs = "static inline " + o = parser.types["vl_api_string_t"] + print("%s%s" % (function_attrs, o.get_swap_to_be_func_def())) + print("") + print("%s%s" % (function_attrs, o.get_swap_to_host_func_def())) + print("") + print("#endif //__vl_api_string_swap_fns_defined__") + + if name == "memclnt.api.vapi.h": print("") - print("static inline vapi_error_e vapi_send_with_control_ping " - "(vapi_ctx_t ctx, void * msg, u32 context);") + print( + "static inline vapi_error_e vapi_send_with_control_ping " + "(vapi_ctx_t ctx, void * msg, u32 context);" + ) + elif name == "vlib.api.vapi.h": + print("#include <vapi/memclnt.api.vapi.h>") else: - print("#include <vapi/vpe.api.vapi.h>") + print("#include <vapi/vlib.api.vapi.h>") print("") for m in parser.messages_by_json[j].values(): print("extern vapi_msg_id_t %s;" % m.get_msg_id_name()) print("") - print("#define DEFINE_VAPI_MSG_IDS_%s\\" % - f.replace(".", "_").replace("/", "_").replace("-", "_").upper()) - print("\\\n".join([ - " vapi_msg_id_t %s;" % m.get_msg_id_name() - for m in parser.messages_by_json[j].values() - ])) + print( + "#define DEFINE_VAPI_MSG_IDS_%s\\" + % f.replace(".", "_").replace("/", "_").replace("-", "_").upper() + ) + print( + "\\\n".join( + [ + " vapi_msg_id_t %s;" % m.get_msg_id_name() + for m in parser.messages_by_json[j].values() + ] + ) + ) print("") print("") emitted = [] @@ -737,8 +966,22 @@ def gen_json_unified_header(parser, logger, j, io, name): print("") - if name == "vpe.api.vapi.h": - print("%s" % vapi_send_with_control_ping) + if name == "vlib.api.vapi.h": + vapi_send_with_control_ping_function = """ +static inline vapi_error_e +vapi_send_with_control_ping (vapi_ctx_t ctx, void *msg, u32 context) +{ + vapi_msg_control_ping *ping = vapi_alloc_control_ping (ctx); + if (!ping) + { + return VAPI_ENOMEM; + } + ping->header.context = context; + vapi_msg_control_ping_hton (ping); + return vapi_send2 (ctx, msg, ping); +} +""" + print("%s" % vapi_send_with_control_ping_function) print("") print("#ifdef __cplusplus") @@ -765,12 +1008,11 @@ def gen_c_unified_headers(parser, logger, prefix, remove_path): d, f = os.path.split(j) else: f = j - with open('%s%s' % (prefix, json_to_c_header_name(f)), "w") as io: - gen_json_unified_header( - parser, logger, j, io, json_to_c_header_name(f)) + with open("%s%s" % (prefix, json_to_c_header_name(f)), "w") as io: + gen_json_unified_header(parser, logger, j, io, json_to_c_header_name(f)) -if __name__ == '__main__': +if __name__ == "__main__": try: verbose = int(os.getenv("V", 0)) except: @@ -788,23 +1030,30 @@ if __name__ == '__main__': logger.setLevel(log_level) argparser = argparse.ArgumentParser(description="VPP C API generator") - argparser.add_argument('files', metavar='api-file', action='append', - type=str, help='json api file' - '(may be specified multiple times)') - argparser.add_argument('--prefix', action='store', default=None, - help='path prefix') - argparser.add_argument('--remove-path', action='store_true', - help='remove path from filename') + argparser.add_argument( + "files", + metavar="api-file", + action="append", + type=str, + help="json api file" "(may be specified multiple times)", + ) + argparser.add_argument("--prefix", action="store", default=None, help="path prefix") + argparser.add_argument( + "--remove-path", action="store_true", help="remove path from filename" + ) args = argparser.parse_args() - jsonparser = JsonParser(logger, args.files, - simple_type_class=CSimpleType, - enum_class=CEnum, - union_class=CUnion, - struct_type_class=CStructType, - field_class=CField, - message_class=CMessage, - alias_class=CAlias) + jsonparser = JsonParser( + logger, + args.files, + simple_type_class=CSimpleType, + enum_class=CEnum, + union_class=CUnion, + struct_type_class=CStructType, + field_class=CField, + message_class=CMessage, + alias_class=CAlias, + ) # not using the model of having separate generated header and code files # with generated symbols present in shared library (per discussion with diff --git a/src/vpp-api/vapi/vapi_c_test.c b/src/vpp-api/vapi/vapi_c_test.c index efa6a735611..7a0e462e40a 100644 --- a/src/vpp-api/vapi/vapi_c_test.c +++ b/src/vpp-api/vapi/vapi_c_test.c @@ -24,8 +24,11 @@ #include <check.h> #include <vppinfra/string.h> #include <vapi/vapi.h> +#include <vapi/memclnt.api.vapi.h> +#include <vapi/vlib.api.vapi.h> #include <vapi/vpe.api.vapi.h> #include <vapi/interface.api.vapi.h> +#include <vapi/mss_clamp.api.vapi.h> #include <vapi/l2.api.vapi.h> #include <fake.api.vapi.h> @@ -34,11 +37,13 @@ DEFINE_VAPI_MSG_IDS_VPE_API_JSON; DEFINE_VAPI_MSG_IDS_INTERFACE_API_JSON; +DEFINE_VAPI_MSG_IDS_MSS_CLAMP_API_JSON; DEFINE_VAPI_MSG_IDS_L2_API_JSON; DEFINE_VAPI_MSG_IDS_FAKE_API_JSON; static char *app_name = NULL; static char *api_prefix = NULL; +static bool use_uds = false; static const int max_outstanding_requests = 64; static const int response_queue_size = 32; @@ -61,8 +66,9 @@ START_TEST (test_invalid_values) ck_assert_ptr_eq (NULL, sv); rv = vapi_send (ctx, sv); ck_assert_int_eq (VAPI_EINVAL, rv); - rv = vapi_connect (ctx, app_name, api_prefix, max_outstanding_requests, - response_queue_size, VAPI_MODE_BLOCKING, true); + rv = + vapi_connect_ex (ctx, app_name, api_prefix, max_outstanding_requests, + response_queue_size, VAPI_MODE_BLOCKING, true, use_uds); ck_assert_int_eq (VAPI_OK, rv); rv = vapi_send (ctx, NULL); ck_assert_int_eq (VAPI_EINVAL, rv); @@ -363,8 +369,9 @@ START_TEST (test_connect) vapi_ctx_t ctx; vapi_error_e rv = vapi_ctx_alloc (&ctx); ck_assert_int_eq (VAPI_OK, rv); - rv = vapi_connect (ctx, app_name, api_prefix, max_outstanding_requests, - response_queue_size, VAPI_MODE_BLOCKING, true); + rv = + vapi_connect_ex (ctx, app_name, api_prefix, max_outstanding_requests, + response_queue_size, VAPI_MODE_BLOCKING, true, use_uds); ck_assert_int_eq (VAPI_OK, rv); rv = vapi_disconnect (ctx); ck_assert_int_eq (VAPI_OK, rv); @@ -380,8 +387,9 @@ setup_blocking (void) { vapi_error_e rv = vapi_ctx_alloc (&ctx); ck_assert_int_eq (VAPI_OK, rv); - rv = vapi_connect (ctx, app_name, api_prefix, max_outstanding_requests, - response_queue_size, VAPI_MODE_BLOCKING, true); + rv = + vapi_connect_ex (ctx, app_name, api_prefix, max_outstanding_requests, + response_queue_size, VAPI_MODE_BLOCKING, true, use_uds); ck_assert_int_eq (VAPI_OK, rv); } @@ -390,8 +398,9 @@ setup_nonblocking (void) { vapi_error_e rv = vapi_ctx_alloc (&ctx); ck_assert_int_eq (VAPI_OK, rv); - rv = vapi_connect (ctx, app_name, api_prefix, max_outstanding_requests, - response_queue_size, VAPI_MODE_NONBLOCKING, true); + rv = vapi_connect_ex (ctx, app_name, api_prefix, max_outstanding_requests, + response_queue_size, VAPI_MODE_NONBLOCKING, true, + use_uds); ck_assert_int_eq (VAPI_OK, rv); } @@ -479,6 +488,48 @@ sw_interface_dump_cb (struct vapi_ctx_s *ctx, void *callback_ctx, return VAPI_OK; } +vapi_error_e +vapi_mss_clamp_enable_disable_reply_cb ( + struct vapi_ctx_s *ctx, void *callback_ctx, vapi_error_e rv, bool is_last, + vapi_payload_mss_clamp_enable_disable_reply *reply) +{ + bool *x = callback_ctx; + *x = true; + return VAPI_OK; +} + +vapi_error_e +vapi_mss_clamp_get_reply_cb (struct vapi_ctx_s *ctx, void *callback_ctx, + vapi_error_e rv, bool is_last, + vapi_payload_mss_clamp_get_reply *reply) +{ + int *counter = callback_ctx; + ck_assert_int_gt (*counter, 0); // make sure details were called first + ++*counter; + ck_assert_int_eq (is_last, true); + printf ("Got mss clamp reply error %d\n", rv); + ck_assert_int_eq (rv, VAPI_OK); + printf ("counter is %d", *counter); + return VAPI_OK; +} + +vapi_error_e +vapi_mss_clamp_get_details_cb (struct vapi_ctx_s *ctx, void *callback_ctx, + vapi_error_e rv, bool is_last, + vapi_payload_mss_clamp_details *details) +{ + int *counter = callback_ctx; + ++*counter; + if (!is_last) + { + printf ("Got ipv4 mss clamp to %u for sw_if_index %u\n", + details->ipv4_mss, details->sw_if_index); + ck_assert_int_eq (details->ipv4_mss, 1000 + details->sw_if_index); + } + printf ("counter is %d", *counter); + return VAPI_OK; +} + START_TEST (test_loopbacks_1) { printf ("--- Create/delete loopbacks using blocking API ---\n"); @@ -501,8 +552,11 @@ START_TEST (test_loopbacks_1) for (i = 0; i < num_ifs; ++i) { vapi_msg_create_loopback *cl = vapi_alloc_create_loopback (ctx); - memcpy (cl->payload.mac_address, mac_addresses[i], - sizeof (cl->payload.mac_address)); + int j; + for (j = 0; j < 6; ++j) + { + cl->payload.mac_address[j] = mac_addresses[i][j]; + } vapi_error_e rv = vapi_create_loopback (ctx, cl, loopback_create_cb, &clcs[i]); ck_assert_int_eq (VAPI_OK, rv); @@ -516,6 +570,37 @@ START_TEST (test_loopbacks_1) mac_addresses[i][3], mac_addresses[i][4], mac_addresses[i][5], sw_if_indexes[i]); } + + { // new context + for (int i = 0; i < num_ifs; ++i) + { + vapi_msg_mss_clamp_enable_disable *mc = + vapi_alloc_mss_clamp_enable_disable (ctx); + mc->payload.sw_if_index = sw_if_indexes[i]; + mc->payload.ipv4_mss = 1000 + sw_if_indexes[i]; + mc->payload.ipv4_direction = MSS_CLAMP_DIR_RX; + bool reply_ctx = false; + printf ("Set ipv4 mss clamp to %u for sw_if_index %u\n", + mc->payload.ipv4_mss, mc->payload.sw_if_index); + vapi_error_e rv = vapi_mss_clamp_enable_disable ( + ctx, mc, vapi_mss_clamp_enable_disable_reply_cb, &reply_ctx); + ck_assert_int_eq (VAPI_OK, rv); + ck_assert_int_eq (reply_ctx, true); + } + } + + { // new context + int counter = 0; + vapi_msg_mss_clamp_get *msg = vapi_alloc_mss_clamp_get (ctx); + msg->payload.sw_if_index = ~0; + vapi_error_e rv = + vapi_mss_clamp_get (ctx, msg, vapi_mss_clamp_get_reply_cb, &counter, + vapi_mss_clamp_get_details_cb, &counter); + printf ("counter is %d", counter); + ck_assert_int_eq (VAPI_OK, rv); + ck_assert_int_eq (counter, num_ifs + 1); + } + bool seen[num_ifs]; sw_interface_dump_ctx dctx = { false, num_ifs, sw_if_indexes, seen, 0 }; vapi_msg_sw_interface_dump *dump; @@ -525,7 +610,7 @@ START_TEST (test_loopbacks_1) { dctx.last_called = false; clib_memset (&seen, 0, sizeof (seen)); - dump = vapi_alloc_sw_interface_dump (ctx); + dump = vapi_alloc_sw_interface_dump (ctx, 0); while (VAPI_EAGAIN == (rv = vapi_sw_interface_dump (ctx, dump, sw_interface_dump_cb, @@ -554,7 +639,7 @@ START_TEST (test_loopbacks_1) } dctx.last_called = false; clib_memset (&seen, 0, sizeof (seen)); - dump = vapi_alloc_sw_interface_dump (ctx); + dump = vapi_alloc_sw_interface_dump (ctx, 0); while (VAPI_EAGAIN == (rv = vapi_sw_interface_dump (ctx, dump, sw_interface_dump_cb, &dctx))) @@ -580,7 +665,8 @@ START_TEST (test_show_version_3) ; ck_assert_int_eq (VAPI_OK, rv); ck_assert_int_eq (0, called); - rv = vapi_dispatch (ctx); + while (VAPI_EAGAIN == (rv = vapi_dispatch (ctx))) + ; ck_assert_int_eq (VAPI_OK, rv); ck_assert_int_eq (1, called); called = 0; @@ -614,14 +700,16 @@ START_TEST (test_show_version_4) ck_assert_int_eq (0, contexts[j]); } } - rv = vapi_dispatch (ctx); + while (VAPI_EAGAIN == (rv = vapi_dispatch (ctx))) + ; ck_assert_int_eq (VAPI_OK, rv); for (i = 0; i < num_req; ++i) { ck_assert_int_eq (1, contexts[i]); } clib_memset (contexts, 0, sizeof (contexts)); - rv = vapi_dispatch (ctx); + while (VAPI_EAGAIN == (rv = vapi_dispatch (ctx))) + ; ck_assert_int_eq (VAPI_OK, rv); for (i = 0; i < num_req; ++i) { @@ -654,15 +742,19 @@ START_TEST (test_loopbacks_2) for (i = 0; i < num_ifs; ++i) { vapi_msg_create_loopback *cl = vapi_alloc_create_loopback (ctx); - memcpy (cl->payload.mac_address, mac_addresses[i], - sizeof (cl->payload.mac_address)); + int j; + for (j = 0; j < 6; ++j) + { + cl->payload.mac_address[j] = mac_addresses[i][j]; + } while (VAPI_EAGAIN == (rv = vapi_create_loopback (ctx, cl, loopback_create_cb, &clcs[i]))) ; ck_assert_int_eq (VAPI_OK, rv); } - rv = vapi_dispatch (ctx); + while (VAPI_EAGAIN == (rv = vapi_dispatch (ctx))) + ; ck_assert_int_eq (VAPI_OK, rv); for (i = 0; i < num_ifs; ++i) { @@ -676,7 +768,7 @@ START_TEST (test_loopbacks_2) bool seen[num_ifs]; clib_memset (&seen, 0, sizeof (seen)); sw_interface_dump_ctx dctx = { false, num_ifs, sw_if_indexes, seen, 0 }; - vapi_msg_sw_interface_dump *dump = vapi_alloc_sw_interface_dump (ctx); + vapi_msg_sw_interface_dump *dump = vapi_alloc_sw_interface_dump (ctx, 0); while (VAPI_EAGAIN == (rv = vapi_sw_interface_dump (ctx, dump, sw_interface_dump_cb, &dctx))) @@ -687,7 +779,8 @@ START_TEST (test_loopbacks_2) } clib_memset (&seen, 0, sizeof (seen)); ck_assert_int_eq (false, dctx.last_called); - rv = vapi_dispatch (ctx); + while (VAPI_EAGAIN == (rv = vapi_dispatch (ctx))) + ; ck_assert_int_eq (VAPI_OK, rv); for (i = 0; i < num_ifs; ++i) { @@ -705,7 +798,8 @@ START_TEST (test_loopbacks_2) ; ck_assert_int_eq (VAPI_OK, rv); } - rv = vapi_dispatch (ctx); + while (VAPI_EAGAIN == (rv = vapi_dispatch (ctx))) + ; ck_assert_int_eq (VAPI_OK, rv); for (i = 0; i < num_ifs; ++i) { @@ -714,12 +808,13 @@ START_TEST (test_loopbacks_2) } clib_memset (&seen, 0, sizeof (seen)); dctx.last_called = false; - dump = vapi_alloc_sw_interface_dump (ctx); + dump = vapi_alloc_sw_interface_dump (ctx, 0); while (VAPI_EAGAIN == (rv = vapi_sw_interface_dump (ctx, dump, sw_interface_dump_cb, &dctx))) ; - rv = vapi_dispatch (ctx); + while (VAPI_EAGAIN == (rv = vapi_dispatch (ctx))) + ; ck_assert_int_eq (VAPI_OK, rv); for (i = 0; i < num_ifs; ++i) { @@ -758,7 +853,8 @@ START_TEST (test_show_version_5) int called = 0; vapi_set_generic_event_cb (ctx, generic_cb, &called); ck_assert_int_eq (VAPI_OK, rv); - rv = vapi_dispatch_one (ctx); + while (VAPI_EAGAIN == (rv = vapi_dispatch_one (ctx))) + ; ck_assert_int_eq (VAPI_OK, rv); ck_assert_int_eq (1, called); sv = vapi_alloc_show_version (ctx); @@ -768,7 +864,8 @@ START_TEST (test_show_version_5) ; ck_assert_int_eq (VAPI_OK, rv); vapi_clear_generic_event_cb (ctx); - rv = vapi_dispatch_one (ctx); + while (VAPI_EAGAIN == (rv = vapi_dispatch_one (ctx))) + ; ck_assert_int_eq (VAPI_OK, rv); ck_assert_int_eq (1, called); /* needs to remain unchanged */ } @@ -805,7 +902,8 @@ START_TEST (test_no_response_1) (rv = vapi_show_version (ctx, sv, show_version_cb, &called))) ; ck_assert_int_eq (VAPI_OK, rv); - rv = vapi_dispatch (ctx); + while (VAPI_EAGAIN == (rv = vapi_dispatch (ctx))) + ; ck_assert_int_eq (VAPI_OK, rv); ck_assert_int_eq (2, called); } @@ -829,14 +927,15 @@ START_TEST (test_no_response_2) { printf ("--- Simulate no response to dump message ---\n"); vapi_error_e rv; - vapi_msg_sw_interface_dump *dump = vapi_alloc_sw_interface_dump (ctx); + vapi_msg_sw_interface_dump *dump = vapi_alloc_sw_interface_dump (ctx, 0); dump->header._vl_msg_id = ~0; /* malformed ID causes vpp to drop the msg */ int no_called = 0; while (VAPI_EAGAIN == (rv = vapi_sw_interface_dump (ctx, dump, no_msg_cb, &no_called))) ; ck_assert_int_eq (VAPI_OK, rv); - rv = vapi_dispatch (ctx); + while (VAPI_EAGAIN == (rv = vapi_dispatch (ctx))) + ; ck_assert_int_eq (VAPI_OK, rv); ck_assert_int_eq (1, no_called); } @@ -910,6 +1009,7 @@ START_TEST (test_api_strings) /* Assert nul terminator NOT present */ ck_assert_int_eq (vec_len (vstr), strlen (str)); vec_free (vstr); + free (dump); } END_TEST; @@ -970,13 +1070,23 @@ test_suite (void) int main (int argc, char *argv[]) { - if (3 != argc) + if (4 != argc) { printf ("Invalid argc==`%d'\n", argc); return EXIT_FAILURE; } app_name = argv[1]; api_prefix = argv[2]; + if (!strcmp (argv[3], "shm")) + use_uds = 0; + else if (!strcmp (argv[3], "uds")) + use_uds = 1; + else + { + printf ("Unrecognised required argument '%s', expected 'uds' or 'shm'.", + argv[3]); + return EXIT_FAILURE; + } printf ("App name: `%s', API prefix: `%s'\n", app_name, api_prefix); int number_failed; diff --git a/src/vpp-api/vapi/vapi_common.h b/src/vpp-api/vapi/vapi_common.h index 7157f0a8e0d..69b9b788b51 100644 --- a/src/vpp-api/vapi/vapi_common.h +++ b/src/vpp-api/vapi/vapi_common.h @@ -22,37 +22,34 @@ extern "C" { #endif -typedef enum -{ - VAPI_OK = 0, /**< success */ - VAPI_EINVAL, /**< invalid value encountered */ - VAPI_EAGAIN, /**< operation would block */ - VAPI_ENOTSUP, /**< operation not supported */ - VAPI_ENOMEM, /**< out of memory */ - VAPI_ENORESP, /**< no response to request */ - VAPI_EMAP_FAIL, /**< failure while mapping api */ - VAPI_ECON_FAIL, /**< failure while connecting to vpp */ - VAPI_EINCOMPATIBLE, /**< fundamental incompatibility while connecting to vpp - (control ping/control ping reply mismatch) */ - VAPI_MUTEX_FAILURE, /**< failure manipulating internal mutex(es) */ - VAPI_EUSER, /**< user error used for breaking dispatch, - never used by VAPI */ -} vapi_error_e; - -typedef enum -{ - VAPI_MODE_BLOCKING = 1, /**< operations block until response received */ - VAPI_MODE_NONBLOCKING = 2, /**< operations never block */ -} vapi_mode_e; - -typedef enum -{ - VAPI_WAIT_FOR_READ, /**< wait until some message is readable */ - VAPI_WAIT_FOR_WRITE, /**< wait until a message can be written */ - VAPI_WAIT_FOR_READ_WRITE, /**< wait until a read or write can be done */ -} vapi_wait_mode_e; - -typedef unsigned int vapi_msg_id_t; + typedef enum + { + VAPI_OK = 0, /**< success */ + VAPI_EINVAL, /**< invalid value encountered */ + VAPI_EAGAIN, /**< operation would block */ + VAPI_ENOTSUP, /**< operation not supported */ + VAPI_ENOMEM, /**< out of memory */ + VAPI_ENORESP, /**< no response to request */ + VAPI_EMAP_FAIL, /**< failure while mapping api */ + VAPI_ECON_FAIL, /**< failure while connecting to vpp */ + VAPI_EINCOMPATIBLE, /**< fundamental incompatibility while connecting to + vpp (control ping/control ping reply mismatch) */ + VAPI_MUTEX_FAILURE, /**< failure manipulating internal mutex(es) */ + VAPI_EUSER, /**< user error used for breaking dispatch, + never used by VAPI */ + VAPI_ENOTSOCK, /**< vapi socket doesn't refer to a socket */ + VAPI_EACCES, /**< no write permission for socket */ + VAPI_ECONNRESET, /**< connection reset by peer*/ + VAPI_ESOCK_FAILURE, /**< generic socket failure, check errno */ + } vapi_error_e; + + typedef enum + { + VAPI_MODE_BLOCKING = 1, /**< operations block until response received */ + VAPI_MODE_NONBLOCKING = 2, /**< operations never block */ + } vapi_mode_e; + + typedef unsigned int vapi_msg_id_t; #define VAPI_INVALID_MSG_ID ((vapi_msg_id_t)(~0)) diff --git a/src/vpp-api/vapi/vapi_cpp_gen.py b/src/vpp-api/vapi/vapi_cpp_gen.py index c6aa009cbb9..165730ca4b8 100755 --- a/src/vpp-api/vapi/vapi_cpp_gen.py +++ b/src/vpp-api/vapi/vapi_cpp_gen.py @@ -4,8 +4,16 @@ import argparse import os import sys import logging -from vapi_c_gen import CField, CEnum, CStruct, CSimpleType, CStructType,\ - CMessage, json_to_c_header_name, CAlias +from vapi_c_gen import ( + CField, + CEnum, + CStruct, + CSimpleType, + CStructType, + CMessage, + json_to_c_header_name, + CAlias, +) from vapi_json_parser import JsonParser @@ -25,58 +33,76 @@ class CppAlias(CAlias): pass -class CppSimpleType (CSimpleType): +class CppSimpleType(CSimpleType): pass -class CppStructType (CStructType, CppStruct): +class CppStructType(CStructType, CppStruct): pass -class CppMessage (CMessage): +class CppMessage(CMessage): def get_swap_to_be_template_instantiation(self): - return "\n".join([ - "template <> inline void vapi_swap_to_be<%s>(%s *msg)" % - (self.get_c_name(), self.get_c_name()), - "{", - " %s(msg);" % self.get_swap_to_be_func_name(), - "}", - ]) + return "\n".join( + [ + "template <> inline void vapi_swap_to_be<%s>(%s *msg)" + % (self.get_c_name(), self.get_c_name()), + "{", + " %s(msg);" % self.get_swap_to_be_func_name(), + "}", + ] + ) def get_swap_to_host_template_instantiation(self): - return "\n".join([ - "template <> inline void vapi_swap_to_host<%s>(%s *msg)" % - (self.get_c_name(), self.get_c_name()), - "{", - " %s(msg);" % self.get_swap_to_host_func_name(), - "}", - ]) + return "\n".join( + [ + "template <> inline void vapi_swap_to_host<%s>(%s *msg)" + % (self.get_c_name(), self.get_c_name()), + "{", + " %s(msg);" % self.get_swap_to_host_func_name(), + "}", + ] + ) def get_alloc_template_instantiation(self): - return "\n".join([ - "template <> inline %s* vapi_alloc<%s%s>" - "(Connection &con%s)" % - (self.get_c_name(), self.get_c_name(), - ", size_t" * len(self.get_alloc_vla_param_names()), - "".join([", size_t %s" % n for n in - self.get_alloc_vla_param_names()]) - ), - "{", - " %s* result = %s(con.vapi_ctx%s);" % - (self.get_c_name(), self.get_alloc_func_name(), - "".join([", %s" % n - for n in self.get_alloc_vla_param_names()])), - "#if VAPI_CPP_DEBUG_LEAKS", - " con.on_shm_data_alloc(result);", - "#endif", - " return result;", - "}", - ]) + return "\n".join( + [ + "template <> inline %s* vapi_alloc<%s%s>" + "(Connection &con%s)" + % ( + self.get_c_name(), + self.get_c_name(), + ", size_t" * len(self.get_alloc_vla_param_names()), + "".join( + [", size_t %s" % n for n in self.get_alloc_vla_param_names()] + ), + ), + "{", + " %s* result = %s(con.vapi_ctx%s);" + % ( + self.get_c_name(), + self.get_alloc_func_name(), + "".join([", %s" % n for n in self.get_alloc_vla_param_names()]), + ), + "#if VAPI_CPP_DEBUG_LEAKS", + " con.on_shm_data_alloc(result);", + "#endif", + " return result;", + "}", + ] + ) def get_cpp_name(self): return "%s%s" % (self.name[0].upper(), self.name[1:]) def get_req_template_name(self): + if self.has_stream_msg: + return "Stream<%s, %s, %s>" % ( + self.get_c_name(), + self.reply.get_c_name(), + self.stream_msg.get_c_name(), + ) + if self.reply_is_stream: template = "Dump" else: @@ -86,51 +112,60 @@ class CppMessage (CMessage): template, self.get_c_name(), self.reply.get_c_name(), - "".join([", size_t"] * len(self.get_alloc_vla_param_names())) + "".join([", size_t"] * len(self.get_alloc_vla_param_names())), ) def get_req_template_instantiation(self): return "template class %s;" % self.get_req_template_name() def get_type_alias(self): - return "using %s = %s;" % ( - self.get_cpp_name(), self.get_req_template_name()) + return "using %s = %s;" % (self.get_cpp_name(), self.get_req_template_name()) def get_reply_template_name(self): return "Msg<%s>" % (self.get_c_name()) def get_reply_type_alias(self): - return "using %s = %s;" % ( - self.get_cpp_name(), self.get_reply_template_name()) + return "using %s = %s;" % (self.get_cpp_name(), self.get_reply_template_name()) def get_msg_class_instantiation(self): return "template class Msg<%s>;" % self.get_c_name() def get_get_msg_id_t_instantiation(self): - return "\n".join([ - ("template <> inline vapi_msg_id_t vapi_get_msg_id_t<%s>()" - % self.get_c_name()), - "{", - " return ::%s; " % self.get_msg_id_name(), - "}", - "", - ("template <> inline vapi_msg_id_t " - "vapi_get_msg_id_t<Msg<%s>>()" % self.get_c_name()), - "{", - " return ::%s; " % self.get_msg_id_name(), - "}", - ]) + return "\n".join( + [ + ( + "template <> inline vapi_msg_id_t vapi_get_msg_id_t<%s>()" + % self.get_c_name() + ), + "{", + " return ::%s; " % self.get_msg_id_name(), + "}", + "", + ( + "template <> inline vapi_msg_id_t " + "vapi_get_msg_id_t<Msg<%s>>()" % self.get_c_name() + ), + "{", + " return ::%s; " % self.get_msg_id_name(), + "}", + ] + ) def get_cpp_constructor(self): - return '\n'.join([ - ('static void __attribute__((constructor)) ' - '__vapi_cpp_constructor_%s()' - % self.name), - '{', - (' vapi::vapi_msg_set_msg_id<%s>(%s);' % ( - self.get_c_name(), self.get_msg_id_name())), - '}', - ]) + return "\n".join( + [ + ( + "static void __attribute__((constructor)) " + "__vapi_cpp_constructor_%s()" % self.name + ), + "{", + ( + " vapi::vapi_msg_set_msg_id<%s>(%s);" + % (self.get_c_name(), self.get_msg_id_name()) + ), + "}", + ] + ) def gen_json_header(parser, logger, j, io, gen_h_prefix, add_debug_comments): @@ -139,7 +174,8 @@ def gen_json_header(parser, logger, j, io, gen_h_prefix, add_debug_comments): sys.stdout = io d, f = os.path.split(j) include_guard = "__included_hpp_%s" % ( - f.replace(".", "_").replace("/", "_").replace("-", "_")) + f.replace(".", "_").replace("/", "_").replace("-", "_").replace("@", "_") + ) print("#ifndef %s" % include_guard) print("#define %s" % include_guard) print("") @@ -167,7 +203,7 @@ def gen_json_header(parser, logger, j, io, gen_h_prefix, add_debug_comments): print("/* m.get_cpp_constructor() */") print("%s" % m.get_cpp_constructor()) print("") - if not m.is_reply and not m.is_event: + if not m.is_reply and not m.is_event and not m.is_stream: if add_debug_comments: print("/* m.get_alloc_template_instantiation() */") print("%s" % m.get_alloc_template_instantiation()) @@ -181,6 +217,8 @@ def gen_json_header(parser, logger, j, io, gen_h_prefix, add_debug_comments): print("/* m.get_reply_type_alias() */") print("%s" % m.get_reply_type_alias()) continue + if m.is_stream: + continue if add_debug_comments: print("/* m.get_req_template_instantiation() */") print("%s" % m.get_req_template_instantiation()) @@ -201,8 +239,9 @@ def json_to_cpp_header_name(json_name): raise Exception("Unexpected json name `%s'!" % json_name) -def gen_cpp_headers(parser, logger, prefix, gen_h_prefix, remove_path, - add_debug_comments=False): +def gen_cpp_headers( + parser, logger, prefix, gen_h_prefix, remove_path, add_debug_comments=False +): if prefix == "" or prefix is None: prefix = "" else: @@ -216,12 +255,11 @@ def gen_cpp_headers(parser, logger, prefix, gen_h_prefix, remove_path, d, f = os.path.split(j) else: f = j - with open('%s%s' % (prefix, json_to_cpp_header_name(f)), "w") as io: - gen_json_header(parser, logger, j, io, - gen_h_prefix, add_debug_comments) + with open("%s%s" % (prefix, json_to_cpp_header_name(f)), "w") as io: + gen_json_header(parser, logger, j, io, gen_h_prefix, add_debug_comments) -if __name__ == '__main__': +if __name__ == "__main__": try: verbose = int(os.getenv("V", 0)) except: @@ -239,27 +277,36 @@ if __name__ == '__main__': logger.setLevel(log_level) argparser = argparse.ArgumentParser(description="VPP C++ API generator") - argparser.add_argument('files', metavar='api-file', action='append', - type=str, help='json api file' - '(may be specified multiple times)') - argparser.add_argument('--prefix', action='store', default=None, - help='path prefix') - argparser.add_argument('--gen-h-prefix', action='store', default=None, - help='generated C header prefix') - argparser.add_argument('--remove-path', action='store_true', - help='remove path from filename') + argparser.add_argument( + "files", + metavar="api-file", + action="append", + type=str, + help="json api file" "(may be specified multiple times)", + ) + argparser.add_argument("--prefix", action="store", default=None, help="path prefix") + argparser.add_argument( + "--gen-h-prefix", action="store", default=None, help="generated C header prefix" + ) + argparser.add_argument( + "--remove-path", action="store_true", help="remove path from filename" + ) args = argparser.parse_args() - jsonparser = JsonParser(logger, args.files, - simple_type_class=CppSimpleType, - struct_type_class=CppStructType, - field_class=CppField, - enum_class=CppEnum, - message_class=CppMessage, - alias_class=CppAlias) - - gen_cpp_headers(jsonparser, logger, args.prefix, args.gen_h_prefix, - args.remove_path) + jsonparser = JsonParser( + logger, + args.files, + simple_type_class=CppSimpleType, + struct_type_class=CppStructType, + field_class=CppField, + enum_class=CppEnum, + message_class=CppMessage, + alias_class=CppAlias, + ) + + gen_cpp_headers( + jsonparser, logger, args.prefix, args.gen_h_prefix, args.remove_path + ) for e in jsonparser.exceptions: logger.warning(e) diff --git a/src/vpp-api/vapi/vapi_cpp_test.cpp b/src/vpp-api/vapi/vapi_cpp_test.cpp index 1dd58f85484..918c7590b60 100644 --- a/src/vpp-api/vapi/vapi_cpp_test.cpp +++ b/src/vpp-api/vapi/vapi_cpp_test.cpp @@ -21,17 +21,21 @@ #include <assert.h> #include <setjmp.h> #include <check.h> +#include <vapi/memclnt.api.vapi.h> #include <vapi/vapi.hpp> #include <vapi/vpe.api.vapi.hpp> #include <vapi/interface.api.vapi.hpp> +#include <vapi/mss_clamp.api.vapi.hpp> #include <fake.api.vapi.hpp> DEFINE_VAPI_MSG_IDS_VPE_API_JSON; DEFINE_VAPI_MSG_IDS_INTERFACE_API_JSON; +DEFINE_VAPI_MSG_IDS_MSS_CLAMP_API_JSON; DEFINE_VAPI_MSG_IDS_FAKE_API_JSON; static char *app_name = nullptr; static char *api_prefix = nullptr; +static bool use_uds = false; static const int max_outstanding_requests = 32; static const int response_queue_size = 32; @@ -57,8 +61,9 @@ Connection con; void setup (void) { - vapi_error_e rv = con.connect ( - app_name, api_prefix, max_outstanding_requests, response_queue_size); + vapi_error_e rv = + con.connect (app_name, api_prefix, max_outstanding_requests, + response_queue_size, true, use_uds); ck_assert_int_eq (VAPI_OK, rv); } @@ -144,9 +149,53 @@ START_TEST (test_loopbacks_1) } { // new context + for (int i = 0; i < num_ifs; ++i) + { + Mss_clamp_enable_disable d (con); + auto &req = d.get_request ().get_payload (); + req.sw_if_index = sw_if_indexes[i]; + req.ipv4_mss = 1420; + req.ipv4_direction = vapi_enum_mss_clamp_dir::MSS_CLAMP_DIR_RX; + auto rv = d.execute (); + ck_assert_int_eq (VAPI_OK, rv); + WAIT_FOR_RESPONSE (d, rv); + ck_assert_int_eq (VAPI_OK, rv); + } + } + + { // new context + bool seen[num_ifs] = { 0 }; + Mss_clamp_get d (con); + d.get_request ().get_payload ().sw_if_index = ~0; + auto rv = d.execute (); + ck_assert_int_eq (VAPI_OK, rv); + WAIT_FOR_RESPONSE (d, rv); + ck_assert_int_eq (VAPI_OK, rv); + auto &rs = d.get_result_set (); + for (auto &r : rs) + { + auto &p = r.get_payload (); + ck_assert_int_eq (p.ipv4_mss, 1420); + printf ("tcp-clamp: sw_if_idx %u ip4-mss %d dir %d\n", p.sw_if_index, + p.ipv4_mss, p.ipv4_direction); + for (int i = 0; i < num_ifs; ++i) + { + if (sw_if_indexes[i] == p.sw_if_index) + { + ck_assert_int_eq (0, seen[i]); + seen[i] = true; + } + } + } + for (int i = 0; i < num_ifs; ++i) + { + ck_assert_int_eq (1, seen[i]); + } + } + + { // new context bool seen[num_ifs] = {0}; - Sw_interface_dump d (con); - d.get_request ().get_payload (); + Sw_interface_dump d (con, 0); auto rv = d.execute (); ck_assert_int_eq (VAPI_OK, rv); WAIT_FOR_RESPONSE (d, rv); @@ -185,8 +234,7 @@ START_TEST (test_loopbacks_1) } { // new context - Sw_interface_dump d (con); - d.get_request ().get_payload (); + Sw_interface_dump d (con, 0); auto rv = d.execute (); ck_assert_int_eq (VAPI_OK, rv); WAIT_FOR_RESPONSE (d, rv); @@ -207,7 +255,7 @@ END_TEST; struct Create_loopback_cb { - Create_loopback_cb () : called{0}, sw_if_index{0} {}; + Create_loopback_cb () : called{ 0 }, sw_if_index{ 0 }, seen{ false } {}; int called; u32 sw_if_index; bool seen; @@ -222,7 +270,7 @@ struct Create_loopback_cb struct Delete_loopback_cb { - Delete_loopback_cb () : called{0}, sw_if_index{0} {}; + Delete_loopback_cb () : called{ 0 }, sw_if_index{ 0 }, seen{ false } {}; int called; u32 sw_if_index; bool seen; @@ -301,8 +349,7 @@ START_TEST (test_loopbacks_2) } Sw_interface_dump_cb<num_ifs> swdcb (ccbs); - Sw_interface_dump d (con, std::ref (swdcb)); - d.get_request ().get_payload (); + Sw_interface_dump d (con, 0, std::ref (swdcb)); auto rv = d.execute (); ck_assert_int_eq (VAPI_OK, rv); WAIT_FOR_RESPONSE (d, rv); @@ -328,8 +375,7 @@ START_TEST (test_loopbacks_2) } { // new context - Sw_interface_dump d (con); - d.get_request ().get_payload (); + Sw_interface_dump d (con, 0); auto rv = d.execute (); ck_assert_int_eq (VAPI_OK, rv); WAIT_FOR_RESPONSE (d, rv); @@ -408,14 +454,25 @@ Suite *test_suite (void) int main (int argc, char *argv[]) { - if (3 != argc) + if (4 != argc) { printf ("Invalid argc==`%d'\n", argc); return EXIT_FAILURE; } app_name = argv[1]; api_prefix = argv[2]; - printf ("App name: `%s', API prefix: `%s'\n", app_name, api_prefix); + if (!strcmp (argv[3], "shm")) + use_uds = 0; + else if (!strcmp (argv[3], "uds")) + use_uds = 1; + else + { + printf ("Unrecognised required argument '%s', expected 'uds' or 'shm'.", + argv[3]); + return EXIT_FAILURE; + } + printf ("App name: `%s', API prefix: `%s', use unix sockets %d\n", app_name, + api_prefix, use_uds); int number_failed; Suite *s; diff --git a/src/vpp-api/vapi/vapi_doc.md b/src/vpp-api/vapi/vapi_doc.md deleted file mode 100644 index 0e7e29dde01..00000000000 --- a/src/vpp-api/vapi/vapi_doc.md +++ /dev/null @@ -1,155 +0,0 @@ -# VPP API module {#vapi_doc} - -## Overview - -VPP API module allows communicating with VPP over shared memory interface. -The API consists of 3 parts: - -* common code - low-level API -* generated code - high-level API -* code generator - to generate your own high-level API e.g. for custom plugins - -### Common code - -#### C common code - -C common code represents the basic, low-level API, providing functions to -connect/disconnect, perform message discovery and send/receive messages. -The C variant is in vapi.h. - -#### C++ common code - -C++ is provided by vapi.hpp and contains high-level API templates, -which are specialized by generated code. - -### Generated code - -Each API file present in the source tree is automatically translated to JSON -file, which the code generator parses and generates either C (`vapi_c_gen.py`) -or C++ (`vapi_cpp_gen.py`) code. - -This can then be included in the client application and provides convenient way -to interact with VPP. This includes: - -* automatic byte-swapping -* automatic request-response matching based on context -* automatic casts to appropriate types (type-safety) when calling callbacks -* automatic sending of control-pings for dump messages - -The API supports two modes of operation: - -* blocking -* non-blocking - -In blocking mode, whenever an operation is initiated, the code waits until it -can finish. This means that when sending a message, the call blocks until -the message can be written to shared memory. Similarly, receiving a message -blocks until a message becomes available. On higher level, this also means that -when doing a request (e.g. `show_version`), the call blocks until a response -comes back (e.g. `show_version_reply`). - -In non-blocking mode, these are decoupled, the API returns VAPI_EAGAIN whenever -an operation cannot be performed and after sending a request, it's up to -the client to wait for and process a response. - -### Code generator - -Python code generator comes in two flavors - C and C++ and generates high-level -API headers. All the code is stored in the headers. - -## Usage - -### Low-level API - -Refer to inline API documentation in doxygen format in `vapi.h` header -for description of functions. It's recommened to use the safer, high-level -API provided by specialized headers (e.g. `vpe.api.vapi.h` -or `vpe.api.vapi.hpp`). - -#### C high-level API - -##### Callbacks - -The C high-level API is strictly callback-based for maximum efficiency. -Whenever an operation is initiated a callback with a callback context is part -of that operation. The callback is then invoked when the response (or multiple -responses) arrive which are tied to the request. Also, callbacks are invoked -whenever an event arrives, if such callback is registered. All the pointers -to responses/events point to shared memory and are immediately freed after -callback finishes so the client needs to extract/copy any data in which it -is interested in. - -#### Blocking mode - -In simple blocking mode, the whole operation (being a simple request or a dump) -is finished and it's callback is called (potentially multiple times for dumps) -during function call. - -Example pseudo-code for a simple request in this mode: - -` -vapi_show_version(message, callback, callback_context) - -1. generate unique internal context and assign it to message.header.context -2. byteswap the message to network byte order -3. send message to vpp (message is now consumed and vpp will free it) -4. create internal "outstanding request context" which stores the callback, - callback context and the internal context value -5. call dispatch, which in this mode receives and processes responses until - the internal "outstanding requests" queue is empty. In blocking mode, this - queue always contains at most one item. -` - -**Note**: it's possible for different - unrelated callbacks to be called before -the response callbacks is called in cases where e.g. events are stored -in shared memory queue. - -#### Non-blocking mode - -In non-blocking mode, all the requests are only byte-swapped and the context -information along with callbacks is stored locally (so in the above example, -only steps 1-4 are executed and step 5 is skipped). Calling dispatch is up to -the client application. This allows to alternate between sending/receiving -messages or have a dedicated thread which calls dispatch. - -### C++ high level API - -#### Callbacks - -In C++ API, the response is automatically tied to the corresponding `Request`, -`Dump` or `Event_registration` object. Optionally a callback might be specified, -which then gets called when the response is received. - -**Note**: responses take up shared memory space and should be freed either -manually (in case of result sets) or automatically (by destroying the object -owning them) when no longer needed. Once a Request or Dump object was executed, -it cannot be re-sent, since the request itself (stores in shared memory) -is consumed by vpp and inaccessible (set to nullptr) anymore. - -#### Usage - -#### Requests & dumps - -0. Create on object of `Connection` type and call `connect()` to connect to vpp. -1. Create an object of `Request` or `Dump` type using it's typedef (e.g. - `Show_version`) -2. Use `get_request()` to obtain and manipulate the underlying request if - required. -3. Issue `execute()` to send the request. -4. Use either `wait_for_response()` or `dispatch()` to wait for the response. -5. Use `get_response_state()` to get the state and `get_response()` to read - the response. - -#### Events - -0. Create a `Connection` and execute the appropriate `Request` to subscribe to - events (e.g. `Want_stats`) -1. Create an `Event_registration` with a template argument being the type of - event you are insterested in. -2. Call `dispatch()` or `wait_for_response()` to wait for the event. A callback - will be called when an event occurs (if passed to `Event_registration()` - constructor). Alternatively, read the result set. - -**Note**: events stored in the result set take up space in shared memory -and should be freed regularly (e.g. in the callback, once the event is -processed). diff --git a/src/vpp-api/vapi/vapi_doc.rst b/src/vpp-api/vapi/vapi_doc.rst new file mode 100644 index 00000000000..4efbf2d9988 --- /dev/null +++ b/src/vpp-api/vapi/vapi_doc.rst @@ -0,0 +1,191 @@ +.. _vapi_doc: + +VPP API module +============== + +Overview +-------- + +VPP API module allows communicating with VPP over shared memory +interface. The API consists of 3 parts: + +- common code - low-level API +- generated code - high-level API +- code generator - to generate your own high-level API e.g. for custom + plugins + +Common code +~~~~~~~~~~~ + +C common code +^^^^^^^^^^^^^ + +C common code represents the basic, low-level API, providing functions +to connect/disconnect, perform message discovery and send/receive +messages. The C variant is in vapi.h. + +.. _c-common-code-1: + +C++ common code +^^^^^^^^^^^^^^^ + +C++ is provided by vapi.hpp and contains high-level API templates, which +are specialized by generated code. + +Generated code +~~~~~~~~~~~~~~ + +Each API file present in the source tree is automatically translated to +JSON file, which the code generator parses and generates either C +(``vapi_c_gen.py``) or C++ (``vapi_cpp_gen.py``) code. + +This can then be included in the client application and provides +convenient way to interact with VPP. This includes: + +- automatic byte-swapping +- automatic request-response matching based on context +- automatic casts to appropriate types (type-safety) when calling + callbacks +- automatic sending of control-pings for dump messages + +The API supports two modes of operation: + +- blocking +- non-blocking + +In blocking mode, whenever an operation is initiated, the code waits +until it can finish. This means that when sending a message, the call +blocks until the message can be written to shared memory. Similarly, +receiving a message blocks until a message becomes available. On higher +level, this also means that when doing a request +(e.g. ``show_version``), the call blocks until a response comes back +(e.g. ``show_version_reply``). + +In non-blocking mode, these are decoupled, the API returns VAPI_EAGAIN +whenever an operation cannot be performed and after sending a request, +it’s up to the client to wait for and process a response. + +Code generator +~~~~~~~~~~~~~~ + +Python code generator comes in two flavors - C and C++ and generates +high-level API headers. All the code is stored in the headers. + +Usage +----- + +Low-level API +~~~~~~~~~~~~~ + +Refer to inline API documentation in doxygen format in ``vapi.h`` header +for description of functions. It’s recommended to use the safer, +high-level API provided by specialized headers (e.g. ``vpe.api.vapi.h`` +or ``vpe.api.vapi.hpp``). + +C high-level API +^^^^^^^^^^^^^^^^ + +Callbacks +''''''''' + +The C high-level API is strictly callback-based for maximum efficiency. +Whenever an operation is initiated a callback with a callback context is +part of that operation. The callback is then invoked when the response +(or multiple responses) arrive which are tied to the request. Also, +callbacks are invoked whenever an event arrives, if such callback is +registered. All the pointers to responses/events point to shared memory +and are immediately freed after callback finishes so the client needs to +extract/copy any data in which it is interested in. + +Blocking mode +^^^^^^^^^^^^^ + +In simple blocking mode, the whole operation (being a simple request or +a dump) is finished and it’s callback is called (potentially multiple +times for dumps) during function call. + +Example pseudo-code for a simple request in this mode: + +\` vapi_show_version(message, callback, callback_context) + +1. generate unique internal context and assign it to + message.header.context +2. byteswap the message to network byte order +3. send message to vpp (message is now consumed and vpp will free it) +4. create internal “outstanding request context” which stores the + callback, callback context and the internal context value +5. call dispatch, which in this mode receives and processes responses + until the internal “outstanding requests” queue is empty. In blocking + mode, this queue always contains at most one item. \` + +**Note**: it’s possible for different - unrelated callbacks to be called +before the response callbacks is called in cases where e.g. events are +stored in shared memory queue. + +Non-blocking mode +^^^^^^^^^^^^^^^^^ + +In non-blocking mode, all the requests are only byte-swapped and the +context information along with callbacks is stored locally (so in the +above example, only steps 1-4 are executed and step 5 is skipped). +Calling dispatch is up to the client application. This allows to +alternate between sending/receiving messages or have a dedicated thread +which calls dispatch. + +.. _c-high-level-api-1: + +C++ high level API +~~~~~~~~~~~~~~~~~~ + +.. _callbacks-1: + +Callbacks +^^^^^^^^^ + +In C++ API, the response is automatically tied to the corresponding +``Request``, ``Dump`` or ``Event_registration`` object. Optionally a +callback might be specified, which then gets called when the response is +received. + +**Note**: responses take up shared memory space and should be freed +either manually (in case of result sets) or automatically (by destroying +the object owning them) when no longer needed. Once a Request or Dump +object was executed, it cannot be re-sent, since the request itself +(stores in shared memory) is consumed by vpp and inaccessible (set to +nullptr) anymore. + +.. _usage-1: + +Usage +^^^^^ + +Requests & dumps +^^^^^^^^^^^^^^^^ + +0. Create on object of ``Connection`` type and call ``connect()`` to + connect to vpp. +1. Create an object of ``Request`` or ``Dump`` type using it’s typedef + (e.g. ``Show_version``) +2. Use ``get_request()`` to obtain and manipulate the underlying request + if required. +3. Issue ``execute()`` to send the request. +4. Use either ``wait_for_response()`` or ``dispatch()`` to wait for the + response. +5. Use ``get_response_state()`` to get the state and ``get_response()`` + to read the response. + +Events +^^^^^^ + +0. Create a ``Connection`` and execute the appropriate ``Request`` to + subscribe to events (e.g. ``Want_stats``) +1. Create an ``Event_registration`` with a template argument being the + type of event you are interested in. +2. Call ``dispatch()`` or ``wait_for_response()`` to wait for the event. + A callback will be called when an event occurs (if passed to + ``Event_registration()`` constructor). Alternatively, read the result + set. + +**Note**: events stored in the result set take up space in shared memory +and should be freed regularly (e.g. in the callback, once the event is +processed). diff --git a/src/vpp-api/vapi/vapi_internal.h b/src/vpp-api/vapi/vapi_internal.h index e9a9726d86e..ca47dd10459 100644 --- a/src/vpp-api/vapi/vapi_internal.h +++ b/src/vpp-api/vapi/vapi_internal.h @@ -82,6 +82,7 @@ typedef vapi_error_e (*vapi_cb_t) (struct vapi_ctx_s *, void *, vapi_error_e, bool, void *); typedef void (*generic_swap_fn_t) (void *payload); +typedef int (*verify_msg_size_fn_t) (void *msg, uword buf_size); typedef struct { @@ -92,7 +93,7 @@ typedef struct bool has_context; unsigned int context_offset; unsigned int payload_offset; - size_t size; + verify_msg_size_fn_t verify_msg_size; generic_swap_fn_t swap_to_be; generic_swap_fn_t swap_to_host; vapi_msg_id_t id; /* assigned at run-time */ @@ -117,12 +118,21 @@ bool vapi_requests_full (vapi_ctx_t ctx); size_t vapi_get_request_count (vapi_ctx_t ctx); size_t vapi_get_max_request_count (vapi_ctx_t ctx); u32 vapi_gen_req_context (vapi_ctx_t ctx); -void vapi_store_request (vapi_ctx_t ctx, u32 context, bool is_dump, - vapi_cb_t callback, void *callback_ctx); + +enum vapi_request_type +{ + VAPI_REQUEST_REG = 0, + VAPI_REQUEST_DUMP = 1, + VAPI_REQUEST_STREAM = 2, +}; + +void vapi_store_request (vapi_ctx_t ctx, u32 context, + vapi_msg_id_t response_id, + enum vapi_request_type type, vapi_cb_t callback, + void *callback_ctx); int vapi_get_payload_offset (vapi_msg_id_t id); void (*vapi_get_swap_to_host_func (vapi_msg_id_t id)) (void *payload); void (*vapi_get_swap_to_be_func (vapi_msg_id_t id)) (void *payload); -size_t vapi_get_message_size (vapi_msg_id_t id); size_t vapi_get_context_offset (vapi_msg_id_t id); bool vapi_msg_is_with_context (vapi_msg_id_t id); size_t vapi_get_message_count(); diff --git a/src/vpp-api/vapi/vapi_json_parser.py b/src/vpp-api/vapi/vapi_json_parser.py index 1383d456bf1..c06cb8cf77b 100644 --- a/src/vpp-api/vapi/vapi_json_parser.py +++ b/src/vpp-api/vapi/vapi_json_parser.py @@ -3,7 +3,7 @@ import json -class ParseError (Exception): +class ParseError(Exception): pass @@ -13,14 +13,12 @@ magic_suffix = "_t" def remove_magic(what): if what.startswith(magic_prefix) and what.endswith(magic_suffix): - return what[len(magic_prefix): - len(magic_suffix)] + return what[len(magic_prefix) : -len(magic_suffix)] return what class Field(object): - - def __init__(self, field_name, field_type, array_len=None, - nelem_field=None): + def __init__(self, field_name, field_type, array_len=None, nelem_field=None): self.name = field_name self.type = field_type self.len = array_len @@ -30,17 +28,23 @@ class Field(object): if self.len is None: return "Field(name: %s, type: %s)" % (self.name, self.type) elif type(self.len) == dict: - return "Field(name: %s, type: %s, length: %s)" % (self.name, - self.type, - self.len) + return "Field(name: %s, type: %s, length: %s)" % ( + self.name, + self.type, + self.len, + ) elif self.len > 0: - return "Field(name: %s, type: %s, length: %s)" % (self.name, - self.type, - self.len) + return "Field(name: %s, type: %s, length: %s)" % ( + self.name, + self.type, + self.len, + ) else: - return ( - "Field(name: %s, type: %s, variable length stored in: %s)" % - (self.name, self.type, self.nelem_field)) + return "Field(name: %s, type: %s, variable length stored in: %s)" % ( + self.name, + self.type, + self.nelem_field, + ) def is_vla(self): return self.nelem_field is not None @@ -61,32 +65,38 @@ class Type(object): return self.name -class SimpleType (Type): - +class SimpleType(Type): def has_vla(self): return False def get_msg_header_defs(struct_type_class, field_class, json_parser, logger): return [ - struct_type_class(['msg_header1_t', - ['u16', '_vl_msg_id'], - ['u32', 'context'], - ], - json_parser, field_class, logger - ), - struct_type_class(['msg_header2_t', - ['u16', '_vl_msg_id'], - ['u32', 'client_index'], - ['u32', 'context'], - ], - json_parser, field_class, logger - ), + struct_type_class( + [ + "msg_header1_t", + ["u16", "_vl_msg_id"], + ["u32", "context"], + ], + json_parser, + field_class, + logger, + ), + struct_type_class( + [ + "msg_header2_t", + ["u16", "_vl_msg_id"], + ["u32", "client_index"], + ["u32", "context"], + ], + json_parser, + field_class, + logger, + ), ] class Struct(object): - def __init__(self, name, fields): self.name = name self.fields = fields @@ -112,7 +122,7 @@ class Enum(SimpleType): def __str__(self): return "Enum(%s, [%s])" % ( self.name, - "], [" .join(["%s => %s" % (i, j) for i, j in self.value_pairs]) + "], [".join(["%s => %s" % (i, j) for i, j in self.value_pairs]), ) @@ -126,7 +136,7 @@ class Union(Type): def __str__(self): return "Union(%s, [%s])" % ( self.name, - "], [" .join(["%s %s" % (i, j) for i, j in self.type_pairs]) + "], [".join(["%s %s" % (i, j) for i, j in self.type_pairs]), ) def has_vla(self): @@ -134,7 +144,6 @@ class Union(Type): class Message(object): - def __init__(self, logger, definition, json_parser): struct_type_class = json_parser.struct_type_class field_class = json_parser.field_class @@ -149,23 +158,26 @@ class Message(object): self.header = None self.is_reply = json_parser.is_reply(self.name) self.is_event = json_parser.is_event(self.name) + self.is_stream = json_parser.is_stream(self.name) fields = [] - for header in get_msg_header_defs(struct_type_class, field_class, - json_parser, logger): + for header in get_msg_header_defs( + struct_type_class, field_class, json_parser, logger + ): logger.debug("Probing header `%s'" % header.name) if header.is_part_of_def(m[1:]): self.header = header logger.debug("Found header `%s'" % header.name) - fields.append(field_class(field_name='header', - field_type=self.header)) + fields.append(field_class(field_name="header", field_type=self.header)) ignore = False break if ignore and not self.is_event and not self.is_reply: - raise ParseError("While parsing message `%s': could not find all " - "common header fields" % name) + raise ParseError( + "While parsing message `%s': could not find all " + "common header fields" % name + ) for field in m[1:]: - if isinstance(field, dict) and 'crc' in field: - self.crc = field['crc'] + if isinstance(field, dict) and "crc" in field: + self.crc = field["crc"] logger.debug("Found CRC `%s'" % self.crc) continue else: @@ -175,25 +187,28 @@ class Message(object): if any(type(n) is dict for n in field): l -= 1 if l == 2: - if self.header is not None and\ - self.header.has_field(field[1]): + if self.header is not None and self.header.has_field(field[1]): continue - p = field_class(field_name=field[1], - field_type=field_type) + p = field_class(field_name=field[1], field_type=field_type) elif l == 3: - if field[2] == 0 and field[0] != 'string': + if field[2] == 0 and field[0] != "string": raise ParseError( "While parsing message `%s': variable length " "array `%s' doesn't have reference to member " - "containing the actual length" % ( - name, field[1])) - if field[0] == 'string' and field[2] > 0: - field_type = json_parser.lookup_type_like_id('u8') - - p = field_class( - field_name=field[1], - field_type=field_type, - array_len=field[2]) + "containing the actual length" % (name, field[1]) + ) + if field[0] == "string" and field[2] == 0: + field_type = json_parser.lookup_type_like_id("vl_api_string_t") + p = field_class(field_name=field[1], field_type=field_type) + else: + if field[0] == "string" and field[2] > 0: + field_type = json_parser.lookup_type_like_id("u8") + + p = field_class( + field_name=field[1], + field_type=field_type, + array_len=field[2], + ) elif l == 4: nelem_field = None for f in fields: @@ -203,17 +218,19 @@ class Message(object): raise ParseError( "While parsing message `%s': couldn't find " "variable length array `%s' member containing " - "the actual length `%s'" % ( - name, field[1], field[3])) + "the actual length `%s'" % (name, field[1], field[3]) + ) p = field_class( field_name=field[1], field_type=field_type, array_len=field[2], - nelem_field=nelem_field) + nelem_field=nelem_field, + ) else: - raise Exception("Don't know how to parse message " - "definition for message `%s': `%s'" % - (m, m[1:])) + raise Exception( + "Don't know how to parse message " + "definition for message `%s': `%s'" % (m, m[1:]) + ) logger.debug("Parsed field `%s'" % p) fields.append(p) self.fields = fields @@ -221,35 +238,54 @@ class Message(object): logger.debug("Parsed message: %s" % self) def __str__(self): - return "Message(%s, [%s], {crc: %s}" % \ - (self.name, - "], [".join([str(f) for f in self.fields]), - self.crc) - + return "Message(%s, [%s], {crc: %s}" % ( + self.name, + "], [".join([str(f) for f in self.fields]), + self.crc, + ) -class StructType (Type, Struct): +class StructType(Type, Struct): def __init__(self, definition, json_parser, field_class, logger): t = definition logger.debug("Parsing struct definition `%s'" % t) name = t[0] fields = [] for field in t[1:]: - if len(field) == 1 and 'crc' in field: - self.crc = field['crc'] + if len(field) == 1 and "crc" in field: + self.crc = field["crc"] continue field_type = json_parser.lookup_type_like_id(field[0]) logger.debug("Parsing type field `%s'" % field) if len(field) == 2: - p = field_class(field_name=field[1], - field_type=field_type) + p = field_class(field_name=field[1], field_type=field_type) elif len(field) == 3: if field[2] == 0: - raise ParseError("While parsing type `%s': array `%s' has " - "variable length" % (name, field[1])) - p = field_class(field_name=field[1], - field_type=field_type, - array_len=field[2]) + if name == "vl_api_string_t": + p = None + for f in fields: + if f.name == "length": + nelem_field = f + p = field_class( + field_name=field[1], + field_type=field_type, + array_len=field[2], + nelem_field=nelem_field, + ) + break + if p is None: + raise ParseError( + "While parsing type `%s': missing `length'" % name + ) + else: + raise ParseError( + "While parsing type `%s': array `%s' has " + "variable length" % (name, field[1]) + ) + else: + p = field_class( + field_name=field[1], field_type=field_type, array_len=field[2] + ) elif len(field) == 4: nelem_field = None for f in fields: @@ -259,23 +295,25 @@ class StructType (Type, Struct): raise ParseError( "While parsing message `%s': couldn't find " "variable length array `%s' member containing " - "the actual length `%s'" % ( - name, field[1], field[3])) - p = field_class(field_name=field[1], - field_type=field_type, - array_len=field[2], - nelem_field=nelem_field) + "the actual length `%s'" % (name, field[1], field[3]) + ) + p = field_class( + field_name=field[1], + field_type=field_type, + array_len=field[2], + nelem_field=nelem_field, + ) else: raise ParseError( "Don't know how to parse field `%s' of type definition " - "for type `%s'" % (field, t)) + "for type `%s'" % (field, t) + ) fields.append(p) Type.__init__(self, name) Struct.__init__(self, name, fields) def __str__(self): - return "StructType(%s, %s)" % (Type.__str__(self), - Struct.__str__(self)) + return "StructType(%s, %s)" % (Type.__str__(self), Struct.__str__(self)) def has_field(self, name): return name in self.field_names @@ -289,32 +327,57 @@ class StructType (Type, Struct): if field[0] != p.type.name: raise ParseError( "Unexpected field type `%s' (should be `%s'), " - "while parsing msg/def/field `%s/%s/%s'" % - (field[0], p.type, p.name, definition, field)) + "while parsing msg/def/field `%s/%s/%s'" + % (field[0], p.type, p.name, definition, field) + ) return True class JsonParser(object): - def __init__(self, logger, files, simple_type_class=SimpleType, - enum_class=Enum, union_class=Union, - struct_type_class=StructType, field_class=Field, - message_class=Message, alias_class=Alias): + def __init__( + self, + logger, + files, + simple_type_class=SimpleType, + enum_class=Enum, + union_class=Union, + struct_type_class=StructType, + field_class=Field, + message_class=Message, + alias_class=Alias, + ): self.services = {} self.messages = {} self.enums = {} + self.enumflags = {} self.unions = {} self.aliases = {} self.types = { - x: simple_type_class(x) for x in [ - 'i8', 'i16', 'i32', 'i64', - 'u8', 'u16', 'u32', 'u64', - 'f64', 'bool' + x: simple_type_class(x) + for x in [ + "i8", + "i16", + "i32", + "i64", + "u8", + "u16", + "u32", + "u64", + "f64", + "bool", ] } - self.types['string'] = simple_type_class('vl_api_string_t') + self.types["string"] = simple_type_class("u8") + self.types["vl_api_string_t"] = struct_type_class( + ["vl_api_string_t", ["u32", "length"], ["u8", "buf", 0]], + self, + field_class, + logger, + ) self.replies = set() self.events = set() + self.streams = set() self.simple_type_class = simple_type_class self.enum_class = enum_class self.union_class = union_class @@ -345,15 +408,17 @@ class JsonParser(object): self.messages_by_json[path] = {} with open(path) as f: j = json.load(f) - for k in j['services']: + for k in j["services"]: if k in self.services: raise ParseError("Duplicate service `%s'" % k) - self.services[k] = j['services'][k] + self.services[k] = j["services"][k] self.replies.add(self.services[k]["reply"]) if "events" in self.services[k]: for x in self.services[k]["events"]: self.events.add(x) - for e in j['enums']: + if "stream_msg" in self.services[k]: + self.streams.add(self.services[k]["stream_msg"]) + for e in j["enums"]: name = e[0] value_pairs = e[1:-1] enumtype = self.types[e[-1]["enumtype"]] @@ -361,18 +426,27 @@ class JsonParser(object): self.enums[enum.name] = enum self.logger.debug("Parsed enum: %s" % enum) self.enums_by_json[path].append(enum) + for e in j["enumflags"]: + name = e[0] + value_pairs = e[1:-1] + enumtype = self.types[e[-1]["enumtype"]] + enum = self.enum_class(name, value_pairs, enumtype) + self.enums[enum.name] = enum + self.logger.debug("Parsed enumflag: %s" % enum) + self.enums_by_json[path].append(enum) exceptions = [] progress = 0 last_progress = 0 while True: - for u in j['unions']: + for u in j["unions"]: name = u[0] if name in self.unions: progress = progress + 1 continue try: - type_pairs = [[self.lookup_type_like_id(t), n] - for t, n in u[1:]] + type_pairs = [ + [self.lookup_type_like_id(t), n] for t, n in u[1:] + ] union = self.union_class(name, type_pairs, 0) progress = progress + 1 except ParseError as e: @@ -381,17 +455,16 @@ class JsonParser(object): self.unions[union.name] = union self.logger.debug("Parsed union: %s" % union) self.unions_by_json[path].append(union) - for t in j['types']: + for t in j["types"]: if t[0] in self.types: progress = progress + 1 continue try: - type_ = self.struct_type_class(t, self, - self.field_class, - self.logger) + type_ = self.struct_type_class( + t, self, self.field_class, self.logger + ) if type_.name in self.types: - raise ParseError( - "Duplicate type `%s'" % type_.name) + raise ParseError("Duplicate type `%s'" % type_.name) progress = progress + 1 except ParseError as e: exceptions.append(e) @@ -399,16 +472,16 @@ class JsonParser(object): self.types[type_.name] = type_ self.types_by_json[path].append(type_) self.logger.debug("Parsed type: %s" % type_) - for name, body in j['aliases'].items(): + for name, body in j["aliases"].items(): if name in self.aliases: progress = progress + 1 continue - if 'length' in body: - array_len = body['length'] + if "length" in body: + array_len = body["length"] else: array_len = None try: - t = self.lookup_type_like_id(body['type']) + t = self.lookup_type_like_id(body["type"]) except ParseError as e: exceptions.append(e) continue @@ -430,14 +503,13 @@ class JsonParser(object): processed = [] while True: exceptions = [] - for m in j['messages']: + for m in j["messages"]: if m in processed: continue try: msg = self.message_class(self.logger, m, self) if msg.name in self.messages: - raise ParseError( - "Duplicate message `%s'" % msg.name) + raise ParseError("Duplicate message `%s'" % msg.name) except ParseError as e: exceptions.append(e) continue @@ -456,6 +528,8 @@ class JsonParser(object): return self.types[name] elif name in self.enums: return self.enums[name] + elif name in self.enumflags: + return self.enumflags[name] elif name in self.unions: return self.unions[name] elif name in self.aliases: @@ -464,13 +538,16 @@ class JsonParser(object): return self.types[mundane_name] elif mundane_name in self.enums: return self.enums[mundane_name] + elif mundane_name in self.enumflags: + return self.enumflags[mundane_name] elif mundane_name in self.unions: return self.unions[mundane_name] elif mundane_name in self.aliases: return self.aliases[mundane_name] raise ParseError( "Could not find type, enum or union by magic name `%s' nor by " - "mundane name `%s'" % (name, mundane_name)) + "mundane name `%s'" % (name, mundane_name) + ) def is_reply(self, message): return message in self.replies @@ -478,8 +555,22 @@ class JsonParser(object): def is_event(self, message): return message in self.events + def is_stream(self, message): + return message in self.streams + + def has_stream_msg(self, message): + return ( + message.name in self.services + and "stream_msg" in self.services[message.name] + ) + + def get_stream_msg(self, message): + if not self.has_stream_msg(message): + return None + return self.messages[self.services[message.name]["stream_msg"]] + def get_reply(self, message): - return self.messages[self.services[message]['reply']] + return self.messages[self.services[message]["reply"]] def finalize_parsing(self): if len(self.messages) == 0: @@ -489,21 +580,20 @@ class JsonParser(object): remove = [] for n, m in j.items(): try: - if not m.is_reply and not m.is_event: + if not m.is_reply and not m.is_event and not m.is_stream: try: m.reply = self.get_reply(n) + m.reply_is_stream = False + m.has_stream_msg = self.has_stream_msg(m) if "stream" in self.services[m.name]: - m.reply_is_stream = \ - self.services[m.name]["stream"] - else: - m.reply_is_stream = False + m.reply_is_stream = self.services[m.name]["stream"] + if m.has_stream_msg: + m.stream_msg = self.get_stream_msg(m) m.reply.request = m except: - raise ParseError( - "Cannot find reply to message `%s'" % n) + raise ParseError("Cannot find reply to message `%s'" % n) except ParseError as e: self.exceptions.append(e) remove.append(n) - self.messages_by_json[jn] = { - k: v for k, v in j.items() if k not in remove} + self.messages_by_json[jn] = {k: v for k, v in j.items() if k not in remove} |