aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKlement Sekera <ksekera@cisco.com>2017-06-12 06:49:33 +0200
committerNeale Ranns <nranns@cisco.com>2017-09-19 20:06:08 +0000
commitdc15be2ca7c51772b00e4c5548934a35aa7e4add (patch)
treeba4b707b73d21d3875264248a3affa93249816d3
parent9d063047eb1a3738cb0fc9ebebb55793d155bb20 (diff)
Add C++ API
Change-Id: Iff634f22d43470e2dc028387b3816257fd7b4156 Signed-off-by: Klement Sekera <ksekera@cisco.com>
-rw-r--r--.clang-format38
-rwxr-xr-xbuild-root/scripts/checkstyle.sh62
-rw-r--r--src/configure.ac1
-rw-r--r--src/vpp-api/vapi/Makefile.am27
-rw-r--r--src/vpp-api/vapi/libvapiclient.map3
-rw-r--r--src/vpp-api/vapi/vapi.c59
-rw-r--r--src/vpp-api/vapi/vapi.h102
-rw-r--r--src/vpp-api/vapi/vapi.hpp905
-rwxr-xr-xsrc/vpp-api/vapi/vapi_c_gen.py188
-rw-r--r--src/vpp-api/vapi/vapi_common.h61
-rwxr-xr-xsrc/vpp-api/vapi/vapi_cpp_gen.py262
-rw-r--r--src/vpp-api/vapi/vapi_dbg.h1
-rw-r--r--src/vpp-api/vapi/vapi_doc.md155
-rw-r--r--src/vpp-api/vapi/vapi_internal.h22
-rw-r--r--src/vpp-api/vapi/vapi_json_parser.py2
-rw-r--r--test/ext/Makefile26
-rw-r--r--test/ext/fake.api.json35
-rw-r--r--test/ext/vapi_c_test.c (renamed from test/ext/vapi_test.c)30
-rw-r--r--test/ext/vapi_cpp_test.cpp591
-rw-r--r--test/test_vapi.py36
20 files changed, 2338 insertions, 268 deletions
diff --git a/.clang-format b/.clang-format
new file mode 100644
index 00000000..977ed2db
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,38 @@
+---
+AlignEscapedNewlinesLeft: true
+AlignTrailingComments: true
+AllowAllParametersOfDeclarationOnNextLine: true
+AllowShortIfStatementsOnASingleLine: false
+AllowShortLoopsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: false
+AlwaysBreakBeforeMultilineStrings: false
+BreakBeforeBinaryOperators: false
+BreakBeforeTernaryOperators: true
+BinPackParameters: true
+BreakBeforeBraces: GNU
+ColumnLimit: 79
+IndentCaseLabels: false
+MaxEmptyLinesToKeep: 1
+PenaltyBreakBeforeFirstCallParameter: 19
+PenaltyBreakComment: 60
+PenaltyBreakString: 1000
+PenaltyBreakFirstLessLess: 120
+PenaltyExcessCharacter: 1000000
+PenaltyReturnTypeOnItsOwnLine: 60
+PointerBindsToType: false
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeParens: Always
+SpacesBeforeTrailingComments: 1
+SpacesInParentheses: false
+SpaceInEmptyParentheses: false
+SpacesInCStyleCastParentheses: false
+SpaceAfterControlStatementKeyword: true
+Cpp11BracedListStyle: true
+Standard: Cpp11
+SortIncludes: false
+IndentWidth: 2
+TabWidth: 4
+UseTab: Never
+IndentFunctionDeclarationAfterType: false
+ContinuationIndentWidth: 4
+...
diff --git a/build-root/scripts/checkstyle.sh b/build-root/scripts/checkstyle.sh
index 55fe4ab5..bd2ba813 100755
--- a/build-root/scripts/checkstyle.sh
+++ b/build-root/scripts/checkstyle.sh
@@ -32,37 +32,75 @@ fi
# don't *fail*.
command -v indent > /dev/null
if [ $? != 0 ]; then
- echo "Cound not find required commend \"indent\". Checkstyle aborted"
+ echo "Cound not find required command \"indent\". Checkstyle aborted"
exit ${EXIT_CODE}
fi
indent --version
+# Check to make sure we have clang-format. Exit if we don't with an error message, but
+# don't *fail*.
+command -v clang-format > /dev/null
+if [ $? != 0 ]; then
+ echo "Could not find command \"clang-format\". Checking C++ files will cause abort"
+ HAVE_CLANG_FORMAT=0
+else
+ HAVE_CLANG_FORMAT=1
+ clang-format --version
+fi
+
cd ${VPP_DIR}
git status
for i in ${FILELIST}; do
if [ -f ${i} ] && [ ${i} != "build-root/scripts/checkstyle.sh" ] && [ ${i} != "extras/emacs/fix-coding-style.el" ]; then
grep -q "fd.io coding-style-patch-verification: ON" ${i}
if [ $? == 0 ]; then
+ EXTENSION=`basename ${i} | sed 's/^\w\+.//'`
+ case ${EXTENSION} in
+ hpp|cpp|cc|hh)
+ CMD="clang-format"
+ if [ ${HAVE_CLANG_FORMAT} == 0 ]; then
+ echo "C++ file detected. Abort. (missing clang-format)"
+ exit ${EXIT_CODE}
+ fi
+ ;;
+ *)
+ CMD="indent"
+ ;;
+ esac
CHECKSTYLED_FILES="${CHECKSTYLED_FILES} ${i}"
if [ ${FIX} == 0 ]; then
- indent ${i} -o ${i}.out1 > /dev/null 2>&1
- indent ${i}.out1 -o ${i}.out2 > /dev/null 2>&1
- # Remove trailing whitespace
- sed -i -e 's/[[:space:]]*$//' ${i}.out2
+ if [ "${CMD}" == "clang-format" ]
+ then
+ clang-format ${i} > ${i}.out2
+ else
+ indent ${i} -o ${i}.out1 > /dev/null 2>&1
+ indent ${i}.out1 -o ${i}.out2 > /dev/null 2>&1
+ fi
+ # Remove trailing whitespace
+ sed -i -e 's/[[:space:]]*$//' ${i}.out2
diff -q ${i} ${i}.out2
else
- indent ${i}
- indent ${i}
- # Remove trailing whitespace
- sed -i -e 's/[[:space:]]*$//' ${i}
+ if [ "${CMD}" == "clang-format" ]; then
+ clang-format -i ${i} > /dev/null 2>&1
+ else
+ indent ${i}
+ indent ${i}
+ fi
+ # Remove trailing whitespace
+ sed -i -e 's/[[:space:]]*$//' ${i}
fi
if [ $? != 0 ]; then
EXIT_CODE=1
echo
echo "Checkstyle failed for ${i}."
- echo "Run indent (twice!) as shown to fix the problem:"
- echo "indent ${VPP_DIR}${i}"
- echo "indent ${VPP_DIR}${i}"
+ if [ "${CMD}" == "clang-format" ]; then
+ echo "Run clang-format as shown to fix the problem:"
+ echo "clang-format -i ${VPP_DIR}${i}"
+ else
+ echo "Run indent (twice!) as shown to fix the problem:"
+ echo "indent ${VPP_DIR}${i}"
+ echo "indent ${VPP_DIR}${i}"
+ fi
fi
if [ -f ${i}.out1 ]; then
rm ${i}.out1
diff --git a/src/configure.ac b/src/configure.ac
index 2efb23ad..f5ce3be2 100644
--- a/src/configure.ac
+++ b/src/configure.ac
@@ -7,6 +7,7 @@ AC_CONFIG_FILES([Makefile plugins/Makefile vpp-api/python/Makefile vpp-api/java/
AC_CONFIG_MACRO_DIR([m4])
AC_PROG_CC
+AC_PROG_CXX
AM_PROG_AS
AM_PROG_LIBTOOL
AC_PROG_YACC
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)
diff --git a/test/ext/Makefile b/test/ext/Makefile
index 4a45fef6..a188427a 100644
--- a/test/ext/Makefile
+++ b/test/ext/Makefile
@@ -1,17 +1,31 @@
BINDIR = $(BR)/vapi_test/
-BIN = $(addprefix $(BINDIR), vapi_test)
+CBIN = $(addprefix $(BINDIR), vapi_c_test)
+CPPBIN = $(addprefix $(BINDIR), vapi_cpp_test)
+
LIBS = -L$(VPP_TEST_BUILD_DIR)/vpp/.libs/ -L$(VPP_TEST_BUILD_DIR)/vpp/vpp-api/vapi/.libs/ -lvppinfra -lvlibmemoryclient -lsvm -lpthread -lcheck -lsubunit -lrt -lm -lvapiclient
-CFLAGS = -ggdb -O0 -Wall -pthread -I$(WS_ROOT)/src -I$(VPP_TEST_BUILD_DIR)/vpp/vpp-api/vapi -I$(WS_ROOT)/src/vpp-api/vapi/
+CFLAGS = -std=gnu99 -g -Wall -pthread -I$(WS_ROOT)/src -I$(VPP_TEST_INSTALL_PATH)/vpp/include -I$(BINDIR)
+CPPFLAGS = -std=c++11 -g -Wall -pthread -I$(WS_ROOT)/src -I$(VPP_TEST_INSTALL_PATH)/vpp/include -I$(BINDIR)
-all: $(BIN)
+all: $(CBIN) $(CPPBIN)
$(BINDIR):
mkdir -p $(BINDIR)
-SRC = vapi_test.c
+CSRC = vapi_c_test.c
+
+fake.api.vapi.h: fake.api.json $(BINDIR) $(WS_ROOT)/src/vpp-api/vapi/vapi_c_gen.py
+ $(WS_ROOT)/src/vpp-api/vapi/vapi_c_gen.py --prefix $(BINDIR) $<
+
+fake.api.vapi.hpp: fake.api.json $(BINDIR) $(WS_ROOT)/src/vpp-api/vapi/vapi_cpp_gen.py
+ $(WS_ROOT)/src/vpp-api/vapi/vapi_cpp_gen.py --prefix $(BINDIR) $<
+
+$(CBIN): $(CSRC) $(BINDIR) $(VPP_TEST_BUILD_DIR)/vpp/vpp-api/vapi/.libs/libvapiclient.so $(VPP_TEST_BUILD_DIR)/vpp/.libs/libvppinfra.so $(VPP_TEST_BUILD_DIR)/vpp/.libs/libvlibmemoryclient.so $(VPP_TEST_BUILD_DIR)/vpp/.libs/libsvm.so fake.api.vapi.h
+ $(CC) -o $@ $(CFLAGS) $(CSRC) $(LIBS)
+
+CPPSRC = vapi_cpp_test.cpp
-$(BIN): $(SRC) $(BINDIR) $(VPP_TEST_BUILD_DIR)/vpp/vpp-api/vapi/.libs/libvapiclient.so $(VPP_TEST_BUILD_DIR)/vpp/.libs/libvppinfra.so $(VPP_TEST_BUILD_DIR)/vpp/.libs/libvlibmemoryclient.so $(VPP_TEST_BUILD_DIR)/vpp/.libs/libsvm.so
- gcc -ggdb -o $@ $(SRC) $(CFLAGS) $(LIBS)
+$(CPPBIN): $(CPPSRC) $(BINDIR) $(VPP_TEST_BUILD_DIR)/vpp/vpp-api/vapi/.libs/libvapiclient.so $(VPP_TEST_BUILD_DIR)/vpp/.libs/libvppinfra.so $(VPP_TEST_BUILD_DIR)/vpp/.libs/libvlibmemoryclient.so $(VPP_TEST_BUILD_DIR)/vpp/.libs/libsvm.so fake.api.vapi.hpp
+ $(CXX) -o $@ $(CPPFLAGS) $(CPPSRC) $(LIBS)
clean:
rm -rf $(BINDIR)
diff --git a/test/ext/fake.api.json b/test/ext/fake.api.json
new file mode 100644
index 00000000..3e8d6a95
--- /dev/null
+++ b/test/ext/fake.api.json
@@ -0,0 +1,35 @@
+{
+ "types" : [
+
+ ],
+ "messages" : [
+ ["test_fake_msg",
+ ["u16", "_vl_msg_id"],
+ ["u32", "client_index"],
+ ["u32", "context"],
+ ["u8", "dummy", 256],
+ {"crc" : "0xcafebafe"}
+ ],
+ ["test_fake_msg_reply",
+ ["u16", "_vl_msg_id"],
+ ["u32", "context"],
+ ["i32", "retval"],
+ {"crc" : "0xcafebafe"}
+ ],
+ ["test_fake_dump",
+ ["u16", "_vl_msg_id"],
+ ["u32", "client_index"],
+ ["u32", "context"],
+ ["u32", "dummy"],
+ {"crc" : "0xcafebafe"}
+ ],
+ ["test_fake_details",
+ ["u16", "_vl_msg_id"],
+ ["u32", "client_index"],
+ ["u32", "context"],
+ ["u32", "dummy"],
+ {"crc" : "0xcafebafe"}
+ ]
+ ],
+"vl_api_version" :"0x224c7aad"
+}
diff --git a/test/ext/vapi_test.c b/test/ext/vapi_c_test.c
index eca6be7d..622b617b 100644
--- a/test/ext/vapi_test.c
+++ b/test/ext/vapi_c_test.c
@@ -22,16 +22,18 @@
#include <assert.h>
#include <setjmp.h>
#include <check.h>
-#include <vpp-api/vapi/vapi.h>
-#include <vpe.api.vapi.h>
-#include <interface.api.vapi.h>
-#include <l2.api.vapi.h>
-#include <stats.api.vapi.h>
+#include <vapi/vapi.h>
+#include <vapi/vpe.api.vapi.h>
+#include <vapi/interface.api.vapi.h>
+#include <vapi/l2.api.vapi.h>
+#include <vapi/stats.api.vapi.h>
+#include <fake.api.vapi.h>
DEFINE_VAPI_MSG_IDS_VPE_API_JSON;
DEFINE_VAPI_MSG_IDS_INTERFACE_API_JSON;
DEFINE_VAPI_MSG_IDS_L2_API_JSON;
DEFINE_VAPI_MSG_IDS_STATS_API_JSON;
+DEFINE_VAPI_MSG_IDS_FAKE_API_JSON;
static char *app_name = NULL;
static char *api_prefix = NULL;
@@ -521,9 +523,8 @@ START_TEST (test_show_version_1)
size_t size;
rv = vapi_recv (ctx, (void *) &resp, &size);
ck_assert_int_eq (VAPI_OK, rv);
- vapi_payload_show_version_reply *payload = &resp->payload;
int dummy;
- show_version_cb (NULL, &dummy, VAPI_OK, true, payload);
+ show_version_cb (NULL, &dummy, VAPI_OK, true, &resp->payload);
vapi_msg_free (ctx, resp);
}
@@ -1069,6 +1070,16 @@ START_TEST (test_no_response_2)
}
END_TEST;
+
+START_TEST (test_unsupported)
+{
+ printf ("--- Unsupported messages ---\n");
+ bool available = vapi_is_msg_available (ctx, vapi_msg_id_test_fake_msg);
+ ck_assert_int_eq (false, available);
+}
+
+END_TEST;
+
Suite *
test_suite (void)
{
@@ -1115,6 +1126,11 @@ test_suite (void)
tcase_add_test (tc_nonblock, test_no_response_2);
suite_add_tcase (s, tc_nonblock);
+ TCase *tc_unsupported = tcase_create ("Unsupported message");
+ tcase_add_checked_fixture (tc_unsupported, setup_blocking, teardown);
+ tcase_add_test (tc_unsupported, test_unsupported);
+ suite_add_tcase (s, tc_unsupported);
+
return s;
}
diff --git a/test/ext/vapi_cpp_test.cpp b/test/ext/vapi_cpp_test.cpp
new file mode 100644
index 00000000..14c35d5b
--- /dev/null
+++ b/test/ext/vapi_cpp_test.cpp
@@ -0,0 +1,591 @@
+/*
+ *------------------------------------------------------------------
+ * 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.
+ *------------------------------------------------------------------
+ */
+
+#include <memory>
+#include <stdio.h>
+#include <unistd.h>
+#include <assert.h>
+#include <setjmp.h>
+#include <check.h>
+#include <vapi/vapi.hpp>
+#include <vapi/vpe.api.vapi.hpp>
+#include <vapi/interface.api.vapi.hpp>
+#include <vapi/stats.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_STATS_API_JSON;
+DEFINE_VAPI_MSG_IDS_FAKE_API_JSON;
+
+static char *app_name = nullptr;
+static char *api_prefix = nullptr;
+static const int max_outstanding_requests = 32;
+static const int response_queue_size = 32;
+
+using namespace vapi;
+
+void verify_show_version_reply (const Show_version_reply &r)
+{
+ auto &p = r.get_payload ();
+ printf ("show_version_reply: program: `%s', version: `%s', build directory: "
+ "`%s', build date: `%s'\n",
+ p.program, p.version, p.build_directory, p.build_date);
+ ck_assert_str_eq ("vpe", (char *)p.program);
+}
+
+Connection con;
+
+void setup (void)
+{
+ vapi_error_e rv = con.connect (
+ app_name, api_prefix, max_outstanding_requests, response_queue_size);
+ ck_assert_int_eq (VAPI_OK, rv);
+}
+
+void teardown (void)
+{
+ con.disconnect ();
+}
+
+START_TEST (test_show_version_1)
+{
+ printf ("--- Show version by reading response associated to request ---\n");
+ Show_version sv (con);
+ vapi_error_e rv = sv.execute ();
+ ck_assert_int_eq (VAPI_OK, rv);
+ rv = con.wait_for_response (sv);
+ ck_assert_int_eq (VAPI_OK, rv);
+ auto &r = sv.get_response ();
+ verify_show_version_reply (r);
+}
+
+END_TEST;
+
+struct Show_version_cb
+{
+ Show_version_cb () : called{0} {};
+ int called;
+ vapi_error_e operator() (Show_version &sv)
+ {
+ auto &r = sv.get_response ();
+ verify_show_version_reply (r);
+ ++called;
+ return VAPI_OK;
+ }
+};
+
+START_TEST (test_show_version_2)
+{
+ printf ("--- Show version by getting a callback ---\n");
+ Show_version_cb cb;
+ Show_version sv (con, std::ref (cb));
+ vapi_error_e rv = sv.execute ();
+ ck_assert_int_eq (VAPI_OK, rv);
+ con.dispatch (sv);
+ ck_assert_int_eq (1, cb.called);
+}
+
+END_TEST;
+
+START_TEST (test_loopbacks_1)
+{
+ printf ("--- Create/delete loopbacks by waiting for response ---\n");
+ const auto num_ifs = 5;
+ u8 mac_addresses[num_ifs][6];
+ memset (&mac_addresses, 0, sizeof (mac_addresses));
+ u32 sw_if_indexes[num_ifs];
+ memset (&sw_if_indexes, 0xff, sizeof (sw_if_indexes));
+ for (int i = 0; i < num_ifs; ++i)
+ {
+ memcpy (&mac_addresses[i], "\1\2\3\4\5\6", 6);
+ mac_addresses[i][5] = i;
+ }
+ for (int i = 0; i < num_ifs; ++i)
+ {
+ Create_loopback cl (con);
+ auto &p = cl.get_request ().get_payload ();
+ memcpy (p.mac_address, mac_addresses[i], sizeof (p.mac_address));
+ auto e = cl.execute ();
+ ck_assert_int_eq (VAPI_OK, e);
+ vapi_error_e rv = con.wait_for_response (cl);
+ ck_assert_int_eq (VAPI_OK, rv);
+ auto &rp = cl.get_response ().get_payload ();
+ ck_assert_int_eq (0, rp.retval);
+ sw_if_indexes[i] = rp.sw_if_index;
+ }
+ for (int i = 0; i < num_ifs; ++i)
+ {
+ printf ("Created loopback with MAC %02x:%02x:%02x:%02x:%02x:%02x --> "
+ "sw_if_index %u\n",
+ mac_addresses[i][0], mac_addresses[i][1], mac_addresses[i][2],
+ mac_addresses[i][3], mac_addresses[i][4], mac_addresses[i][5],
+ sw_if_indexes[i]);
+ }
+
+ { // new context
+ bool seen[num_ifs] = {0};
+ Sw_interface_dump d (con);
+ auto &p = d.get_request ().get_payload ();
+ p.name_filter_valid = 0;
+ memset (p.name_filter, 0, sizeof (p.name_filter));
+ auto rv = d.execute ();
+ ck_assert_int_eq (VAPI_OK, rv);
+ rv = con.wait_for_response (d);
+ ck_assert_int_eq (VAPI_OK, rv);
+ auto &rs = d.get_result_set ();
+ for (auto &r : rs)
+ {
+ auto &p = r.get_payload ();
+ 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]);
+ }
+ }
+
+ for (int i = 0; i < num_ifs; ++i)
+ {
+ Delete_loopback dl (con);
+ dl.get_request ().get_payload ().sw_if_index = sw_if_indexes[i];
+ auto rv = dl.execute ();
+ ck_assert_int_eq (VAPI_OK, rv);
+ rv = con.wait_for_response (dl);
+ ck_assert_int_eq (VAPI_OK, rv);
+ auto &response = dl.get_response ();
+ auto rp = response.get_payload ();
+ ck_assert_int_eq (0, rp.retval);
+ printf ("Deleted loopback with sw_if_index %u\n", sw_if_indexes[i]);
+ }
+
+ { // new context
+ Sw_interface_dump d (con);
+ auto &p = d.get_request ().get_payload ();
+ p.name_filter_valid = 0;
+ memset (p.name_filter, 0, sizeof (p.name_filter));
+ auto rv = d.execute ();
+ ck_assert_int_eq (VAPI_OK, rv);
+ rv = con.wait_for_response (d);
+ ck_assert_int_eq (VAPI_OK, rv);
+ auto &rs = d.get_result_set ();
+ for (auto &r : rs)
+ {
+ auto &p = r.get_payload ();
+ for (int i = 0; i < num_ifs; ++i)
+ {
+ ck_assert_int_ne (sw_if_indexes[i], p.sw_if_index);
+ }
+ }
+ }
+}
+
+END_TEST;
+
+struct Create_loopback_cb
+{
+ Create_loopback_cb () : called{0}, sw_if_index{0} {};
+ int called;
+ u32 sw_if_index;
+ bool seen;
+ vapi_error_e operator() (Create_loopback &cl)
+ {
+ auto &r = cl.get_response ();
+ sw_if_index = r.get_payload ().sw_if_index;
+ ++called;
+ return VAPI_OK;
+ }
+};
+
+struct Delete_loopback_cb
+{
+ Delete_loopback_cb () : called{0}, sw_if_index{0} {};
+ int called;
+ u32 sw_if_index;
+ bool seen;
+ vapi_error_e operator() (Delete_loopback &dl)
+ {
+ auto &r = dl.get_response ();
+ ck_assert_int_eq (0, r.get_payload ().retval);
+ ++called;
+ return VAPI_OK;
+ }
+};
+
+template <int num_ifs> struct Sw_interface_dump_cb
+{
+ Sw_interface_dump_cb (std::array<Create_loopback_cb, num_ifs> &cbs)
+ : called{0}, cbs{cbs} {};
+ int called;
+ std::array<Create_loopback_cb, num_ifs> &cbs;
+ vapi_error_e operator() (Sw_interface_dump &d)
+ {
+ for (auto &y : cbs)
+ {
+ y.seen = false;
+ }
+ for (auto &x : d.get_result_set ())
+ {
+ auto &p = x.get_payload ();
+ for (auto &y : cbs)
+ {
+ if (p.sw_if_index == y.sw_if_index)
+ {
+ y.seen = true;
+ }
+ }
+ }
+ for (auto &y : cbs)
+ {
+ ck_assert_int_eq (true, y.seen);
+ }
+ ++called;
+ return VAPI_OK;
+ }
+};
+
+START_TEST (test_loopbacks_2)
+{
+ printf ("--- Create/delete loopbacks by getting a callback ---\n");
+ const auto num_ifs = 5;
+ u8 mac_addresses[num_ifs][6];
+ memset (&mac_addresses, 0, sizeof (mac_addresses));
+ for (int i = 0; i < num_ifs; ++i)
+ {
+ memcpy (&mac_addresses[i], "\1\2\3\4\5\6", 6);
+ mac_addresses[i][5] = i;
+ }
+ std::array<Create_loopback_cb, num_ifs> ccbs;
+ std::array<std::unique_ptr<Create_loopback>, num_ifs> clcs;
+ for (int i = 0; i < num_ifs; ++i)
+ {
+ Create_loopback *cl = new Create_loopback (con, std::ref (ccbs[i]));
+ clcs[i].reset (cl);
+ auto &p = cl->get_request ().get_payload ();
+ memcpy (p.mac_address, mac_addresses[i], sizeof (p.mac_address));
+ auto e = cl->execute ();
+ ck_assert_int_eq (VAPI_OK, e);
+ }
+ con.dispatch ();
+ for (int i = 0; i < num_ifs; ++i)
+ {
+ ck_assert_int_eq (1, ccbs[i].called);
+ printf ("Created loopback with MAC %02x:%02x:%02x:%02x:%02x:%02x --> "
+ "sw_if_index %u\n",
+ mac_addresses[i][0], mac_addresses[i][1], mac_addresses[i][2],
+ mac_addresses[i][3], mac_addresses[i][4], mac_addresses[i][5],
+ ccbs[i].sw_if_index);
+ }
+
+ Sw_interface_dump_cb<num_ifs> swdcb (ccbs);
+ Sw_interface_dump d (con, std::ref (swdcb));
+ auto &p = d.get_request ().get_payload ();
+ p.name_filter_valid = 0;
+ memset (p.name_filter, 0, sizeof (p.name_filter));
+ auto rv = d.execute ();
+ ck_assert_int_eq (VAPI_OK, rv);
+ rv = con.wait_for_response (d);
+ ck_assert_int_eq (VAPI_OK, rv);
+ ck_assert_int_ne (0, swdcb.called);
+ std::array<Delete_loopback_cb, num_ifs> dcbs;
+ std::array<std::unique_ptr<Delete_loopback>, num_ifs> dlcs;
+ for (int i = 0; i < num_ifs; ++i)
+ {
+ Delete_loopback *dl = new Delete_loopback (con, std::ref (dcbs[i]));
+ dlcs[i].reset (dl);
+ auto &p = dl->get_request ().get_payload ();
+ p.sw_if_index = ccbs[i].sw_if_index;
+ dcbs[i].sw_if_index = ccbs[i].sw_if_index;
+ auto e = dl->execute ();
+ ck_assert_int_eq (VAPI_OK, e);
+ }
+ con.dispatch ();
+ for (auto &x : dcbs)
+ {
+ ck_assert_int_eq (true, x.called);
+ printf ("Deleted loopback with sw_if_index %u\n", x.sw_if_index);
+ }
+
+ { // new context
+ Sw_interface_dump d (con);
+ auto &p = d.get_request ().get_payload ();
+ p.name_filter_valid = 0;
+ memset (p.name_filter, 0, sizeof (p.name_filter));
+ auto rv = d.execute ();
+ ck_assert_int_eq (VAPI_OK, rv);
+ rv = con.wait_for_response (d);
+ ck_assert_int_eq (VAPI_OK, rv);
+ auto &rs = d.get_result_set ();
+ for (auto &r : rs)
+ {
+ auto &p = r.get_payload ();
+ for (int i = 0; i < num_ifs; ++i)
+ {
+ ck_assert_int_ne (ccbs[i].sw_if_index, p.sw_if_index);
+ }
+ }
+ }
+}
+
+END_TEST;
+
+START_TEST (test_stats_1)
+{
+ printf ("--- Receive single stats by waiting for response ---\n");
+ Want_stats ws (con);
+ auto &payload = ws.get_request ().get_payload ();
+ payload.enable_disable = 1;
+ payload.pid = getpid ();
+ auto rv = ws.execute ();
+ ck_assert_int_eq (VAPI_OK, rv);
+ Event_registration<Vnet_interface_simple_counters> sc (con);
+ rv = con.wait_for_response (sc);
+ ck_assert_int_eq (VAPI_OK, rv);
+ auto &rs = sc.get_result_set ();
+ int count = 0;
+ for (auto &r : rs)
+ {
+ printf ("simple counters: first_sw_if_index=%u\n",
+ r.get_payload ().first_sw_if_index);
+ ++count;
+ }
+ ck_assert_int_ne (0, count);
+}
+
+END_TEST;
+
+struct Vnet_interface_simple_counters_cb
+{
+ Vnet_interface_simple_counters_cb () : called{0} {};
+ int called;
+ vapi_error_e
+ operator() (Event_registration<Vnet_interface_simple_counters> &e)
+ {
+ ++called;
+ auto &rs = e.get_result_set ();
+ int count = 0;
+ for (auto &r : rs)
+ {
+ printf ("simple counters: first_sw_if_index=%u\n",
+ r.get_payload ().first_sw_if_index);
+ ++count;
+ }
+ ck_assert_int_ne (0, count);
+ return VAPI_OK;
+ }
+};
+
+START_TEST (test_stats_2)
+{
+ printf ("--- Receive single stats by getting a callback ---\n");
+ Want_stats ws (con);
+ auto &payload = ws.get_request ().get_payload ();
+ payload.enable_disable = 1;
+ payload.pid = getpid ();
+ auto rv = ws.execute ();
+ ck_assert_int_eq (VAPI_OK, rv);
+ Vnet_interface_simple_counters_cb cb;
+ Event_registration<Vnet_interface_simple_counters> sc (con, std::ref (cb));
+ rv = con.wait_for_response (sc);
+ ck_assert_int_eq (VAPI_OK, rv);
+ ck_assert_int_ne (0, cb.called);
+}
+
+END_TEST;
+
+struct Vnet_interface_simple_counters_2_cb
+{
+ Vnet_interface_simple_counters_2_cb () : called{0}, total{0} {};
+ int called;
+ int total;
+ vapi_error_e
+ operator() (Event_registration<Vnet_interface_simple_counters> &e)
+ {
+ ++called;
+ auto &rs = e.get_result_set ();
+ int count = 0;
+ for (auto &r : rs)
+ {
+ printf ("simple counters: first_sw_if_index=%u\n",
+ r.get_payload ().first_sw_if_index);
+ ++count;
+ }
+ rs.free_all_responses ();
+ ck_assert_int_ne (0, count);
+ total += count;
+ return VAPI_OK;
+ }
+};
+
+START_TEST (test_stats_3)
+{
+ printf (
+ "--- Receive single stats by getting a callback - clear results ---\n");
+ Want_stats ws (con);
+ auto &payload = ws.get_request ().get_payload ();
+ payload.enable_disable = 1;
+ payload.pid = getpid ();
+ auto rv = ws.execute ();
+ ck_assert_int_eq (VAPI_OK, rv);
+ Vnet_interface_simple_counters_2_cb cb;
+ Event_registration<Vnet_interface_simple_counters> sc (con, std::ref (cb));
+ for (int i = 0; i < 5; ++i)
+ {
+ rv = con.wait_for_response (sc);
+ }
+ ck_assert_int_eq (VAPI_OK, rv);
+ ck_assert_int_eq (5, cb.called);
+ ck_assert_int_eq (5, cb.total);
+}
+
+END_TEST;
+
+START_TEST (test_stats_4)
+{
+ printf ("--- Receive multiple stats by waiting for response ---\n");
+ Want_stats ws (con);
+ auto &payload = ws.get_request ().get_payload ();
+ payload.enable_disable = 1;
+ payload.pid = getpid ();
+ auto rv = ws.execute ();
+ ck_assert_int_eq (VAPI_OK, rv);
+ Event_registration<Vnet_interface_simple_counters> sc (con);
+ Event_registration<Vnet_interface_combined_counters> cc (con);
+ rv = con.wait_for_response (sc);
+ ck_assert_int_eq (VAPI_OK, rv);
+ rv = con.wait_for_response (cc);
+ ck_assert_int_eq (VAPI_OK, rv);
+ int count = 0;
+ for (auto &r : sc.get_result_set ())
+ {
+ printf ("simple counters: first_sw_if_index=%u\n",
+ r.get_payload ().first_sw_if_index);
+ ++count;
+ }
+ ck_assert_int_ne (0, count);
+ count = 0;
+ for (auto &r : cc.get_result_set ())
+ {
+ printf ("combined counters: first_sw_if_index=%u\n",
+ r.get_payload ().first_sw_if_index);
+ ++count;
+ }
+ ck_assert_int_ne (0, count);
+}
+
+END_TEST;
+
+START_TEST (test_unsupported)
+{
+ printf ("--- Unsupported messages ---\n");
+ bool thrown = false;
+ try
+ {
+ Test_fake_msg fake (con);
+ }
+ catch (const Msg_not_available_exception &)
+ {
+ thrown = true;
+ printf ("Constructing unsupported msg not possible - test pass.\n");
+ }
+ ck_assert_int_eq (true, thrown);
+ thrown = false;
+ try
+ {
+ Test_fake_dump fake (con);
+ }
+ catch (const Msg_not_available_exception &)
+ {
+ thrown = true;
+ printf ("Constructing unsupported dump not possible - test pass.\n");
+ }
+ ck_assert_int_eq (true, thrown);
+ thrown = false;
+ try
+ {
+ Event_registration<Test_fake_details> fake (con);
+ }
+ catch (const Msg_not_available_exception &)
+ {
+ thrown = true;
+ printf ("Constructing unsupported event registration not possible - "
+ "test pass.\n");
+ }
+ ck_assert_int_eq (true, thrown);
+}
+
+END_TEST;
+
+Suite *test_suite (void)
+{
+ Suite *s = suite_create ("VAPI test");
+
+ TCase *tc_cpp_api = tcase_create ("C++ API");
+ tcase_set_timeout (tc_cpp_api, 25);
+ tcase_add_checked_fixture (tc_cpp_api, setup, teardown);
+ tcase_add_test (tc_cpp_api, test_show_version_1);
+ tcase_add_test (tc_cpp_api, test_show_version_2);
+ tcase_add_test (tc_cpp_api, test_loopbacks_1);
+ tcase_add_test (tc_cpp_api, test_loopbacks_2);
+ tcase_add_test (tc_cpp_api, test_stats_1);
+ tcase_add_test (tc_cpp_api, test_stats_2);
+ tcase_add_test (tc_cpp_api, test_stats_3);
+ tcase_add_test (tc_cpp_api, test_stats_4);
+ tcase_add_test (tc_cpp_api, test_unsupported);
+ suite_add_tcase (s, tc_cpp_api);
+
+ return s;
+}
+
+int main (int argc, char *argv[])
+{
+ if (3 != 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);
+
+ int number_failed;
+ Suite *s;
+ SRunner *sr;
+
+ s = test_suite ();
+ sr = srunner_create (s);
+
+ srunner_run_all (sr, CK_NORMAL);
+ number_failed = srunner_ntests_failed (sr);
+ srunner_free (sr);
+ return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/test/test_vapi.py b/test/test_vapi.py
index 86c1ee06..d8e1ebe0 100644
--- a/test/test_vapi.py
+++ b/test/test_vapi.py
@@ -45,17 +45,45 @@ class Worker(Thread):
class VAPITestCase(VppTestCase):
""" VAPI test """
- def test_vapi(self):
- """ run VAPI tests """
+ def test_vapi_c(self):
+ """ run C VAPI tests """
var = "BR"
built_root = os.getenv(var, None)
self.assertIsNotNone(built_root,
"Environment variable `%s' not set" % var)
- executable = "%s/vapi_test/vapi_test" % built_root
+ executable = "%s/vapi_test/vapi_c_test" % built_root
worker = Worker(
[executable, "vapi client", self.shm_prefix], self.logger)
worker.start()
- timeout = 45
+ timeout = 60
+ worker.join(timeout)
+ self.logger.info("Worker result is `%s'" % worker.result)
+ error = False
+ if worker.result is None:
+ try:
+ error = True
+ self.logger.error(
+ "Timeout! Worker did not finish in %ss" % timeout)
+ os.killpg(os.getpgid(worker.process.pid), signal.SIGTERM)
+ worker.join()
+ except:
+ raise Exception("Couldn't kill worker-spawned process")
+ if error:
+ raise Exception(
+ "Timeout! Worker did not finish in %ss" % timeout)
+ self.assert_equal(worker.result, 0, "Binary test return code")
+
+ def test_vapi_cpp(self):
+ """ run C++ VAPI tests """
+ var = "BR"
+ built_root = os.getenv(var, None)
+ self.assertIsNotNone(built_root,
+ "Environment variable `%s' not set" % var)
+ executable = "%s/vapi_test/vapi_cpp_test" % built_root
+ worker = Worker(
+ [executable, "vapi client", self.shm_prefix], self.logger)
+ worker.start()
+ timeout = 120
worker.join(timeout)
self.logger.info("Worker result is `%s'" % worker.result)
error = False