diff options
author | Klement Sekera <ksekera@cisco.com> | 2017-06-12 06:49:33 +0200 |
---|---|---|
committer | Neale Ranns <nranns@cisco.com> | 2017-09-19 20:06:08 +0000 |
commit | dc15be2ca7c51772b00e4c5548934a35aa7e4add (patch) | |
tree | ba4b707b73d21d3875264248a3affa93249816d3 /src/vpp-api/vapi | |
parent | 9d063047eb1a3738cb0fc9ebebb55793d155bb20 (diff) |
Add C++ API
Change-Id: Iff634f22d43470e2dc028387b3816257fd7b4156
Signed-off-by: Klement Sekera <ksekera@cisco.com>
Diffstat (limited to 'src/vpp-api/vapi')
-rw-r--r-- | src/vpp-api/vapi/Makefile.am | 27 | ||||
-rw-r--r-- | src/vpp-api/vapi/libvapiclient.map | 3 | ||||
-rw-r--r-- | src/vpp-api/vapi/vapi.c | 59 | ||||
-rw-r--r-- | src/vpp-api/vapi/vapi.h | 102 | ||||
-rw-r--r-- | src/vpp-api/vapi/vapi.hpp | 905 | ||||
-rwxr-xr-x | src/vpp-api/vapi/vapi_c_gen.py | 188 | ||||
-rw-r--r-- | src/vpp-api/vapi/vapi_common.h | 61 | ||||
-rwxr-xr-x | src/vpp-api/vapi/vapi_cpp_gen.py | 262 | ||||
-rw-r--r-- | src/vpp-api/vapi/vapi_dbg.h | 1 | ||||
-rw-r--r-- | src/vpp-api/vapi/vapi_doc.md | 155 | ||||
-rw-r--r-- | src/vpp-api/vapi/vapi_internal.h | 22 | ||||
-rw-r--r-- | src/vpp-api/vapi/vapi_json_parser.py | 2 |
12 files changed, 1548 insertions, 239 deletions
diff --git a/src/vpp-api/vapi/Makefile.am b/src/vpp-api/vapi/Makefile.am index ce681c38..74b2b47e 100644 --- a/src/vpp-api/vapi/Makefile.am +++ b/src/vpp-api/vapi/Makefile.am @@ -15,7 +15,7 @@ AUTOMAKE_OPTIONS = foreign ACLOCAL_AMFLAGS = -I m4 AM_LIBTOOLFLAGS = --quiet -AM_CFLAGS = -Wall -I${top_srcdir} -I${top_builddir} -I. -I$(top_srcdir)/vpp-api/vapi +AM_CFLAGS = -Wall -I${top_srcdir} -I${top_builddir} -I. -I$(top_srcdir)/vpp-api/ AM_LDFLAGS = -shared -avoid-version -rpath /none -no-undefined @@ -23,26 +23,33 @@ bin_PROGRAMS = noinst_LTLIBRARIES = CLEANDIRS = -%.api.vapi.h: %.api.json vapi_c_gen.py +vapi/%.api.vapi.h: %.api.json vapi_c_gen.py vapi_json_parser.py @echo " VAPI C GEN $< " $@ ; \ mkdir -p `dirname $@` ; \ - $(top_srcdir)/vpp-api/vapi/vapi_c_gen.py $< + $(top_srcdir)/vpp-api/vapi/vapi_c_gen.py --prefix=vapi $< + +vapi/%.api.vapi.hpp: %.api.json vapi_cpp_gen.py vapi_c_gen.py vapi_json_parser.py + @echo " VAPI CPP GEN $< " $@ ; \ + mkdir -p `dirname $@` ; \ + $(top_srcdir)/vpp-api/vapi/vapi_cpp_gen.py --prefix=vapi --gen-h-prefix=vapi $< %.api.json: find $(top_builddir) -name '$@' | xargs ln -s BUILT_SOURCES = $(shell find $(top_builddir) -name '*.api.json' | xargs -n1 basename) \ - $(patsubst %.api.json,%.api.vapi.h,$(JSON_FILES)) + $(patsubst %.api.json,vapi/%.api.vapi.h,$(JSON_FILES)) \ + $(patsubst %.api.json,vapi/%.api.vapi.hpp,$(JSON_FILES)) vapi.c: $(BUILT_SOURCES) JSON_FILES = $(wildcard *.api.json) - lib_LTLIBRARIES = libvapiclient.la libvapiclient_la_SOURCES = vapi.c +libvapiclient_la_DEPENDENCIES = libvapiclient.map + libvapiclient_la_LIBADD = -lpthread -lm -lrt \ $(top_builddir)/libvppinfra.la \ $(top_builddir)/libvlibmemoryclient.la \ @@ -54,10 +61,14 @@ libvapiclient_la_LDFLAGS = \ libvapiclient_la_CPPFLAGS = -I. -I$(top_builddir)/vpp-api/vapi -nobase_include_HEADERS = ${top_srcdir}/vpp-api/client/vppapiclient.h \ - vapi.h \ +vapiincludedir = $(includedir)/vapi + +vapiinclude_HEADERS = vapi.h \ + vapi.hpp \ vapi_dbg.h \ + vapi_common.h \ vapi_internal.h \ - $(patsubst %.api.json,%.api.vapi.h,$(JSON_FILES)) + $(patsubst %.api.json,vapi/%.api.vapi.h,$(JSON_FILES)) \ + $(patsubst %.api.json,vapi/%.api.vapi.hpp,$(JSON_FILES)) # vi:syntax=automake diff --git a/src/vpp-api/vapi/libvapiclient.map b/src/vpp-api/vapi/libvapiclient.map index 53733002..6b58d1e9 100644 --- a/src/vpp-api/vapi/libvapiclient.map +++ b/src/vpp-api/vapi/libvapiclient.map @@ -23,6 +23,7 @@ VAPICLIENT_17.07 { vapi_register_msg; vapi_get_client_index; vapi_is_nonblocking; + vapi_requests_empty; vapi_requests_full; vapi_gen_req_context; vapi_producer_lock; @@ -36,6 +37,8 @@ VAPICLIENT_17.07 { vapi_get_context_offset; vapi_msg_id_control_ping; vapi_msg_id_control_ping_reply; + vapi_get_message_count; + vapi_get_msg_name; local: *; }; diff --git a/src/vpp-api/vapi/vapi.c b/src/vpp-api/vapi/vapi.c index b9c81a13..59415e03 100644 --- a/src/vpp-api/vapi/vapi.c +++ b/src/vpp-api/vapi/vapi.c @@ -102,7 +102,7 @@ vapi_requests_full (vapi_ctx_t ctx) return (ctx->requests_count == ctx->requests_size); } -static bool +bool vapi_requests_empty (vapi_ctx_t ctx) { return (0 == ctx->requests_count); @@ -229,6 +229,16 @@ vapi_msg_free (vapi_ctx_t ctx, void *msg) vl_msg_api_free (msg); } +vapi_msg_id_t +vapi_lookup_vapi_msg_id_t (vapi_ctx_t ctx, u16 vl_msg_id) +{ + if (vl_msg_id <= ctx->vl_msg_id_max) + { + return ctx->vl_msg_id_to_vapi_msg_t[vl_msg_id]; + } + return ~0; +} + vapi_error_e vapi_ctx_alloc (vapi_ctx_t * result) { @@ -420,16 +430,17 @@ vapi_send (vapi_ctx_t ctx, void *msg) vapi_msg_id_t id = ctx->vl_msg_id_to_vapi_msg_t[msgid]; if (id < __vapi_metadata.count) { - VAPI_DBG ("send msg %u[%s]", msgid, __vapi_metadata.msgs[id]->name); + VAPI_DBG ("send msg@%p:%u[%s]", msg, msgid, + __vapi_metadata.msgs[id]->name); } else { - VAPI_DBG ("send msg %u[UNKNOWN]", msgid); + VAPI_DBG ("send msg@%p:%u[UNKNOWN]", msg, msgid); } } else { - VAPI_DBG ("send msg %u[UNKNOWN]", msgid); + VAPI_DBG ("send msg@%p:%u[UNKNOWN]", msg, msgid); } #endif tmp = unix_shared_memory_queue_add (q, (u8 *) & msg, @@ -522,7 +533,26 @@ vapi_recv (vapi_ctx_t ctx, void **msg, size_t * msg_size) } *msg = (u8 *) data; *msg_size = ntohl (msgbuf->data_len); - VAPI_DBG ("recv msg %p", *msg); +#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); + } +#endif } else { @@ -534,7 +564,6 @@ vapi_recv (vapi_ctx_t ctx, void **msg, size_t * msg_size) vapi_error_e vapi_wait (vapi_ctx_t ctx, vapi_wait_mode_e mode) { - /* FIXME */ return VAPI_ENOTSUP; } @@ -657,7 +686,7 @@ vapi_dispatch_event (vapi_ctx_t ctx, vapi_msg_id_t id, void *msg) return VAPI_OK; } -static bool +bool vapi_msg_is_with_context (vapi_msg_id_t id) { assert (id <= __vapi_metadata.count); @@ -785,10 +814,6 @@ vapi_is_nonblocking (vapi_ctx_t ctx) return (VAPI_MODE_NONBLOCKING == ctx->mode); } -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) { @@ -886,6 +911,18 @@ vapi_producer_unlock (vapi_ctx_t ctx) return VAPI_OK; } +size_t +vapi_get_message_count () +{ + return __vapi_metadata.count; +} + +const char * +vapi_get_msg_name (vapi_msg_id_t id) +{ + return __vapi_metadata.msgs[id]->name; +} + /* * fd.io coding-style-patch-verification: ON * diff --git a/src/vpp-api/vapi/vapi.h b/src/vpp-api/vapi/vapi.h index 1e1d567a..245bf654 100644 --- a/src/vpp-api/vapi/vapi.h +++ b/src/vpp-api/vapi/vapi.h @@ -21,6 +21,12 @@ #include <string.h> #include <stdbool.h> #include <vppinfra/types.h> +#include <vapi/vapi_common.h> + +#ifdef __cplusplus +extern "C" +{ +#endif /** * @file vapi.h @@ -36,39 +42,7 @@ * 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 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 a message can be read */ - 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 int vapi_msg_id_t; -typedef struct vapi_ctx_s *vapi_ctx_t; + typedef struct vapi_ctx_s *vapi_ctx_t; /** * @brief allocate vapi message of given size @@ -80,7 +54,7 @@ typedef struct vapi_ctx_s *vapi_ctx_t; * * @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 @@ -90,7 +64,7 @@ void *vapi_msg_alloc (vapi_ctx_t ctx, size_t size); * @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 @@ -99,18 +73,18 @@ void vapi_msg_free (vapi_ctx_t ctx, void *msg); * * @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 @@ -124,10 +98,10 @@ bool vapi_is_msg_available (vapi_ctx_t ctx, vapi_msg_id_t type); * * @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); + 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); /** * @brief disconnect from vpp @@ -136,7 +110,7 @@ vapi_error_e vapi_connect (vapi_ctx_t ctx, const char *name, * * @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); /** * @brief get event file descriptor @@ -149,7 +123,7 @@ vapi_error_e vapi_disconnect (vapi_ctx_t ctx); * * @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 @@ -162,7 +136,7 @@ vapi_error_e vapi_get_fd (vapi_ctx_t ctx, int *fd); * * @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 @@ -177,7 +151,7 @@ vapi_error_e vapi_send (vapi_ctx_t ctx, void *msg); * * @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 @@ -191,7 +165,7 @@ vapi_error_e vapi_send2 (vapi_ctx_t ctx, void *msg1, void *msg2); * * @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); + vapi_error_e vapi_recv (vapi_ctx_t ctx, void **msg, size_t * msg_size); /** * @brief wait for connection to become readable or writable @@ -201,14 +175,14 @@ vapi_error_e vapi_recv (vapi_ctx_t ctx, void **msg, size_t * msg_size); * * @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, vapi_wait_mode_e mode); /** * @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); + vapi_error_e vapi_dispatch_one (vapi_ctx_t ctx); /** * @brief loop vapi_dispatch_one until responses to all currently outstanding @@ -224,11 +198,11 @@ vapi_error_e vapi_dispatch_one (vapi_ctx_t ctx); * * @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 @@ -238,8 +212,8 @@ typedef vapi_error_e (*vapi_event_cb) (vapi_ctx_t ctx, void *callback_ctx, * @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 @@ -247,12 +221,12 @@ void vapi_set_event_cb (vapi_ctx_t ctx, vapi_msg_id_t id, * @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 * @@ -263,16 +237,20 @@ typedef vapi_error_e (*vapi_generic_event_cb) (vapi_ctx_t ctx, * @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); + +#ifdef __cplusplus +} +#endif #endif diff --git a/src/vpp-api/vapi/vapi.hpp b/src/vpp-api/vapi/vapi.hpp new file mode 100644 index 00000000..3be78b41 --- /dev/null +++ b/src/vpp-api/vapi/vapi.hpp @@ -0,0 +1,905 @@ +/* + *------------------------------------------------------------------ + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *------------------------------------------------------------------ + */ + +#ifndef vapi_hpp_included +#define vapi_hpp_included + +#include <cstddef> +#include <vector> +#include <mutex> +#include <queue> +#include <cassert> +#include <functional> +#include <algorithm> +#include <atomic> +#include <vppinfra/types.h> +#include <vapi/vapi.h> +#include <vapi/vapi_internal.h> +#include <vapi/vapi_dbg.h> +#include <vapi/vpe.api.vapi.h> + +#if VAPI_CPP_DEBUG_LEAKS +#include <unordered_set> +#endif + +/** + * @file + * @brief C++ VPP API + */ + +namespace vapi +{ + +class Connection; + +template <typename Req, typename Resp, typename... Args> class Request; +template <typename M> class Msg; +template <typename M> void vapi_swap_to_be (M *msg); +template <typename M> void vapi_swap_to_host (M *msg); +template <typename M, typename... Args> +M *vapi_alloc (Connection &con, Args...); +template <typename M> vapi_msg_id_t vapi_get_msg_id_t (); +template <typename M> class Event_registration; + +class Unexpected_msg_id_exception : public std::exception +{ +public: + virtual const char *what () const throw () + { + return "unexpected message id"; + } +}; + +class Msg_not_available_exception : public std::exception +{ +public: + virtual const char *what () const throw () + { + return "message unavailable"; + } +}; + +typedef enum { + /** response not ready yet */ + RESPONSE_NOT_READY, + + /** response to request is ready */ + RESPONSE_READY, + + /** no response to request (will never come) */ + RESPONSE_NO_RESPONSE, +} vapi_response_state_e; + +/** + * Class representing common functionality of a request - response state + * and context + */ +class Common_req +{ +public: + virtual ~Common_req (){}; + + Connection &get_connection () + { + return con; + }; + + vapi_response_state_e get_response_state (void) const + { + return response_state; + } + +private: + Connection &con; + Common_req (Connection &con) : con{con}, response_state{RESPONSE_NOT_READY} + { + } + + void set_response_state (vapi_response_state_e state) + { + response_state = state; + } + + virtual std::tuple<vapi_error_e, bool> assign_response (vapi_msg_id_t id, + void *shm_data) = 0; + + void set_context (u32 context) + { + this->context = context; + } + + u32 get_context () + { + return context; + } + + u32 context; + vapi_response_state_e response_state; + + friend class Connection; + + template <typename M> friend class Msg; + + template <typename Req, typename Resp, typename... Args> + friend class Request; + + template <typename Req, typename Resp, typename... Args> friend class Dump; + + template <typename M> friend class Event_registration; +}; + +/** + * Class representing a connection to VPP + * + * After creating a Connection object, call connect() to actually connect + * to VPP. Use is_msg_available to discover whether a specific message is known + * and supported by the VPP connected to. + */ +class Connection +{ +public: + Connection (void) : vapi_ctx{0}, event_count{0} + { + + vapi_error_e rv = VAPI_OK; + if (!vapi_ctx) + { + if (VAPI_OK != (rv = vapi_ctx_alloc (&vapi_ctx))) + { + throw std::bad_alloc (); + } + } + events.reserve (vapi_get_message_count () + 1); + } + + Connection (const Connection &) = delete; + + ~Connection (void) + { + vapi_ctx_free (vapi_ctx); +#if VAPI_CPP_DEBUG_LEAKS + for (auto x : shm_data_set) + { + printf ("Leaked shm_data@%p!\n", x); + } +#endif + } + + /** + * @brief check if message identified by it's message id is known by the + * vpp to which the connection is open + */ + bool is_msg_available (vapi_msg_id_t type) + { + return vapi_is_msg_available (vapi_ctx, type); + } + + /** + * @brief connect to vpp + * + * @param name application name + * @param chroot_prefix shared memory prefix + * @param max_queued_request max number of outstanding requests queued + * + * @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) + { + return vapi_connect (vapi_ctx, name, chroot_prefix, + max_outstanding_requests, response_queue_size, + VAPI_MODE_BLOCKING); + } + + /** + * @brief disconnect from vpp + * + * @return VAPI_OK on success, other error code on error + */ + vapi_error_e disconnect () + { + auto x = requests.size (); + while (x > 0) + { + VAPI_DBG ("popping request @%p", requests.front ()); + requests.pop_front (); + --x; + } + return vapi_disconnect (vapi_ctx); + }; + + /** + * @brief get event file descriptor + * + * @note this file descriptor becomes readable when messages (from vpp) + * are waiting in queue + * + * @param[out] fd pointer to result variable + * + * @return VAPI_OK on success, other error code on error + */ + vapi_error_e get_fd (int *fd) + { + return vapi_get_fd (vapi_ctx, fd); + } + + /** + * @brief wait for responses from vpp and assign them to appropriate objects + * + * @param limit stop dispatch after the limit object received it's response + * + * @return VAPI_OK on success, other error code on error + */ + vapi_error_e dispatch (const Common_req *limit = nullptr) + { + std::lock_guard<std::mutex> lock (dispatch_mutex); + vapi_error_e rv = VAPI_OK; + bool loop_again = true; + while (loop_again) + { + void *shm_data; + size_t shm_data_size; + rv = vapi_recv (vapi_ctx, &shm_data, &shm_data_size); + if (VAPI_OK != rv) + { + return rv; + } +#if VAPI_CPP_DEBUG_LEAKS + on_shm_data_alloc (shm_data); +#endif + std::lock_guard<std::recursive_mutex> requests_lock (requests_mutex); + std::lock_guard<std::recursive_mutex> events_lock (events_mutex); + vapi_msg_id_t id = vapi_lookup_vapi_msg_id_t ( + vapi_ctx, be16toh (*static_cast<u16 *> (shm_data))); + bool has_context = vapi_msg_is_with_context (id); + bool break_dispatch = false; + Common_req *matching_req = nullptr; + if (has_context) + { + u32 context = *reinterpret_cast<u32 *> ( + (static_cast<u8 *> (shm_data) + vapi_get_context_offset (id))); + const auto x = requests.front (); + matching_req = x; + if (context == x->context) + { + std::tie (rv, break_dispatch) = + x->assign_response (id, shm_data); + } + else + { + std::tie (rv, break_dispatch) = + x->assign_response (id, nullptr); + } + if (break_dispatch) + { + requests.pop_front (); + } + } + else + { + if (events[id]) + { + std::tie (rv, break_dispatch) = + events[id]->assign_response (id, shm_data); + matching_req = events[id]; + } + else + { + msg_free (shm_data); + } + } + if ((matching_req && matching_req == limit && break_dispatch) || + VAPI_OK != rv) + { + return rv; + } + loop_again = !requests.empty () || (event_count > 0); + } + return rv; + } + + /** + * @brief convenience wrapper function + */ + vapi_error_e dispatch (const Common_req &limit) + { + return dispatch (&limit); + } + + /** + * @brief wait for response to a specific request + * + * @param req request to wait for response for + * + * @return VAPI_OK on success, other error code on error + */ + vapi_error_e wait_for_response (const Common_req &req) + { + if (RESPONSE_READY == req.get_response_state ()) + { + return VAPI_OK; + } + return dispatch (req); + } + +private: + void msg_free (void *shm_data) + { +#if VAPI_CPP_DEBUG_LEAKS + on_shm_data_free (shm_data); +#endif + vapi_msg_free (vapi_ctx, shm_data); + } + + template <template <typename XReq, typename XResp, typename... XArgs> + class X, + typename Req, typename Resp, typename... Args> + vapi_error_e send (X<Req, Resp, Args...> *req) + { + if (!req) + { + return VAPI_EINVAL; + } + u32 req_context = + req_context_counter.fetch_add (1, std::memory_order_relaxed); + req->request.shm_data->header.context = req_context; + vapi_swap_to_be<Req> (req->request.shm_data); + std::lock_guard<std::recursive_mutex> lock (requests_mutex); + vapi_error_e rv = vapi_send (vapi_ctx, req->request.shm_data); + if (VAPI_OK == rv) + { + VAPI_DBG ("Push %p", req); + requests.emplace_back (req); + req->set_context (req_context); +#if VAPI_CPP_DEBUG_LEAKS + on_shm_data_free (req->request.shm_data); +#endif + req->request.shm_data = nullptr; /* consumed by vapi_send */ + } + else + { + vapi_swap_to_host<Req> (req->request.shm_data); + } + return rv; + } + + template <template <typename XReq, typename XResp, typename... XArgs> + class X, + typename Req, typename Resp, typename... Args> + vapi_error_e send_with_control_ping (X<Req, Resp, Args...> *req) + { + if (!req) + { + return VAPI_EINVAL; + } + u32 req_context = + req_context_counter.fetch_add (1, std::memory_order_relaxed); + req->request.shm_data->header.context = req_context; + vapi_swap_to_be<Req> (req->request.shm_data); + std::lock_guard<std::recursive_mutex> lock (requests_mutex); + vapi_error_e rv = vapi_send_with_control_ping ( + vapi_ctx, req->request.shm_data, req_context); + if (VAPI_OK == rv) + { + VAPI_DBG ("Push %p", req); + requests.emplace_back (req); + req->set_context (req_context); +#if VAPI_CPP_DEBUG_LEAKS + on_shm_data_free (req->request.shm_data); +#endif + req->request.shm_data = nullptr; /* consumed by vapi_send */ + } + else + { + vapi_swap_to_host<Req> (req->request.shm_data); + } + return rv; + } + + void unregister_request (Common_req *request) + { + std::lock_guard<std::recursive_mutex> lock (requests_mutex); + std::remove (requests.begin (), requests.end (), request); + } + + template <typename M> void register_event (Event_registration<M> *event) + { + const vapi_msg_id_t id = M::get_msg_id (); + std::lock_guard<std::recursive_mutex> lock (events_mutex); + events[id] = event; + ++event_count; + } + + template <typename M> void unregister_event (Event_registration<M> *event) + { + const vapi_msg_id_t id = M::get_msg_id (); + std::lock_guard<std::recursive_mutex> lock (events_mutex); + events[id] = nullptr; + --event_count; + } + + vapi_ctx_t vapi_ctx; + std::atomic_ulong req_context_counter; + std::mutex dispatch_mutex; + + std::recursive_mutex requests_mutex; + std::recursive_mutex events_mutex; + std::deque<Common_req *> requests; + std::vector<Common_req *> events; + int event_count; + + template <typename Req, typename Resp, typename... Args> + friend class Request; + + template <typename Req, typename Resp, typename... Args> friend class Dump; + + template <typename M> friend class Result_set; + + template <typename M> friend class Event_registration; + + template <typename M, typename... Args> + friend M *vapi_alloc (Connection &con, Args...); + + template <typename M> friend class Msg; + +#if VAPI_CPP_DEBUG_LEAKS + void on_shm_data_alloc (void *shm_data) + { + if (shm_data) + { + auto pos = shm_data_set.find (shm_data); + if (pos == shm_data_set.end ()) + { + shm_data_set.insert (shm_data); + } + else + { + printf ("Double-add shm_data @%p!\n", shm_data); + } + } + } + + void on_shm_data_free (void *shm_data) + { + auto pos = shm_data_set.find (shm_data); + if (pos == shm_data_set.end ()) + { + printf ("Freeing untracked shm_data @%p!\n", shm_data); + } + else + { + shm_data_set.erase (pos); + } + } + std::unordered_set<void *> shm_data_set; +#endif +}; + +template <typename Req, typename Resp, typename... Args> class Request; + +template <typename Req, typename Resp, typename... Args> class Dump; + +template <class, class = void> struct vapi_has_payload_trait : std::false_type +{ +}; + +template <class... T> using vapi_void_t = void; + +template <class T> +struct vapi_has_payload_trait<T, vapi_void_t<decltype (&T::payload)>> + : std::true_type +{ +}; + +template <typename M> void vapi_msg_set_msg_id (vapi_msg_id_t id) +{ + Msg<M>::set_msg_id (id); +} + +/** + * Class representing a message stored in shared memory + */ +template <typename M> class Msg +{ +public: + Msg (const Msg &) = delete; + + ~Msg () + { + VAPI_DBG ("Destroy Msg<%s>@%p, shm_data@%p", + vapi_get_msg_name (get_msg_id ()), this, shm_data); + if (shm_data) + { + con.get ().msg_free (shm_data); + shm_data = nullptr; + } + } + + static vapi_msg_id_t get_msg_id () + { + return *msg_id_holder (); + } + + template <typename X = M> + typename std::enable_if<vapi_has_payload_trait<X>::value, + decltype (X::payload) &>::type + get_payload () const + { + return shm_data->payload; + } + +private: + Msg (Msg<M> &&msg) : con{msg.con} + { + VAPI_DBG ("Move construct Msg<%s> from msg@%p to msg@%p, shm_data@%p", + vapi_get_msg_name (get_msg_id ()), &msg, this, msg.shm_data); + shm_data = msg.shm_data; + msg.shm_data = nullptr; + } + + Msg<M> &operator= (Msg<M> &&msg) + { + VAPI_DBG ("Move assign Msg<%s> from msg@%p to msg@%p, shm_data@%p", + vapi_get_msg_name (get_msg_id ()), &msg, this, msg.shm_data); + con.get ().msg_free (shm_data); + con = msg.con; + shm_data = msg.shm_data; + msg.shm_data = nullptr; + return *this; + } + + struct Msg_allocator : std::allocator<Msg<M>> + { + template <class U, class... Args> void construct (U *p, Args &&... args) + { + ::new ((void *)p) U (std::forward<Args> (args)...); + } + + template <class U> struct rebind + { + typedef Msg_allocator other; + }; + }; + + static void set_msg_id (vapi_msg_id_t id) + { + assert ((~0 == *msg_id_holder ()) || (id == *msg_id_holder ())); + *msg_id_holder () = id; + } + + static vapi_msg_id_t *msg_id_holder () + { + static vapi_msg_id_t my_id{~0}; + return &my_id; + } + + Msg (Connection &con, void *shm_data) throw (Msg_not_available_exception) + : con{con} + { + if (!con.is_msg_available (get_msg_id ())) + { + throw Msg_not_available_exception (); + } + this->shm_data = static_cast<shm_data_type *> (shm_data); + VAPI_DBG ("New Msg<%s>@%p shm_data@%p", vapi_get_msg_name (get_msg_id ()), + this, shm_data); + } + + void assign_response (vapi_msg_id_t resp_id, + void *shm_data) throw (Unexpected_msg_id_exception) + { + assert (nullptr == this->shm_data); + if (resp_id != get_msg_id ()) + { + throw Unexpected_msg_id_exception (); + } + this->shm_data = static_cast<M *> (shm_data); + vapi_swap_to_host<M> (this->shm_data); + VAPI_DBG ("Assign response to Msg<%s>@%p shm_data@%p", + vapi_get_msg_name (get_msg_id ()), this, shm_data); + } + + std::reference_wrapper<Connection> con; + using shm_data_type = M; + shm_data_type *shm_data; + + friend class Connection; + + template <typename Req, typename Resp, typename... Args> + friend class Request; + + template <typename Req, typename Resp, typename... Args> friend class Dump; + + template <typename X> friend class Event_registration; + + template <typename X> friend class Result_set; + + friend struct Msg_allocator; + + template <typename X> friend void vapi_msg_set_msg_id (vapi_msg_id_t id); +}; + +/** + * Class representing a simple request - with a single response message + */ +template <typename Req, typename Resp, typename... Args> +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} + { + } + + Request (const Request &) = delete; + + virtual ~Request () + { + if (RESPONSE_NOT_READY == get_response_state ()) + { + con.unregister_request (this); + } + } + + vapi_error_e execute () + { + return con.send (this); + } + + const Msg<Req> &get_request (void) const + { + return request; + } + + const Msg<Resp> &get_response (void) + { + return response; + } + +private: + virtual std::tuple<vapi_error_e, bool> assign_response (vapi_msg_id_t id, + void *shm_data) + { + assert (RESPONSE_NOT_READY == get_response_state ()); + response.assign_response (id, shm_data); + set_response_state (RESPONSE_READY); + if (nullptr != callback) + { + return std::make_pair (callback (*this), true); + } + return std::make_pair (VAPI_OK, true); + } + std::function<vapi_error_e (Request<Req, Resp, Args...> &)> callback; + Msg<Req> request; + Msg<Resp> response; + + friend class Connection; +}; + +/** + * Class representing iterable set of responses of the same type + */ +template <typename M> class Result_set +{ +public: + ~Result_set () + { + } + + Result_set (const Result_set &) = delete; + + bool is_complete () const + { + return complete; + } + + size_t size () const + { + return set.size (); + } + + using const_iterator = + typename std::vector<Msg<M>, + typename Msg<M>::Msg_allocator>::const_iterator; + + const_iterator begin () const + { + return set.begin (); + } + + const_iterator end () const + { + return set.end (); + } + + void free_response (const_iterator pos) + { + set.erase (pos); + } + + void free_all_responses () + { + set.clear (); + } + +private: + void mark_complete () + { + complete = true; + } + + void assign_response (vapi_msg_id_t resp_id, + void *shm_data) throw (Unexpected_msg_id_exception) + { + if (resp_id != Msg<M>::get_msg_id ()) + { + { + throw Unexpected_msg_id_exception (); + } + } + else if (shm_data) + { + vapi_swap_to_host<M> (static_cast<M *> (shm_data)); + set.emplace_back (con, shm_data); + VAPI_DBG ("Result_set@%p emplace_back shm_data@%p", this, shm_data); + } + } + + Result_set (Connection &con) : con{con}, complete{false} + { + } + + Connection &con; + bool complete; + std::vector<Msg<M>, typename Msg<M>::Msg_allocator> set; + + template <typename Req, typename Resp, typename... Args> friend class Dump; + + template <typename X> friend class Event_registration; +}; + +/** + * Class representing a dump request - zero or more identical responses to a + * single request message + */ +template <typename Req, typename Resp, typename... Args> +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} + { + } + + Dump (const Dump &) = delete; + + virtual ~Dump () + { + } + + virtual std::tuple<vapi_error_e, bool> assign_response (vapi_msg_id_t id, + void *shm_data) + { + if (id == vapi_msg_id_control_ping_reply) + { + con.msg_free (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_with_control_ping (this); + } + + Msg<Req> &get_request (void) + { + return request; + } + + using resp_type = typename Msg<Resp>::shm_data_type; + + const Result_set<Resp> &get_result_set (void) const + { + return result_set; + } + +private: + Msg<Req> request; + Result_set<resp_type> result_set; + std::function<vapi_error_e (Dump<Req, Resp, Args...> &)> callback; + + friend class Connection; +}; + +/** + * Class representing event registration - incoming events (messages) from + * vpp as a result of a subscription (typically a want_* simple request) + */ +template <typename M> class Event_registration : public Common_req +{ +public: + Event_registration ( + Connection &con, + std::function<vapi_error_e (Event_registration<M> &)> callback = + nullptr) throw (Msg_not_available_exception) + : Common_req{con}, result_set{con}, callback{callback} + { + if (!con.is_msg_available (M::get_msg_id ())) + { + throw Msg_not_available_exception (); + } + con.register_event (this); + } + + Event_registration (const Event_registration &) = delete; + + virtual ~Event_registration () + { + con.unregister_event (this); + } + + virtual std::tuple<vapi_error_e, bool> assign_response (vapi_msg_id_t id, + void *shm_data) + { + result_set.assign_response (id, shm_data); + if (nullptr != callback) + { + return std::make_pair (callback (*this), true); + } + return std::make_pair (VAPI_OK, true); + } + + using resp_type = typename M::shm_data_type; + + Result_set<resp_type> &get_result_set (void) + { + return result_set; + } + +private: + Result_set<resp_type> result_set; + std::function<vapi_error_e (Event_registration<M> &)> callback; +}; +}; + +#endif + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/vpp-api/vapi/vapi_c_gen.py b/src/vpp-api/vapi/vapi_c_gen.py index 2bc1eef8..ef6e2663 100755 --- a/src/vpp-api/vapi/vapi_c_gen.py +++ b/src/vpp-api/vapi/vapi_c_gen.py @@ -26,7 +26,7 @@ class CField(Field): def get_swap_to_be_code(self, struct, var): if self.len is not None: if self.len > 0: - return "do { int i; for (i = 0; i < %d; ++i) { %s } }"\ + 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)) @@ -38,7 +38,7 @@ class CField(Field): else: nelem_field = "%s%s" % (struct, self.nelem_field.name) return ( - "do { int i; for (i = 0; i < %s; ++i) { %s } }" + "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))) @@ -47,14 +47,14 @@ class CField(Field): def get_swap_to_host_code(self, struct, var): if self.len is not None: if self.len > 0: - return "do { int i; for (i = 0; i < %d; ++i) { %s } }"\ + 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 { int i; for (i = 0; i < %s%s; ++i) { %s } }" + "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( @@ -199,14 +199,17 @@ class CMessage (Message): def get_alloc_func_name(self): return "vapi_alloc_%s" % self.name + def get_alloc_vla_param_names(self): + return [self.get_alloc_func_vla_field_length_name(f) + for f in self.fields + if f.nelem_field is not None] + 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" % - self.get_alloc_func_vla_field_length_name(f) - for f in self.fields - if f.nelem_field is not None])) + "".join([", size_t %s" % n for n in + self.get_alloc_vla_param_names()])) def get_alloc_func_def(self): extra = [] @@ -228,7 +231,8 @@ class CMessage (Message): for f in self.fields if f.nelem_field is not None ])), - " msg = vapi_msg_alloc(ctx, size);", + " /* cast here required to play nicely with C++ world ... */", + " msg = (%s*)vapi_msg_alloc(ctx, size);" % self.get_c_name(), " if (!msg) {", " return NULL;", " }", @@ -441,7 +445,7 @@ class CMessage (Message): def get_event_cb_func_decl(self): if not self.is_reply(): raise Exception( - "Cannot register event callback for non-reply function") + "Cannot register event callback for non-reply message") if self.has_payload(): return "\n".join([ "void vapi_set_%s_event_cb (" % @@ -498,7 +502,7 @@ class CMessage (Message): ' 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 '-1,', + if self.has_payload() else ' ~0,', ' 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(), @@ -529,8 +533,8 @@ vapi_send_with_control_ping (vapi_ctx_t ctx, void *msg, u32 context) """ -def gen_json_header(parser, logger, j, io): - logger.info("Generating header `%s'" % io.name) +def gen_json_unified_header(parser, logger, j, io, name): + logger.info("Generating header `%s'" % name) orig_stdout = sys.stdout sys.stdout = io include_guard = "__included_%s" % ( @@ -538,131 +542,22 @@ def gen_json_header(parser, logger, j, io): print("#ifndef %s" % include_guard) print("#define %s" % include_guard) print("") - print("#include <vapi_internal.h>") - print("") - if io.name == "vpe.api.vapi.h": - print("static inline vapi_error_e vapi_send_with_control_ping " - "(vapi_ctx_t ctx, void * msg, u32 context);") - print("") - for m in parser.messages_by_json[j].values(): - print("extern vapi_msg_id_t %s;" % m.get_msg_id_name()) - print("") - for t in parser.types_by_json[j].values(): - try: - print("%s" % t.get_c_def()) - print("") - except: - pass - for t in parser.types_by_json[j].values(): - print("%s;" % t.get_swap_to_be_func_decl()) - print("") - print("%s;" % t.get_swap_to_host_func_decl()) - print("") - for m in parser.messages_by_json[j].values(): - print("%s" % m.get_c_def()) - print("") - for m in parser.messages_by_json[j].values(): - if not m.is_reply(): - print("%s;" % m.get_alloc_func_decl()) - print("") - print("%s;" % m.get_op_func_decl()) - if m.has_payload(): - print("%s;" % m.get_swap_payload_to_be_func_decl()) - print("") - print("%s;" % m.get_swap_payload_to_host_func_decl()) - print("") - print("%s;" % m.get_calc_msg_size_func_decl()) - print("") - print("%s;" % m.get_swap_to_host_func_decl()) - print("") - print("%s;" % m.get_swap_to_be_func_decl()) - print("") - for m in parser.messages_by_json[j].values(): - if not m.is_reply(): - continue - print("%s;" % m.get_event_cb_func_decl()) - print("") - - if io.name == "vpe.api.vapi.h": - print("%s" % vapi_send_with_control_ping) - print("") - - print("#endif") - sys.stdout = orig_stdout - - -def gen_json_code(parser, logger, j, io): - logger.info("Generating code `%s'" % io.name) - orig_stdout = sys.stdout - sys.stdout = io - print("#include <%s>" % json_to_header_name(j)) print("#include <stdlib.h>") print("#include <stddef.h>") print("#include <arpa/inet.h>") - print("#include <vapi_internal.h>") - print("#include <vapi_dbg.h>") - print("") - for t in parser.types_by_json[j].values(): - print("%s" % t.get_swap_to_be_func_def()) - print("") - print("%s" % t.get_swap_to_host_func_def()) - print("") - for m in parser.messages_by_json[j].values(): - if m.has_payload(): - print("%s" % m.get_swap_payload_to_be_func_def()) - print("") - print("%s" % m.get_swap_payload_to_host_func_def()) - print("") - print("%s" % m.get_calc_msg_size_func_def()) - print("") - print("%s" % m.get_swap_to_be_func_def()) - print("") - print("%s" % m.get_swap_to_host_func_def()) - print("") - for m in parser.messages_by_json[j].values(): - if m.is_reply(): - continue - print("%s" % m.get_alloc_func_def()) - print("") - print("%s" % m.get_op_func_def()) - print("") + print("#include <vapi/vapi_internal.h>") + print("#include <vapi/vapi.h>") + print("#include <vapi/vapi_dbg.h>") print("") - for m in parser.messages_by_json[j].values(): - print("%s" % m.get_c_constructor()) - print("") - print("") - for m in parser.messages_by_json[j].values(): - if not m.is_reply(): - continue - print("%s;" % m.get_event_cb_func_def()) - print("") - print("") - for m in parser.messages_by_json[j].values(): - print("vapi_msg_id_t %s;" % m.get_msg_id_name()) - sys.stdout = orig_stdout - - -def gen_json_unified_header(parser, logger, j, io): - logger.info("Generating header `%s'" % io.name) - orig_stdout = sys.stdout - sys.stdout = io - include_guard = "__included_%s" % ( - j.replace(".", "_").replace("/", "_").replace("-", "_")) - print("#ifndef %s" % include_guard) - print("#define %s" % include_guard) - print("") - print("#include <vapi_internal.h>") - print("#include <vapi.h>") - print("#include <stdlib.h>") - print("#include <stddef.h>") - print("#include <arpa/inet.h>") - print("#include <vapi_dbg.h>") - if io.name == "vpe.api.vapi.h": + print("#ifdef __cplusplus") + print("extern \"C\" {") + print("#endif") + if name == "vpe.api.vapi.h": print("") print("static inline vapi_error_e vapi_send_with_control_ping " "(vapi_ctx_t ctx, void * msg, u32 context);") else: - print("#include <vpe.api.vapi.h>") + print("#include <vapi/vpe.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()) @@ -725,46 +620,33 @@ def gen_json_unified_header(parser, logger, j, io): print("") print("") - if io.name == "vpe.api.vapi.h": + if name == "vpe.api.vapi.h": print("%s" % vapi_send_with_control_ping) print("") + print("#ifdef __cplusplus") + print("}") + print("#endif") + print("") print("#endif") sys.stdout = orig_stdout -def json_to_header_name(json_name): +def json_to_c_header_name(json_name): if json_name.endswith(".json"): return "%s.vapi.h" % os.path.splitext(json_name)[0] raise Exception("Unexpected json name `%s'!" % json_name) -def json_to_code_name(json_name): - if json_name.endswith(".json"): - return "%s.vapi.c" % os.path.splitext(json_name)[0] - raise Exception("Unexpected json name `%s'!" % json_name) - - -def gen_c_headers_and_code(parser, logger, prefix): - if prefix == "" or prefix is None: - prefix = "" - else: - prefix = "%s/" % prefix - for j in parser.json_files: - with open('%s%s' % (prefix, json_to_header_name(j)), "w") as io: - gen_json_header(parser, logger, j, io) - with open('%s%s' % (prefix, json_to_code_name(j)), "w") as io: - gen_json_code(parser, logger, j, io) - - def gen_c_unified_headers(parser, logger, prefix): if prefix == "" or prefix is None: prefix = "" else: prefix = "%s/" % prefix for j in parser.json_files: - with open('%s%s' % (prefix, json_to_header_name(j)), "w") as io: - gen_json_unified_header(parser, logger, j, io) + with open('%s%s' % (prefix, json_to_c_header_name(j)), "w") as io: + gen_json_unified_header( + parser, logger, j, io, json_to_c_header_name(j)) if __name__ == '__main__': @@ -784,7 +666,7 @@ if __name__ == '__main__': logger = logging.getLogger("VAPI C GEN") logger.setLevel(log_level) - argparser = argparse.ArgumentParser(description="VPP JSON API parser") + 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)') diff --git a/src/vpp-api/vapi/vapi_common.h b/src/vpp-api/vapi/vapi_common.h new file mode 100644 index 00000000..ce64469d --- /dev/null +++ b/src/vpp-api/vapi/vapi_common.h @@ -0,0 +1,61 @@ +/* + *------------------------------------------------------------------ + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *------------------------------------------------------------------ + */ + +#ifndef vapi_common_h_included +#define vapi_common_h_included + +#ifdef __cplusplus +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 int vapi_msg_id_t; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/vpp-api/vapi/vapi_cpp_gen.py b/src/vpp-api/vapi/vapi_cpp_gen.py new file mode 100755 index 00000000..6e9f5d3f --- /dev/null +++ b/src/vpp-api/vapi/vapi_cpp_gen.py @@ -0,0 +1,262 @@ +#!/usr/bin/env python3 + +import argparse +import os +import sys +import logging +from vapi_c_gen import CField, CStruct, CSimpleType, CStructType, CMessage, \ + json_to_c_header_name +from vapi_json_parser import JsonParser + + +class CppField(CField): + def __init__( + self, + field_name, + field_type, + array_len=None, + nelem_field=None): + super().__init__(field_name, field_type, array_len, nelem_field) + + +class CppStruct(CStruct): + def __init__(self, name, fields): + super().__init__(name, fields) + + +class CppSimpleType (CSimpleType): + + def __init__(self, name): + super().__init__(name) + + +class CppStructType (CStructType, CppStruct): + def __init__(self, definition, typedict, field_class): + super().__init__(definition, typedict, field_class) + + +class CppMessage (CMessage): + def __init__(self, logger, definition, typedict, + struct_type_class, simple_type_class, field_class): + super().__init__(logger, definition, typedict, struct_type_class, + simple_type_class, field_class) + + 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(), + "}", + ]) + + 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(), + "}", + ]) + + 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;", + "}", + ]) + + def get_cpp_name(self): + return "%s%s" % (self.name[0].upper(), self.name[1:]) + + def get_req_template_name(self): + if self.is_dump(): + template = "Dump" + else: + template = "Request" + + return "%s<%s, %s%s>" % ( + template, + self.get_c_name(), + self.reply.get_c_name(), + "".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()) + + 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()) + + 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(), + "}", + ]) + + 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())), + '}', + ]) + + +def gen_json_header(parser, logger, j, io, gen_h_prefix, add_debug_comments): + logger.info("Generating header `%s'" % io.name) + orig_stdout = sys.stdout + sys.stdout = io + include_guard = "__included_hpp_%s" % ( + j.replace(".", "_").replace("/", "_").replace("-", "_")) + print("#ifndef %s" % include_guard) + print("#define %s" % include_guard) + print("") + print("#include <vapi/vapi.hpp>") + print("#include <%s%s>" % (gen_h_prefix, json_to_c_header_name(j))) + print("") + print("namespace vapi {") + print("") + for m in parser.messages_by_json[j].values(): + # utility functions need to go first, otherwise internal instantiation + # causes headaches ... + if add_debug_comments: + print("/* m.get_swap_to_be_template_instantiation() */") + print("%s" % m.get_swap_to_be_template_instantiation()) + print("") + if add_debug_comments: + print("/* m.get_swap_to_host_template_instantiation() */") + print("%s" % m.get_swap_to_host_template_instantiation()) + print("") + if add_debug_comments: + print("/* m.get_get_msg_id_t_instantiation() */") + print("%s" % m.get_get_msg_id_t_instantiation()) + print("") + if add_debug_comments: + print("/* m.get_cpp_constructor() */") + print("%s" % m.get_cpp_constructor()) + print("") + if not m.is_reply(): + if add_debug_comments: + print("/* m.get_alloc_template_instantiation() */") + print("%s" % m.get_alloc_template_instantiation()) + print("") + if add_debug_comments: + print("/* m.get_msg_class_instantiation() */") + print("%s" % m.get_msg_class_instantiation()) + print("") + if m.is_reply(): + if add_debug_comments: + print("/* m.get_reply_type_alias() */") + print("%s" % m.get_reply_type_alias()) + continue + if add_debug_comments: + print("/* m.get_req_template_instantiation() */") + print("%s" % m.get_req_template_instantiation()) + print("") + if add_debug_comments: + print("/* m.get_type_alias() */") + print("%s" % m.get_type_alias()) + print("") + print("}") # namespace vapi + + print("#endif") + sys.stdout = orig_stdout + + +def json_to_cpp_header_name(json_name): + if json_name.endswith(".json"): + return "%s.vapi.hpp" % os.path.splitext(json_name)[0] + raise Exception("Unexpected json name `%s'!" % json_name) + + +def gen_cpp_headers(parser, logger, prefix, gen_h_prefix, + add_debug_comments=False): + if prefix == "" or prefix is None: + prefix = "" + else: + prefix = "%s/" % prefix + if gen_h_prefix is None: + gen_h_prefix = "" + else: + gen_h_prefix = "%s/" % gen_h_prefix + for j in parser.json_files: + with open('%s%s' % (prefix, json_to_cpp_header_name(j)), "w") as io: + gen_json_header(parser, logger, j, io, + gen_h_prefix, add_debug_comments) + + +if __name__ == '__main__': + try: + verbose = int(os.getenv("V", 0)) + except: + verbose = 0 + + if verbose >= 2: + log_level = 10 + elif verbose == 1: + log_level = 20 + else: + log_level = 40 + + logging.basicConfig(stream=sys.stdout, level=log_level) + logger = logging.getLogger("VAPI CPP GEN") + 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') + args = argparser.parse_args() + + jsonparser = JsonParser(logger, args.files, + simple_type_class=CppSimpleType, + struct_type_class=CppStructType, + field_class=CppField, + message_class=CppMessage) + + gen_cpp_headers(jsonparser, logger, args.prefix, args.gen_h_prefix) + + for e in jsonparser.exceptions: + logger.error(e) diff --git a/src/vpp-api/vapi/vapi_dbg.h b/src/vpp-api/vapi/vapi_dbg.h index 95a80089..ec3a3006 100644 --- a/src/vpp-api/vapi/vapi_dbg.h +++ b/src/vpp-api/vapi/vapi_dbg.h @@ -22,6 +22,7 @@ #define VAPI_DEBUG (0) #define VAPI_DEBUG_CONNECT (0) #define VAPI_DEBUG_ALLOC (0) +#define VAPI_CPP_DEBUG_LEAKS (0) #if VAPI_DEBUG #include <stdio.h> diff --git a/src/vpp-api/vapi/vapi_doc.md b/src/vpp-api/vapi/vapi_doc.md new file mode 100644 index 00000000..0e7e29dd --- /dev/null +++ b/src/vpp-api/vapi/vapi_doc.md @@ -0,0 +1,155 @@ +# 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_internal.h b/src/vpp-api/vapi/vapi_internal.h index 5b85788d..2c51c673 100644 --- a/src/vpp-api/vapi/vapi_internal.h +++ b/src/vpp-api/vapi/vapi_internal.h @@ -18,6 +18,7 @@ #ifndef VAPI_INTERNAL_H #define VAPI_INTERNAL_H +#include <endian.h> #include <string.h> #include <vppinfra/types.h> @@ -31,6 +32,10 @@ * time.. */ +#ifdef __cplusplus +extern "C" { +#endif + struct vapi_ctx_s; typedef struct __attribute__ ((__packed__)) @@ -71,7 +76,7 @@ vapi_type_msg_header2_t_ntoh (vapi_type_msg_header2_t * h) } -#include <vapi.h> +#include <vapi/vapi.h> typedef vapi_error_e (*vapi_cb_t) (struct vapi_ctx_s *, void *, vapi_error_e, bool, void *); @@ -85,8 +90,8 @@ typedef struct const char *name_with_crc; size_t name_with_crc_len; bool has_context; - size_t context_offset; - size_t payload_offset; + int context_offset; + int payload_offset; size_t size; generic_swap_fn_t swap_to_be; generic_swap_fn_t swap_to_host; @@ -102,12 +107,12 @@ typedef struct void (*swap_to_host) (void *payload); } vapi_event_desc_t; -extern bool *__vapi_msg_is_with_context; - vapi_msg_id_t vapi_register_msg (vapi_message_desc_t * msg); u16 vapi_lookup_vl_msg_id (vapi_ctx_t ctx, vapi_msg_id_t id); +vapi_msg_id_t vapi_lookup_vapi_msg_id_t (vapi_ctx_t ctx, u16 vl_msg_id); int vapi_get_client_index (vapi_ctx_t ctx); bool vapi_is_nonblocking (vapi_ctx_t ctx); +bool vapi_requests_empty (vapi_ctx_t ctx); 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); @@ -119,8 +124,15 @@ 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(); +const char *vapi_get_msg_name(vapi_msg_id_t id); vapi_error_e vapi_producer_lock (vapi_ctx_t ctx); vapi_error_e vapi_producer_unlock (vapi_ctx_t ctx); +#ifdef __cplusplus +} +#endif + #endif diff --git a/src/vpp-api/vapi/vapi_json_parser.py b/src/vpp-api/vapi/vapi_json_parser.py index 57a22383..1e17c7a5 100644 --- a/src/vpp-api/vapi/vapi_json_parser.py +++ b/src/vpp-api/vapi/vapi_json_parser.py @@ -90,6 +90,7 @@ class Message: def __init__(self, logger, definition, typedict, struct_type_class, simple_type_class, field_class): + self.request = None self.logger = logger m = definition logger.debug("Parsing message definition `%s'" % m) @@ -292,6 +293,7 @@ class JsonParser: if not m.is_reply(): try: m.reply = self.get_reply(n) + m.reply.request = m except: raise ParseError( "Cannot find reply to message `%s'" % n) |