Add C++ API 84/7884/10
authorKlement Sekera <ksekera@cisco.com>
Mon, 12 Jun 2017 04:49:33 +0000 (06:49 +0200)
committerNeale Ranns <nranns@cisco.com>
Tue, 19 Sep 2017 20:06:08 +0000 (20:06 +0000)
Change-Id: Iff634f22d43470e2dc028387b3816257fd7b4156
Signed-off-by: Klement Sekera <ksekera@cisco.com>
20 files changed:
.clang-format [new file with mode: 0644]
build-root/scripts/checkstyle.sh
src/configure.ac
src/vpp-api/vapi/Makefile.am
src/vpp-api/vapi/libvapiclient.map
src/vpp-api/vapi/vapi.c
src/vpp-api/vapi/vapi.h
src/vpp-api/vapi/vapi.hpp [new file with mode: 0644]
src/vpp-api/vapi/vapi_c_gen.py
src/vpp-api/vapi/vapi_common.h [new file with mode: 0644]
src/vpp-api/vapi/vapi_cpp_gen.py [new file with mode: 0755]
src/vpp-api/vapi/vapi_dbg.h
src/vpp-api/vapi/vapi_doc.md [new file with mode: 0644]
src/vpp-api/vapi/vapi_internal.h
src/vpp-api/vapi/vapi_json_parser.py
test/ext/Makefile
test/ext/fake.api.json [new file with mode: 0644]
test/ext/vapi_c_test.c [moved from test/ext/vapi_test.c with 97% similarity]
test/ext/vapi_cpp_test.cpp [new file with mode: 0644]
test/test_vapi.py

diff --git a/.clang-format b/.clang-format
new file mode 100644 (file)
index 0000000..977ed2d
--- /dev/null
@@ -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
+...
index 55fe4ab..bd2ba81 100755 (executable)
@@ -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
index 2efb23a..f5ce3be 100644 (file)
@@ -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
index ce681c3..74b2b47 100644 (file)
@@ -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
index 5373300..6b58d1e 100644 (file)
@@ -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: *;
 };
index b9c81a1..59415e0 100644 (file)
@@ -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
  *
index 1e1d567..245bf65 100644 (file)
 #include <string.h>
 #include <stdbool.h>
 #include <vppinfra/types.h>
+#include <vapi/vapi_common.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
 
 /**
  * @file vapi.h
  * 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 (file)
index 0000000..3be78b4
--- /dev/null
@@ -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:
+ */
index 2bc1eef..ef6e266 100755 (executable)
@@ -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 (file)
index 0000000..ce64469
--- /dev/null
@@ -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 (executable)
index 0000000..6e9f5d3
--- /dev/null
@@ -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)
index 95a8008..ec3a300 100644 (file)
@@ -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 (file)
index 0000000..0e7e29d
--- /dev/null
@@ -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).
index 5b85788..2c51c67 100644 (file)
@@ -18,6 +18,7 @@
 #ifndef VAPI_INTERNAL_H
 #define VAPI_INTERNAL_H
 
+#include <endian.h>
 #include <string.h>
 #include <vppinfra/types.h>
 
  * 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
index 57a2238..1e17c7a 100644 (file)
@@ -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)
index 4a45fef..a188427 100644 (file)
@@ -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 (file)
index 0000000..3e8d6a9
--- /dev/null
@@ -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"
+}
similarity index 97%
rename from test/ext/vapi_test.c
rename to test/ext/vapi_c_test.c
index eca6be7..622b617 100644 (file)
 #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 (file)
index 0000000..14c35d5
--- /dev/null
@@ -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:
+ */
index 86c1ee0..d8e1ebe 100644 (file)
@@ -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 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