Add new C API 83/6983/58
authorKlement Sekera <ksekera@cisco.com>
Thu, 4 May 2017 04:15:18 +0000 (06:15 +0200)
committerNeale Ranns <nranns@cisco.com>
Tue, 19 Sep 2017 12:23:44 +0000 (12:23 +0000)
Change-Id: I717ce3cd7c867c155de149ec56623269d26d0ff7
Signed-off-by: Klement Sekera <ksekera@cisco.com>
19 files changed:
.gitignore
Makefile
src/Makefile.am
src/configure.ac
src/vlibmemory/unix_shared_memory_queue.c
src/vlibmemory/unix_shared_memory_queue.h
src/vpp-api/vapi/Makefile.am [new file with mode: 0644]
src/vpp-api/vapi/libvapiclient.map [new file with mode: 0644]
src/vpp-api/vapi/vapi.c [new file with mode: 0644]
src/vpp-api/vapi/vapi.h [new file with mode: 0644]
src/vpp-api/vapi/vapi_c_gen.py [new file with mode: 0755]
src/vpp-api/vapi/vapi_dbg.h [new file with mode: 0644]
src/vpp-api/vapi/vapi_internal.h [new file with mode: 0644]
src/vpp-api/vapi/vapi_json_parser.py [new file with mode: 0644]
test/Makefile
test/ext/Makefile [new file with mode: 0644]
test/ext/vapi_test.c [new file with mode: 0644]
test/scripts/test-loop.sh
test/test_vapi.py [new file with mode: 0644]

index ba4e104..5a6266d 100644 (file)
@@ -17,6 +17,7 @@
 /build-root/test-doc/
 /build-root/test-cov/
 /build-root/python/
+/build-root/vapi_test/
 /build-config.mk
 /dpdk/*.tar.gz
 /dpdk/*.tar.xz
index c08115d..c46fa6b 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -62,7 +62,7 @@ DEB_DEPENDS  = curl build-essential autoconf automake bison libssl-dev ccache
 DEB_DEPENDS += debhelper dkms git libtool libapr1-dev dh-systemd
 DEB_DEPENDS += libconfuse-dev git-review exuberant-ctags cscope pkg-config
 DEB_DEPENDS += lcov chrpath autoconf nasm indent libnuma-dev
-DEB_DEPENDS += python-all python-dev python-virtualenv python-pip libffi6
+DEB_DEPENDS += python-all python-dev python-virtualenv python-pip libffi6 check
 ifeq ($(OS_VERSION_ID),14.04)
        DEB_DEPENDS += openjdk-8-jdk-headless
 else ifeq ($(OS_ID)-$(OS_VERSION_ID),debian-8)
@@ -76,6 +76,7 @@ RPM_DEPENDS  = redhat-lsb glibc-static java-1.8.0-openjdk-devel yum-utils
 RPM_DEPENDS += apr-devel
 RPM_DEPENDS += openssl-devel
 RPM_DEPENDS += numactl-devel
+RPM_DEPENDS += check
 ifeq ($(OS_ID)-$(OS_VERSION_ID),fedora-25)
        RPM_DEPENDS += python-devel
        RPM_DEPENDS += python2-virtualenv
@@ -101,7 +102,13 @@ endif
 
 RPM_SUSE_DEPENDS = autoconf automake bison ccache chrpath distribution-release gcc6 glibc-devel-static
 RPM_SUSE_DEPENDS += java-1_8_0-openjdk-devel libopenssl-devel libtool lsb-release make openssl-devel
-RPM_SUSE_DEPENDS += python-devel python-pip python-rpm-macros shadow nasm libnuma-devel
+RPM_SUSE_DEPENDS += python-devel python-pip python-rpm-macros shadow nasm libnuma-devel python3
+
+ifeq ($(filter rhel centos,$(OS_ID)),$(OS_ID))
+       RPM_DEPENDS += python34
+else
+       RPM_DEPENDS += python3
+endif
 
 ifneq ($(wildcard $(STARTUP_DIR)/startup.conf),)
         STARTUP_CONF ?= $(STARTUP_DIR)/startup.conf
index 41076e0..7b35e50 100644 (file)
@@ -80,6 +80,8 @@ if ENABLE_JAPI
 SUBDIRS += vpp-api/java
 endif
 
+SUBDIRS += vpp-api/vapi
+
 ###############################################################################
 # API
 ###############################################################################
index 6b6d963..2efb23a 100644 (file)
@@ -3,7 +3,7 @@ LT_INIT
 AC_CONFIG_AUX_DIR([.])
 AM_INIT_AUTOMAKE([subdir-objects])
 AM_SILENT_RULES([yes])
-AC_CONFIG_FILES([Makefile plugins/Makefile vpp-api/python/Makefile vpp-api/java/Makefile])
+AC_CONFIG_FILES([Makefile plugins/Makefile vpp-api/python/Makefile vpp-api/java/Makefile vpp-api/vapi/Makefile])
 AC_CONFIG_MACRO_DIR([m4])
 
 AC_PROG_CC
index e86edec..4db4851 100644 (file)
@@ -234,6 +234,71 @@ unix_shared_memory_queue_add (unix_shared_memory_queue_t * q,
   return 0;
 }
 
+/*
+ * unix_shared_memory_queue_add2
+ */
+int
+unix_shared_memory_queue_add2 (unix_shared_memory_queue_t * q, u8 * elem,
+                              u8 * elem2, int nowait)
+{
+  i8 *tailp;
+  int need_broadcast = 0;
+
+  if (nowait)
+    {
+      /* zero on success */
+      if (pthread_mutex_trylock (&q->mutex))
+       {
+         return (-1);
+       }
+    }
+  else
+    pthread_mutex_lock (&q->mutex);
+
+  if (PREDICT_FALSE (q->cursize + 1 == q->maxsize))
+    {
+      if (nowait)
+       {
+         pthread_mutex_unlock (&q->mutex);
+         return (-2);
+       }
+      while (q->cursize + 1 == q->maxsize)
+       {
+         (void) pthread_cond_wait (&q->condvar, &q->mutex);
+       }
+    }
+
+  tailp = (i8 *) (&q->data[0] + q->elsize * q->tail);
+  clib_memcpy (tailp, elem, q->elsize);
+
+  q->tail++;
+  q->cursize++;
+
+  if (q->tail == q->maxsize)
+    q->tail = 0;
+
+  need_broadcast = (q->cursize == 1);
+
+  tailp = (i8 *) (&q->data[0] + q->elsize * q->tail);
+  clib_memcpy (tailp, elem2, q->elsize);
+
+  q->tail++;
+  q->cursize++;
+
+  if (q->tail == q->maxsize)
+    q->tail = 0;
+
+  if (need_broadcast)
+    {
+      (void) pthread_cond_broadcast (&q->condvar);
+      if (q->signal_when_queue_non_empty)
+       kill (q->consumer_pid, q->signal_when_queue_non_empty);
+    }
+  pthread_mutex_unlock (&q->mutex);
+
+  return 0;
+}
+
 /*
  * unix_shared_memory_queue_sub
  */
index 1380006..27de321 100644 (file)
@@ -21,7 +21,6 @@
 #define included_unix_shared_memory_queue_h
 
 #include <pthread.h>
-#include <vppinfra/mem.h>
 
 typedef struct _unix_shared_memory_queue
 {
@@ -43,10 +42,12 @@ unix_shared_memory_queue_t *unix_shared_memory_queue_init (int nels,
                                                           int
                                                           signal_when_queue_non_empty);
 void unix_shared_memory_queue_free (unix_shared_memory_queue_t * q);
-int unix_shared_memory_queue_add (unix_shared_memory_queue_t * q,
-                                 u8 * elem, int nowait);
-int unix_shared_memory_queue_sub (unix_shared_memory_queue_t * q,
-                                 u8 * elem, int nowait);
+int unix_shared_memory_queue_add (unix_shared_memory_queue_t * q, u8 * elem,
+                                 int nowait);
+int unix_shared_memory_queue_add2 (unix_shared_memory_queue_t * q, u8 * elem,
+                                  u8 * elem2, int nowait);
+int unix_shared_memory_queue_sub (unix_shared_memory_queue_t * q, u8 * elem,
+                                 int nowait);
 void unix_shared_memory_queue_lock (unix_shared_memory_queue_t * q);
 void unix_shared_memory_queue_unlock (unix_shared_memory_queue_t * q);
 int unix_shared_memory_queue_is_full (unix_shared_memory_queue_t * q);
diff --git a/src/vpp-api/vapi/Makefile.am b/src/vpp-api/vapi/Makefile.am
new file mode 100644 (file)
index 0000000..ce681c3
--- /dev/null
@@ -0,0 +1,63 @@
+# 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.
+
+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_LDFLAGS = -shared -avoid-version -rpath /none -no-undefined
+
+bin_PROGRAMS =
+noinst_LTLIBRARIES =
+CLEANDIRS =
+
+%.api.vapi.h: %.api.json vapi_c_gen.py
+       @echo "  VAPI C GEN $< " $@ ;                   \
+       mkdir -p `dirname $@` ;                         \
+        $(top_srcdir)/vpp-api/vapi/vapi_c_gen.py $<
+
+%.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))
+
+vapi.c: $(BUILT_SOURCES)
+
+JSON_FILES = $(wildcard *.api.json)
+
+
+lib_LTLIBRARIES = libvapiclient.la
+
+libvapiclient_la_SOURCES = vapi.c
+
+libvapiclient_la_LIBADD = -lpthread -lm -lrt \
+  $(top_builddir)/libvppinfra.la \
+  $(top_builddir)/libvlibmemoryclient.la \
+  $(top_builddir)/libsvm.la
+
+libvapiclient_la_LDFLAGS = \
+  -Wl,-L$(top_builddir)/.libs,--whole-archive,--no-whole-archive \
+  -Wl,--version-script=$(srcdir)/libvapiclient.map,-lrt
+
+libvapiclient_la_CPPFLAGS = -I. -I$(top_builddir)/vpp-api/vapi
+
+nobase_include_HEADERS = ${top_srcdir}/vpp-api/client/vppapiclient.h \
+  vapi.h \
+  vapi_dbg.h \
+  vapi_internal.h \
+  $(patsubst %.api.json,%.api.vapi.h,$(JSON_FILES))
+
+# vi:syntax=automake
diff --git a/src/vpp-api/vapi/libvapiclient.map b/src/vpp-api/vapi/libvapiclient.map
new file mode 100644 (file)
index 0000000..5373300
--- /dev/null
@@ -0,0 +1,41 @@
+
+VAPICLIENT_17.07 {
+       global:
+       vapi_msg_alloc;
+       vapi_msg_free;
+       vapi_ctx_alloc;
+       vapi_ctx_free;
+       vapi_is_msg_available;
+       vapi_connect;
+       vapi_disconnect;
+       vapi_get_fd;
+       vapi_send;
+       vapi_send2;
+       vapi_recv;
+       vapi_wait;
+       vapi_dispatch_one;
+       vapi_dispatch;
+       vapi_set_event_cb;
+       vapi_clear_event_cb;
+       vapi_set_generic_event_cb;
+       vapi_clear_generic_event_cb;
+       vapi_get_client_index;
+       vapi_register_msg;
+       vapi_get_client_index;
+       vapi_is_nonblocking;
+       vapi_requests_full;
+       vapi_gen_req_context;
+       vapi_producer_lock;
+       vapi_send_with_control_ping;
+       vapi_store_request;
+       vapi_is_nonblocking;
+       vapi_producer_unlock;
+       vapi_lookup_vl_msg_id;
+       vapi_lookup_vapi_msg_id_t;
+       vapi_msg_is_with_context;
+       vapi_get_context_offset;
+       vapi_msg_id_control_ping;
+       vapi_msg_id_control_ping_reply;
+
+       local: *;
+};
diff --git a/src/vpp-api/vapi/vapi.c b/src/vpp-api/vapi/vapi.c
new file mode 100644 (file)
index 0000000..b9c81a1
--- /dev/null
@@ -0,0 +1,895 @@
+/*
+ *------------------------------------------------------------------
+ * 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 <stdlib.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <arpa/inet.h>
+#include <stddef.h>
+#include <assert.h>
+
+#include <vpp-api/vapi/vapi_dbg.h>
+#include <vpp-api/vapi/vapi.h>
+#include <vpp-api/vapi/vapi_internal.h>
+#include <vppinfra/types.h>
+#include <vlibapi/api_common.h>
+#include <vlibmemory/api_common.h>
+
+/* we need to use control pings for some stuff and because we're forced to put
+ * the code in headers, we need a way to be able to grab the ids of these
+ * messages - so declare them here as extern */
+vapi_msg_id_t vapi_msg_id_control_ping = 0;
+vapi_msg_id_t vapi_msg_id_control_ping_reply = 0;
+
+struct
+{
+  size_t count;
+  vapi_message_desc_t **msgs;
+  size_t max_len_name_with_crc;
+} __vapi_metadata;
+
+typedef struct
+{
+  u32 context;
+  vapi_cb_t callback;
+  void *callback_ctx;
+  bool is_dump;
+} vapi_req_t;
+
+static const u32 context_counter_mask = (1 << 31);
+
+typedef struct
+{
+  vapi_error_e (*cb) (vapi_ctx_t ctx, void *callback_ctx, vapi_msg_id_t id,
+                     void *payload);
+  void *ctx;
+} vapi_generic_cb_with_ctx;
+
+typedef struct
+{
+  vapi_error_e (*cb) (vapi_ctx_t ctx, void *callback_ctx, void *payload);
+  void *ctx;
+} vapi_event_cb_with_ctx;
+
+struct vapi_ctx_s
+{
+  vapi_mode_e mode;
+  int requests_size;           /* size of the requests array (circular queue) */
+  int requests_start;          /* index of first request */
+  int requests_count;          /* number of used slots */
+  vapi_req_t *requests;
+  u32 context_counter;
+  vapi_generic_cb_with_ctx generic_cb;
+  vapi_event_cb_with_ctx *event_cbs;
+  u16 *vapi_msg_id_t_to_vl_msg_id;
+  u16 vl_msg_id_max;
+  vapi_msg_id_t *vl_msg_id_to_vapi_msg_t;
+  bool connected;
+  pthread_mutex_t requests_mutex;
+};
+
+u32
+vapi_gen_req_context (vapi_ctx_t ctx)
+{
+  ++ctx->context_counter;
+  ctx->context_counter %= context_counter_mask;
+  return ctx->context_counter | context_counter_mask;
+}
+
+size_t
+vapi_get_request_count (vapi_ctx_t ctx)
+{
+  return ctx->requests_count;
+}
+
+bool
+vapi_requests_full (vapi_ctx_t ctx)
+{
+  return (ctx->requests_count == ctx->requests_size);
+}
+
+static bool
+vapi_requests_empty (vapi_ctx_t ctx)
+{
+  return (0 == ctx->requests_count);
+}
+
+static int
+vapi_requests_end (vapi_ctx_t ctx)
+{
+  return (ctx->requests_start + ctx->requests_count) % ctx->requests_size;
+}
+
+void
+vapi_store_request (vapi_ctx_t ctx, u32 context, bool is_dump,
+                   vapi_cb_t callback, void *callback_ctx)
+{
+  assert (!vapi_requests_full (ctx));
+  /* if the mutex is not held, bad things will happen */
+  assert (0 != pthread_mutex_trylock (&ctx->requests_mutex));
+  const int requests_end = vapi_requests_end (ctx);
+  vapi_req_t *slot = &ctx->requests[requests_end];
+  slot->is_dump = is_dump;
+  slot->context = context;
+  slot->callback = callback;
+  slot->callback_ctx = callback_ctx;
+  VAPI_DBG ("stored@%d: context:%x (start is @%d)", requests_end, context,
+           ctx->requests_start);
+  ++ctx->requests_count;
+  assert (!vapi_requests_empty (ctx));
+}
+
+#if VAPI_DEBUG_ALLOC
+struct to_be_freed_s;
+struct to_be_freed_s
+{
+  void *v;
+  struct to_be_freed_s *next;
+};
+
+static struct to_be_freed_s *to_be_freed = NULL;
+
+void
+vapi_add_to_be_freed (void *v)
+{
+  struct to_be_freed_s *prev = NULL;
+  struct to_be_freed_s *tmp;
+  tmp = to_be_freed;
+  while (tmp && tmp->v)
+    {
+      prev = tmp;
+      tmp = tmp->next;
+    }
+  if (!tmp)
+    {
+      if (!prev)
+       {
+         tmp = to_be_freed = calloc (1, sizeof (*to_be_freed));
+       }
+      else
+       {
+         tmp = prev->next = calloc (1, sizeof (*to_be_freed));
+       }
+    }
+  VAPI_DBG ("To be freed %p", v);
+  tmp->v = v;
+}
+
+void
+vapi_trace_free (void *v)
+{
+  struct to_be_freed_s *tmp = to_be_freed;
+  while (tmp && tmp->v != v)
+    {
+      tmp = tmp->next;
+    }
+  if (tmp && tmp->v == v)
+    {
+      VAPI_DBG ("Freed %p", v);
+      tmp->v = NULL;
+    }
+  else
+    {
+      VAPI_ERR ("Trying to free untracked pointer %p", v);
+      abort ();
+    }
+}
+
+void
+vapi_to_be_freed_validate ()
+{
+  struct to_be_freed_s *tmp = to_be_freed;
+  while (tmp)
+    {
+      if (tmp->v)
+       {
+         VAPI_ERR ("Unfreed msg %p!", tmp->v);
+       }
+      tmp = tmp->next;
+    }
+}
+
+#endif
+
+void *
+vapi_msg_alloc (vapi_ctx_t ctx, size_t size)
+{
+  if (!ctx->connected)
+    {
+      return NULL;
+    }
+  void *rv = vl_msg_api_alloc_or_null (size);
+  return rv;
+}
+
+void
+vapi_msg_free (vapi_ctx_t ctx, void *msg)
+{
+  if (!ctx->connected)
+    {
+      return;
+    }
+#if VAPI_DEBUG_ALLOC
+  vapi_trace_free (msg);
+#endif
+  vl_msg_api_free (msg);
+}
+
+vapi_error_e
+vapi_ctx_alloc (vapi_ctx_t * result)
+{
+  vapi_ctx_t ctx = calloc (1, sizeof (struct vapi_ctx_s));
+  if (!ctx)
+    {
+      return VAPI_ENOMEM;
+    }
+  ctx->context_counter = 0;
+  ctx->vapi_msg_id_t_to_vl_msg_id =
+    malloc (__vapi_metadata.count *
+           sizeof (*ctx->vapi_msg_id_t_to_vl_msg_id));
+  if (!ctx->vapi_msg_id_t_to_vl_msg_id)
+    {
+      goto fail;
+    }
+  ctx->event_cbs = calloc (__vapi_metadata.count, sizeof (*ctx->event_cbs));
+  if (!ctx->event_cbs)
+    {
+      goto fail;
+    }
+  pthread_mutex_init (&ctx->requests_mutex, NULL);
+  *result = ctx;
+  return VAPI_OK;
+fail:
+  vapi_ctx_free (ctx);
+  return VAPI_ENOMEM;
+}
+
+void
+vapi_ctx_free (vapi_ctx_t ctx)
+{
+  assert (!ctx->connected);
+  free (ctx->requests);
+  free (ctx->vapi_msg_id_t_to_vl_msg_id);
+  free (ctx->event_cbs);
+  free (ctx->vl_msg_id_to_vapi_msg_t);
+  pthread_mutex_destroy (&ctx->requests_mutex);
+  free (ctx);
+}
+
+bool
+vapi_is_msg_available (vapi_ctx_t ctx, vapi_msg_id_t id)
+{
+  return vapi_lookup_vl_msg_id (ctx, id) != UINT16_MAX;
+}
+
+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)
+{
+  if (response_queue_size <= 0 || max_outstanding_requests <= 0)
+    {
+      return VAPI_EINVAL;
+    }
+  ctx->requests_size = max_outstanding_requests;
+  const size_t size = ctx->requests_size * sizeof (*ctx->requests);
+  void *tmp = realloc (ctx->requests, size);
+  if (!tmp)
+    {
+      return VAPI_ENOMEM;
+    }
+  ctx->requests = tmp;
+  memset (ctx->requests, 0, size);
+  ctx->requests_start = ctx->requests_count = 0;
+  if (chroot_prefix)
+    {
+      VAPI_DBG ("set memory root path `%s'", chroot_prefix);
+      vl_set_memory_root_path ((char *) chroot_prefix);
+    }
+  static char api_map[] = "/vpe-api";
+  VAPI_DBG ("client api map `%s'", api_map);
+  if ((vl_client_api_map (api_map)) < 0)
+    {
+      return VAPI_EMAP_FAIL;
+    }
+  VAPI_DBG ("connect client `%s'", name);
+  if (vl_client_connect ((char *) name, 0, response_queue_size) < 0)
+    {
+      vl_client_api_unmap ();
+      return VAPI_ECON_FAIL;
+    }
+#if VAPI_DEBUG_CONNECT
+  VAPI_DBG ("start probing messages");
+#endif
+  int rv;
+  int i;
+  for (i = 0; i < __vapi_metadata.count; ++i)
+    {
+      vapi_message_desc_t *m = __vapi_metadata.msgs[i];
+      u8 scratch[m->name_with_crc_len + 1];
+      memcpy (scratch, m->name_with_crc, m->name_with_crc_len + 1);
+      u32 id = vl_api_get_msg_index (scratch);
+      if (~0 != id)
+       {
+         if (id > UINT16_MAX)
+           {
+             VAPI_ERR ("Returned vl_msg_id `%u' > UINT16MAX `%u'!", id,
+                       UINT16_MAX);
+             rv = VAPI_EINVAL;
+             goto fail;
+           }
+         if (id > ctx->vl_msg_id_max)
+           {
+             vapi_msg_id_t *tmp = realloc (ctx->vl_msg_id_to_vapi_msg_t,
+                                           sizeof
+                                           (*ctx->vl_msg_id_to_vapi_msg_t) *
+                                           (id + 1));
+             if (!tmp)
+               {
+                 rv = VAPI_ENOMEM;
+                 goto fail;
+               }
+             ctx->vl_msg_id_to_vapi_msg_t = tmp;
+             ctx->vl_msg_id_max = id;
+           }
+         ctx->vl_msg_id_to_vapi_msg_t[id] = m->id;
+         ctx->vapi_msg_id_t_to_vl_msg_id[m->id] = id;
+#if VAPI_DEBUG_CONNECT
+         VAPI_DBG ("Message `%s' has vl_msg_id `%u'", m->name_with_crc,
+                   (unsigned) id);
+#endif
+       }
+      else
+       {
+         ctx->vapi_msg_id_t_to_vl_msg_id[m->id] = UINT16_MAX;
+         VAPI_DBG ("Message `%s' not available", m->name_with_crc);
+       }
+    }
+#if VAPI_DEBUG_CONNECT
+  VAPI_DBG ("finished probing messages");
+#endif
+  if (!vapi_is_msg_available (ctx, vapi_msg_id_control_ping) ||
+      !vapi_is_msg_available (ctx, vapi_msg_id_control_ping_reply))
+    {
+      VAPI_ERR
+       ("control ping or control ping reply not available, cannot connect");
+      rv = VAPI_EINCOMPATIBLE;
+      goto fail;
+    }
+  ctx->mode = mode;
+  ctx->connected = true;
+  return VAPI_OK;
+fail:
+  vl_client_disconnect ();
+  vl_client_api_unmap ();
+  return rv;
+}
+
+vapi_error_e
+vapi_disconnect (vapi_ctx_t ctx)
+{
+  if (!ctx->connected)
+    {
+      return VAPI_EINVAL;
+    }
+  vl_client_disconnect ();
+  vl_client_api_unmap ();
+#if VAPI_DEBUG_ALLOC
+  vapi_to_be_freed_validate ();
+#endif
+  ctx->connected = false;
+  return VAPI_OK;
+}
+
+vapi_error_e
+vapi_get_fd (vapi_ctx_t ctx, int *fd)
+{
+  return VAPI_ENOTSUP;
+}
+
+vapi_error_e
+vapi_send (vapi_ctx_t ctx, void *msg)
+{
+  vapi_error_e rv = VAPI_OK;
+  if (!ctx || !msg || !ctx->connected)
+    {
+      rv = VAPI_EINVAL;
+      goto out;
+    }
+  int tmp;
+  unix_shared_memory_queue_t *q = api_main.shmem_hdr->vl_input_queue;
+#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 ("send msg %u[%s]", msgid, __vapi_metadata.msgs[id]->name);
+       }
+      else
+       {
+         VAPI_DBG ("send msg %u[UNKNOWN]", msgid);
+       }
+    }
+  else
+    {
+      VAPI_DBG ("send msg %u[UNKNOWN]", msgid);
+    }
+#endif
+  tmp = unix_shared_memory_queue_add (q, (u8 *) & msg,
+                                     VAPI_MODE_BLOCKING ==
+                                     ctx->mode ? 0 : 1);
+  if (tmp < 0)
+    {
+      rv = VAPI_EAGAIN;
+    }
+out:
+  VAPI_DBG ("vapi_send() rv = %d", rv);
+  return rv;
+}
+
+vapi_error_e
+vapi_send2 (vapi_ctx_t ctx, void *msg1, void *msg2)
+{
+  vapi_error_e rv = VAPI_OK;
+  if (!ctx || !msg1 || !msg2 || !ctx->connected)
+    {
+      rv = VAPI_EINVAL;
+      goto out;
+    }
+  unix_shared_memory_queue_t *q = api_main.shmem_hdr->vl_input_queue;
+#if VAPI_DEBUG
+  unsigned msgid1 = be16toh (*(u16 *) msg1);
+  unsigned msgid2 = be16toh (*(u16 *) msg2);
+  const char *name1 = "UNKNOWN";
+  const char *name2 = "UNKNOWN";
+  if (msgid1 <= ctx->vl_msg_id_max)
+    {
+      vapi_msg_id_t id = ctx->vl_msg_id_to_vapi_msg_t[msgid1];
+      if (id < __vapi_metadata.count)
+       {
+         name1 = __vapi_metadata.msgs[id]->name;
+       }
+    }
+  if (msgid2 <= ctx->vl_msg_id_max)
+    {
+      vapi_msg_id_t id = ctx->vl_msg_id_to_vapi_msg_t[msgid2];
+      if (id < __vapi_metadata.count)
+       {
+         name2 = __vapi_metadata.msgs[id]->name;
+       }
+    }
+  VAPI_DBG ("send two: %u[%s], %u[%s]", msgid1, name1, msgid2, name2);
+#endif
+  int tmp = unix_shared_memory_queue_add2 (q, (u8 *) & msg1, (u8 *) & msg2,
+                                          VAPI_MODE_BLOCKING ==
+                                          ctx->mode ? 0 : 1);
+  if (tmp < 0)
+    {
+      rv = VAPI_EAGAIN;
+    }
+out:
+  VAPI_DBG ("vapi_send() rv = %d", rv);
+  return rv;
+}
+
+vapi_error_e
+vapi_recv (vapi_ctx_t ctx, void **msg, size_t * msg_size)
+{
+  if (!ctx || !ctx->connected || !msg || !msg_size)
+    {
+      return VAPI_EINVAL;
+    }
+  vapi_error_e rv = VAPI_OK;
+  api_main_t *am = &api_main;
+  uword data;
+
+  if (am->our_pid == 0)
+    {
+      return VAPI_EINVAL;
+    }
+
+  unix_shared_memory_queue_t *q = am->vl_input_queue;
+  VAPI_DBG ("doing shm queue sub");
+  int tmp = unix_shared_memory_queue_sub (q, (u8 *) & data, 0);
+  if (tmp == 0)
+    {
+#if VAPI_DEBUG_ALLOC
+      vapi_add_to_be_freed ((void *) data);
+#endif
+      msgbuf_t *msgbuf =
+       (msgbuf_t *) ((u8 *) data - offsetof (msgbuf_t, data));
+      if (!msgbuf->data_len)
+       {
+         vapi_msg_free (ctx, (u8 *) data);
+         return VAPI_EAGAIN;
+       }
+      *msg = (u8 *) data;
+      *msg_size = ntohl (msgbuf->data_len);
+      VAPI_DBG ("recv msg %p", *msg);
+    }
+  else
+    {
+      rv = VAPI_EAGAIN;
+    }
+  return rv;
+}
+
+vapi_error_e
+vapi_wait (vapi_ctx_t ctx, vapi_wait_mode_e mode)
+{
+  /* FIXME */
+  return VAPI_ENOTSUP;
+}
+
+static vapi_error_e
+vapi_dispatch_response (vapi_ctx_t ctx, vapi_msg_id_t id,
+                       u32 context, void *msg)
+{
+  int mrv;
+  if (0 != (mrv = pthread_mutex_lock (&ctx->requests_mutex)))
+    {
+      VAPI_DBG ("pthread_mutex_lock() failed, rv=%d:%s", mrv, strerror (mrv));
+      return VAPI_MUTEX_FAILURE;
+    }
+  int tmp = ctx->requests_start;
+  const int requests_end = vapi_requests_end (ctx);
+  while (ctx->requests[tmp].context != context && tmp != requests_end)
+    {
+      ++tmp;
+      if (tmp == ctx->requests_size)
+       {
+         tmp = 0;
+       }
+    }
+  VAPI_DBG ("dispatch, search from %d, %s at %d", ctx->requests_start,
+           ctx->requests[tmp].context == context ? "matched" : "stopped",
+           tmp);
+  vapi_error_e rv = VAPI_OK;
+  if (ctx->requests[tmp].context == context)
+    {
+      while (ctx->requests_start != tmp)
+       {
+         VAPI_ERR ("No response to req with context=%u",
+                   (unsigned) ctx->requests[tmp].context);
+         ctx->requests[ctx->requests_start].callback (ctx,
+                                                      ctx->requests
+                                                      [ctx->
+                                                       requests_start].callback_ctx,
+                                                      VAPI_ENORESP, true,
+                                                      NULL);
+         memset (&ctx->requests[ctx->requests_start], 0,
+                 sizeof (ctx->requests[ctx->requests_start]));
+         ++ctx->requests_start;
+         --ctx->requests_count;
+         if (ctx->requests_start == ctx->requests_size)
+           {
+             ctx->requests_start = 0;
+           }
+       }
+      // now ctx->requests_start == tmp
+      int payload_offset = vapi_get_payload_offset (id);
+      void *payload = ((u8 *) msg) + payload_offset;
+      bool is_last = true;
+      if (ctx->requests[tmp].is_dump)
+       {
+         if (vapi_msg_id_control_ping_reply == id)
+           {
+             payload = NULL;
+           }
+         else
+           {
+             is_last = false;
+           }
+       }
+      if (payload_offset != -1)
+       {
+         rv =
+           ctx->requests[tmp].callback (ctx, ctx->requests[tmp].callback_ctx,
+                                        VAPI_OK, is_last, payload);
+       }
+      else
+       {
+         /* this is a message without payload, so bend the callback a little
+          */
+         rv =
+           ((vapi_error_e (*)(vapi_ctx_t, void *, vapi_error_e, bool))
+            ctx->requests[tmp].callback) (ctx,
+                                          ctx->requests[tmp].callback_ctx,
+                                          VAPI_OK, is_last);
+       }
+      if (is_last)
+       {
+         memset (&ctx->requests[ctx->requests_start], 0,
+                 sizeof (ctx->requests[ctx->requests_start]));
+         ++ctx->requests_start;
+         --ctx->requests_count;
+         if (ctx->requests_start == ctx->requests_size)
+           {
+             ctx->requests_start = 0;
+           }
+       }
+      VAPI_DBG ("after dispatch, req start = %d, end = %d, count = %d",
+               ctx->requests_start, requests_end, ctx->requests_count);
+    }
+  if (0 != (mrv = pthread_mutex_unlock (&ctx->requests_mutex)))
+    {
+      VAPI_DBG ("pthread_mutex_unlock() failed, rv=%d:%s", mrv,
+               strerror (mrv));
+      abort ();                        /* this really shouldn't happen */
+    }
+  return rv;
+}
+
+static vapi_error_e
+vapi_dispatch_event (vapi_ctx_t ctx, vapi_msg_id_t id, void *msg)
+{
+  if (ctx->event_cbs[id].cb)
+    {
+      return ctx->event_cbs[id].cb (ctx, ctx->event_cbs[id].ctx, msg);
+    }
+  else if (ctx->generic_cb.cb)
+    {
+      return ctx->generic_cb.cb (ctx, ctx->generic_cb.ctx, id, msg);
+    }
+  else
+    {
+      VAPI_DBG
+       ("No handler/generic handler for msg id %u[%s], message ignored",
+        (unsigned) id, __vapi_metadata.msgs[id]->name);
+    }
+  return VAPI_OK;
+}
+
+static bool
+vapi_msg_is_with_context (vapi_msg_id_t id)
+{
+  assert (id <= __vapi_metadata.count);
+  return __vapi_metadata.msgs[id]->has_context;
+}
+
+vapi_error_e
+vapi_dispatch_one (vapi_ctx_t ctx)
+{
+  VAPI_DBG ("vapi_dispatch_one()");
+  void *msg;
+  size_t size;
+  vapi_error_e rv = vapi_recv (ctx, &msg, &size);
+  if (VAPI_OK != rv)
+    {
+      VAPI_DBG ("vapi_recv failed with rv=%d", rv);
+      return rv;
+    }
+  u16 vpp_id = be16toh (*(u16 *) msg);
+  if (vpp_id > ctx->vl_msg_id_max)
+    {
+      VAPI_ERR ("Unknown msg ID received, id `%u', out of range <0,%u>",
+               (unsigned) vpp_id, (unsigned) ctx->vl_msg_id_max);
+      vapi_msg_free (ctx, msg);
+      return VAPI_EINVAL;
+    }
+  if (~0 == (unsigned) ctx->vl_msg_id_to_vapi_msg_t[vpp_id])
+    {
+      VAPI_ERR ("Unknown msg ID received, id `%u' marked as not supported",
+               (unsigned) vpp_id);
+      vapi_msg_free (ctx, msg);
+      return VAPI_EINVAL;
+    }
+  const vapi_msg_id_t id = ctx->vl_msg_id_to_vapi_msg_t[vpp_id];
+  const size_t expect_size = vapi_get_message_size (id);
+  if (size < expect_size)
+    {
+      VAPI_ERR
+       ("Invalid msg received, unexpected size `%zu' < expected min `%zu'",
+        size, expect_size);
+      vapi_msg_free (ctx, msg);
+      return VAPI_EINVAL;
+    }
+  u32 context;
+  vapi_get_swap_to_host_func (id) (msg);
+  if (vapi_msg_is_with_context (id))
+    {
+      context = *(u32 *) (((u8 *) msg) + vapi_get_context_offset (id));
+      /* is this a message originating from VAPI? */
+      VAPI_DBG ("dispatch, context is %x", context);
+      if (context & context_counter_mask)
+       {
+         rv = vapi_dispatch_response (ctx, id, context, msg);
+         goto done;
+       }
+    }
+  rv = vapi_dispatch_event (ctx, id, msg);
+
+done:
+  vapi_msg_free (ctx, msg);
+  return rv;
+}
+
+vapi_error_e
+vapi_dispatch (vapi_ctx_t ctx)
+{
+  vapi_error_e rv = VAPI_OK;
+  while (!vapi_requests_empty (ctx))
+    {
+      rv = vapi_dispatch_one (ctx);
+      if (VAPI_OK != rv)
+       {
+         return rv;
+       }
+    }
+  return rv;
+}
+
+void
+vapi_set_event_cb (vapi_ctx_t ctx, vapi_msg_id_t id,
+                  vapi_event_cb callback, void *callback_ctx)
+{
+  vapi_event_cb_with_ctx *c = &ctx->event_cbs[id];
+  c->cb = callback;
+  c->ctx = callback_ctx;
+}
+
+void
+vapi_clear_event_cb (vapi_ctx_t ctx, vapi_msg_id_t id)
+{
+  vapi_set_event_cb (ctx, id, NULL, NULL);
+}
+
+void
+vapi_set_generic_event_cb (vapi_ctx_t ctx, vapi_generic_event_cb callback,
+                          void *callback_ctx)
+{
+  ctx->generic_cb.cb = callback;
+  ctx->generic_cb.ctx = callback_ctx;
+}
+
+void
+vapi_clear_generic_event_cb (vapi_ctx_t ctx)
+{
+  ctx->generic_cb.cb = NULL;
+  ctx->generic_cb.ctx = NULL;
+}
+
+u16
+vapi_lookup_vl_msg_id (vapi_ctx_t ctx, vapi_msg_id_t id)
+{
+  assert (id < __vapi_metadata.count);
+  return ctx->vapi_msg_id_t_to_vl_msg_id[id];
+}
+
+int
+vapi_get_client_index (vapi_ctx_t ctx)
+{
+  return api_main.my_client_index;
+}
+
+bool
+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)
+{
+  return ctx->requests_size - 1;
+}
+
+int
+vapi_get_payload_offset (vapi_msg_id_t id)
+{
+  assert (id < __vapi_metadata.count);
+  return __vapi_metadata.msgs[id]->payload_offset;
+}
+
+void (*vapi_get_swap_to_host_func (vapi_msg_id_t id)) (void *msg)
+{
+  assert (id < __vapi_metadata.count);
+  return __vapi_metadata.msgs[id]->swap_to_host;
+}
+
+void (*vapi_get_swap_to_be_func (vapi_msg_id_t id)) (void *msg)
+{
+  assert (id < __vapi_metadata.count);
+  return __vapi_metadata.msgs[id]->swap_to_be;
+}
+
+size_t
+vapi_get_message_size (vapi_msg_id_t id)
+{
+  assert (id < __vapi_metadata.count);
+  return __vapi_metadata.msgs[id]->size;
+}
+
+size_t
+vapi_get_context_offset (vapi_msg_id_t id)
+{
+  assert (id < __vapi_metadata.count);
+  return __vapi_metadata.msgs[id]->context_offset;
+}
+
+vapi_msg_id_t
+vapi_register_msg (vapi_message_desc_t * msg)
+{
+  int i = 0;
+  for (i = 0; i < __vapi_metadata.count; ++i)
+    {
+      if (!strcmp
+         (msg->name_with_crc, __vapi_metadata.msgs[i]->name_with_crc))
+       {
+         /* this happens if somebody is linking together several objects while
+          * using the static inline headers, just fill in the already
+          * assigned id here so that all the objects are in sync */
+         msg->id = __vapi_metadata.msgs[i]->id;
+         return msg->id;
+       }
+    }
+  vapi_msg_id_t id = __vapi_metadata.count;
+  ++__vapi_metadata.count;
+  __vapi_metadata.msgs =
+    realloc (__vapi_metadata.msgs,
+            sizeof (*__vapi_metadata.msgs) * __vapi_metadata.count);
+  __vapi_metadata.msgs[id] = msg;
+  size_t s = strlen (msg->name_with_crc);
+  if (s > __vapi_metadata.max_len_name_with_crc)
+    {
+      __vapi_metadata.max_len_name_with_crc = s;
+    }
+  msg->id = id;
+  return id;
+}
+
+vapi_error_e
+vapi_producer_lock (vapi_ctx_t ctx)
+{
+  int mrv;
+  if (0 != (mrv = pthread_mutex_lock (&ctx->requests_mutex)))
+    {
+      VAPI_DBG ("pthread_mutex_lock() failed, rv=%d:%s", mrv, strerror (mrv));
+      (void) mrv;              /* avoid warning if the above debug is not enabled */
+      return VAPI_MUTEX_FAILURE;
+    }
+  return VAPI_OK;
+}
+
+vapi_error_e
+vapi_producer_unlock (vapi_ctx_t ctx)
+{
+  int mrv;
+  if (0 != (mrv = pthread_mutex_unlock (&ctx->requests_mutex)))
+    {
+      VAPI_DBG ("pthread_mutex_unlock() failed, rv=%d:%s", mrv,
+               strerror (mrv));
+      (void) mrv;              /* avoid warning if the above debug is not enabled */
+      return VAPI_MUTEX_FAILURE;
+    }
+  return VAPI_OK;
+}
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/vpp-api/vapi/vapi.h b/src/vpp-api/vapi/vapi.h
new file mode 100644 (file)
index 0000000..1e1d567
--- /dev/null
@@ -0,0 +1,285 @@
+/*
+ *------------------------------------------------------------------
+ * 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 vpp_api_h_included
+#define vpp_api_h_included
+
+#include <string.h>
+#include <stdbool.h>
+#include <vppinfra/types.h>
+
+/**
+ * @file vapi.h
+ *
+ * common vpp api C declarations
+ *
+ * This file declares the common C API functions. These include connect,
+ * disconnect and utility functions as well as the low-level vapi_send and
+ * vapi_recv API. This is only the transport layer.
+ *
+ * Message formats and higher-level APIs are generated by running the
+ * vapi_c_gen.py script (which is run for in-tree APIs as part of the build
+ * 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;
+
+/**
+ * @brief allocate vapi message of given size
+ *
+ * @note message must be freed by vapi_msg_free if not consumed by vapi_send
+ * call
+ *
+ * @param ctx opaque vapi context
+ *
+ * @return pointer to message or NULL if out of memory
+ */
+void *vapi_msg_alloc (vapi_ctx_t ctx, size_t size);
+
+/**
+ * @brief free a vapi message
+ *
+ * @note messages received by vapi_recv must be freed when no longer needed
+ *
+ * @param ctx opaque vapi context
+ * @param msg message to be freed
+ */
+void vapi_msg_free (vapi_ctx_t ctx, void *msg);
+
+/**
+ * @brief allocate vapi context
+ *
+ * @param[out] pointer to result variable
+ *
+ * @return VAPI_OK on success, other error code on error
+ */
+vapi_error_e vapi_ctx_alloc (vapi_ctx_t * result);
+
+/**
+ * @brief free vapi context
+ */
+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);
+
+/**
+ * @brief connect to vpp
+ *
+ * @param ctx opaque vapi context, must be allocated using vapi_ctx_alloc first
+ * @param name application name
+ * @param chroot_prefix shared memory prefix
+ * @param max_outstanding_requests max number of outstanding requests queued
+ * @param response_queue_size size of the response queue
+ * @param mode mode of operation - blocking or nonblocking
+ *
+ * @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);
+
+/**
+ * @brief disconnect from vpp
+ *
+ * @param ctx opaque vapi context
+ *
+ * @return VAPI_OK on success, other error code on error
+ */
+vapi_error_e vapi_disconnect (vapi_ctx_t ctx);
+
+/**
+ * @brief get event file descriptor
+ *
+ * @note this file descriptor becomes readable when messages (from vpp)
+ * are waiting in queue
+ *
+ * @param ctx opaque vapi context
+ * @param[out] fd pointer to result variable
+ *
+ * @return VAPI_OK on success, other error code on error
+ */
+vapi_error_e vapi_get_fd (vapi_ctx_t ctx, int *fd);
+
+/**
+ * @brief low-level api for sending messages to vpp
+ *
+ * @note it is not recommended to use this api directly, use generated api
+ * instead
+ *
+ * @param ctx opaque vapi context
+ * @param msg message to send
+ *
+ * @return VAPI_OK on success, other error code on error
+ */
+vapi_error_e vapi_send (vapi_ctx_t ctx, void *msg);
+
+/**
+ * @brief low-level api for atomically sending two messages to vpp - either
+ * both messages are sent or neither one is
+ *
+ * @note it is not recommended to use this api directly, use generated api
+ * instead
+ *
+ * @param ctx opaque vapi context
+ * @param msg1 first message to send
+ * @param msg2 second message to send
+ *
+ * @return VAPI_OK on success, other error code on error
+ */
+vapi_error_e vapi_send2 (vapi_ctx_t ctx, void *msg1, void *msg2);
+
+/**
+ * @brief low-level api for reading messages from vpp
+ *
+ * @note it is not recommended to use this api directly, use generated api
+ * instead
+ *
+ * @param ctx opaque vapi context
+ * @param[out] msg pointer to result variable containing message
+ * @param[out] msg_size pointer to result variable containing message size
+ *
+ * @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);
+
+/**
+ * @brief wait for connection to become readable or writable
+ *
+ * @param ctx opaque vapi context
+ * @param mode type of property to wait for - readability, writability or both
+ *
+ * @return VAPI_OK on success, other error code on error
+ */
+vapi_error_e vapi_wait (vapi_ctx_t ctx, vapi_wait_mode_e mode);
+
+/**
+ * @brief pick next message sent by vpp and call the appropriate callback
+ *
+ * @return VAPI_OK on success, other error code on error
+ */
+vapi_error_e vapi_dispatch_one (vapi_ctx_t ctx);
+
+/**
+ * @brief loop vapi_dispatch_one until responses to all currently outstanding
+ * requests have been received and their callbacks called
+ *
+ * @note the dispatch loop is interrupted if any error is encountered or
+ * returned from the callback, in which case this error is returned as the
+ * result of vapi_dispatch. In this case it might be necessary to call dispatch
+ * again to process the remaining messages. Returning VAPI_EUSER from
+ * a callback allows the user to break the dispatch loop (and distinguish
+ * this case in the calling code from other failures). VAPI never returns
+ * VAPI_EUSER on its own.
+ *
+ * @return VAPI_OK on success, other error code on error
+ */
+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);
+
+/**
+ * @brief set event callback to call when message with given id is dispatched
+ *
+ * @param ctx opaque vapi context
+ * @param id message id
+ * @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);
+
+/**
+ * @brief clear event callback for given message id
+ *
+ * @param ctx opaque vapi context
+ * @param id message 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);
+/**
+ * @brief set generic event callback
+ *
+ * @note this callback is called by dispatch if no message-type specific
+ * callback is set (so it's a fallback callback)
+ *
+ * @param ctx opaque vapi context
+ * @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);
+
+/**
+ * @brief clear generic event callback
+ *
+ * @param ctx opaque vapi context
+ */
+void vapi_clear_generic_event_cb (vapi_ctx_t ctx);
+
+#endif
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/vpp-api/vapi/vapi_c_gen.py b/src/vpp-api/vapi/vapi_c_gen.py
new file mode 100755 (executable)
index 0000000..2bc1eef
--- /dev/null
@@ -0,0 +1,809 @@
+#!/usr/bin/env python3
+
+import argparse
+import os
+import sys
+import logging
+from vapi_json_parser import Field, Struct, Message, JsonParser,\
+    SimpleType, StructType
+
+
+class CField(Field):
+    def __init__(
+            self,
+            field_name,
+            field_type,
+            array_len=None,
+            nelem_field=None):
+        super().__init__(field_name, field_type, array_len, nelem_field)
+
+    def get_c_def(self):
+        if self.len is not None:
+            return "%s %s[%d]" % (self.type.get_c_name(), self.name, self.len)
+        else:
+            return "%s %s" % (self.type.get_c_name(), self.name)
+
+    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 } }"\
+                    " while(0);" % (
+                        self.len,
+                        self.type.get_swap_to_be_code(struct, "%s[i]" % var))
+            else:
+                if self.nelem_field.needs_byte_swap():
+                    nelem_field = "%s(%s%s)" % (
+                        self.nelem_field.type.get_swap_to_host_func_name(),
+                        struct, self.nelem_field.name)
+                else:
+                    nelem_field = "%s%s" % (struct, self.nelem_field.name)
+                return (
+                    "do { int i; for (i = 0; i < %s; ++i) { %s } }"
+                    " while(0);" %
+                    (nelem_field, self.type.get_swap_to_be_code(
+                        struct, "%s[i]" % var)))
+        return self.type.get_swap_to_be_code(struct, "%s" % var)
+
+    def get_swap_to_host_code(self, struct, var):
+        if self.len is not None:
+            if self.len > 0:
+                return "do { int 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 } }"
+                    " while(0);" %
+                    (struct, self.nelem_field.name,
+                     self.type.get_swap_to_host_code(
+                         struct, "%s[i]" % var)))
+        return self.type.get_swap_to_host_code(struct, "%s" % var)
+
+    def needs_byte_swap(self):
+        return self.type.needs_byte_swap()
+
+
+class CStruct(Struct):
+    def __init__(self, name, fields):
+        super().__init__(name, fields)
+
+    def get_c_def(self):
+        return "\n".join([
+            "typedef struct __attribute__((__packed__)) {",
+            "%s;" % ";\n".join(["  %s" % x.get_c_def()
+                                for x in self.fields]),
+            "} %s;" % self.get_c_name()])
+
+
+class CSimpleType (SimpleType):
+
+    swap_to_be_dict = {
+        'i16': 'htobe16', 'u16': 'htobe16',
+        'i32': 'htobe32', 'u32': 'htobe32',
+        'i64': 'htobe64', 'u64': 'htobe64',
+    }
+
+    swap_to_host_dict = {
+        'i16': 'be16toh', 'u16': 'be16toh',
+        'i32': 'be32toh', 'u32': 'be32toh',
+        'i64': 'be64toh', 'u64': 'be64toh',
+    }
+
+    def __init__(self, name):
+        super().__init__(name)
+
+    def get_c_name(self):
+        return self.name
+
+    def get_swap_to_be_func_name(self):
+        return self.swap_to_be_dict[self.name]
+
+    def get_swap_to_host_func_name(self):
+        return self.swap_to_host_dict[self.name]
+
+    def get_swap_to_be_code(self, struct, var):
+        x = "%s%s" % (struct, var)
+        return "%s = %s(%s);" % (x, self.get_swap_to_be_func_name(), x)
+
+    def get_swap_to_host_code(self, struct, var):
+        x = "%s%s" % (struct, var)
+        return "%s = %s(%s);" % (x, self.get_swap_to_host_func_name(), x)
+
+    def needs_byte_swap(self):
+        try:
+            self.get_swap_to_host_func_name()
+            return True
+        except:
+            pass
+        return False
+
+
+class CStructType (StructType, CStruct):
+    def __init__(self, definition, typedict, field_class):
+        super().__init__(definition, typedict, field_class)
+
+    def get_c_name(self):
+        return "vapi_type_%s" % self.name
+
+    def get_swap_to_be_func_name(self):
+        return "%s_hton" % self.get_c_name()
+
+    def get_swap_to_host_func_name(self):
+        return "%s_ntoh" % self.get_c_name()
+
+    def get_swap_to_be_func_decl(self):
+        return "void %s(%s *msg)" % (
+            self.get_swap_to_be_func_name(), self.get_c_name())
+
+    def get_swap_to_be_func_def(self):
+        return "%s\n{\n%s\n}" % (
+            self.get_swap_to_be_func_decl(),
+            "\n".join([
+                "  %s" % p.get_swap_to_be_code("msg->", "%s" % p.name)
+                for p in self.fields if p.needs_byte_swap()]),
+        )
+
+    def get_swap_to_host_func_decl(self):
+        return "void %s(%s *msg)" % (
+            self.get_swap_to_host_func_name(), self.get_c_name())
+
+    def get_swap_to_host_func_def(self):
+        return "%s\n{\n%s\n}" % (
+            self.get_swap_to_host_func_decl(),
+            "\n".join([
+                "  %s" % p.get_swap_to_host_code("msg->", "%s" % p.name)
+                for p in self.fields if p.needs_byte_swap()]),
+        )
+
+    def get_swap_to_be_code(self, struct, var):
+        return "%s(&%s%s);" % (self.get_swap_to_be_func_name(), struct, var)
+
+    def get_swap_to_host_code(self, struct, var):
+        return "%s(&%s%s);" % (self.get_swap_to_host_func_name(), struct, var)
+
+    def needs_byte_swap(self):
+        for f in self.fields:
+            if f.needs_byte_swap():
+                return True
+        return False
+
+
+class CMessage (Message):
+    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)
+        self.payload_members = [
+            "  %s" % p.get_c_def()
+            for p in self.fields
+            if p.type != self.header
+        ]
+
+    def has_payload(self):
+        return len(self.payload_members) > 0
+
+    def get_msg_id_name(self):
+        return "vapi_msg_id_%s" % self.name
+
+    def get_c_name(self):
+        return "vapi_msg_%s" % self.name
+
+    def get_payload_struct_name(self):
+        return "vapi_payload_%s" % self.name
+
+    def get_alloc_func_vla_field_length_name(self, field):
+        return "%s_array_size" % field.name
+
+    def get_alloc_func_name(self):
+        return "vapi_alloc_%s" % self.name
+
+    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]))
+
+    def get_alloc_func_def(self):
+        extra = []
+        if self.header.has_field('client_index'):
+            extra.append(
+                "  msg->header.client_index = vapi_get_client_index(ctx);")
+        if self.header.has_field('context'):
+            extra.append("  msg->header.context = 0;")
+        return "\n".join([
+            "%s" % self.get_alloc_func_decl(),
+            "{",
+            "  %s *msg = NULL;" % self.get_c_name(),
+            "  const size_t size = sizeof(%s)%s;" % (
+                self.get_c_name(),
+                "".join([
+                    " + sizeof(msg->payload.%s[0]) * %s" % (
+                        f.name,
+                        self.get_alloc_func_vla_field_length_name(f))
+                    for f in self.fields
+                    if f.nelem_field is not None
+                ])),
+            "  msg = vapi_msg_alloc(ctx, size);",
+            "  if (!msg) {",
+            "    return NULL;",
+            "  }",
+        ] + extra + [
+            "  msg->header._vl_msg_id = vapi_lookup_vl_msg_id(ctx, %s);" %
+            self.get_msg_id_name(),
+            "\n".join(["  msg->payload.%s = %s;" % (
+                f.nelem_field.name,
+                self.get_alloc_func_vla_field_length_name(f))
+                for f in self.fields
+                if f.nelem_field is not None]),
+            "  return msg;",
+            "}"])
+
+    def get_calc_msg_size_func_name(self):
+        return "vapi_calc_%s_msg_size" % self.name
+
+    def get_calc_msg_size_func_decl(self):
+        return "uword %s(%s *msg)" % (
+            self.get_calc_msg_size_func_name(),
+            self.get_c_name())
+
+    def get_calc_msg_size_func_def(self):
+        return "\n".join([
+            "%s" % self.get_calc_msg_size_func_decl(),
+            "{",
+            "  return sizeof(*msg)%s;" %
+            "".join(["+ msg->payload.%s * sizeof(msg->payload.%s[0])" % (
+                    f.nelem_field.name,
+                    f.name)
+                for f in self.fields
+                if f.nelem_field is not None
+            ]),
+            "}",
+        ])
+
+    def get_c_def(self):
+        if self.has_payload():
+            return "\n".join([
+                "typedef struct __attribute__ ((__packed__)) {",
+                "%s; " %
+                ";\n".join(self.payload_members),
+                "} %s;" % self.get_payload_struct_name(),
+                "",
+                "typedef struct __attribute__ ((__packed__)) {",
+                ("  %s %s;" % (self.header.get_c_name(),
+                               self.fields[0].name)
+                    if self.header is not None else ""),
+                "  %s payload;" % self.get_payload_struct_name(),
+                "} %s;" % self.get_c_name(), ])
+        else:
+            return "\n".join([
+                "typedef struct __attribute__ ((__packed__)) {",
+                ("  %s %s;" % (self.header.get_c_name(),
+                               self.fields[0].name)
+                    if self.header is not None else ""),
+                "} %s;" % self.get_c_name(), ])
+
+    def get_swap_payload_to_host_func_name(self):
+        return "%s_payload_ntoh" % self.get_c_name()
+
+    def get_swap_payload_to_be_func_name(self):
+        return "%s_payload_hton" % self.get_c_name()
+
+    def get_swap_payload_to_host_func_decl(self):
+        return "void %s(%s *payload)" % (
+            self.get_swap_payload_to_host_func_name(),
+            self.get_payload_struct_name())
+
+    def get_swap_payload_to_be_func_decl(self):
+        return "void %s(%s *payload)" % (
+            self.get_swap_payload_to_be_func_name(),
+            self.get_payload_struct_name())
+
+    def get_swap_payload_to_be_func_def(self):
+        return "%s\n{\n%s\n}" % (
+            self.get_swap_payload_to_be_func_decl(),
+            "\n".join([
+                "  %s" % p.get_swap_to_be_code("payload->", "%s" % p.name)
+                for p in self.fields
+                if p.needs_byte_swap() and p.type != self.header]),
+        )
+
+    def get_swap_payload_to_host_func_def(self):
+        return "%s\n{\n%s\n}" % (
+            self.get_swap_payload_to_host_func_decl(),
+            "\n".join([
+                "  %s" % p.get_swap_to_host_code("payload->", "%s" % p.name)
+                for p in self.fields
+                if p.needs_byte_swap() and p.type != self.header]),
+        )
+
+    def get_swap_to_host_func_name(self):
+        return "%s_ntoh" % self.get_c_name()
+
+    def get_swap_to_be_func_name(self):
+        return "%s_hton" % self.get_c_name()
+
+    def get_swap_to_host_func_decl(self):
+        return "void %s(%s *msg)" % (
+            self.get_swap_to_host_func_name(), self.get_c_name())
+
+    def get_swap_to_be_func_decl(self):
+        return "void %s(%s *msg)" % (
+            self.get_swap_to_be_func_name(), self.get_c_name())
+
+    def get_swap_to_be_func_def(self):
+        return "\n".join([
+            "%s" % self.get_swap_to_be_func_decl(),
+            "{",
+            ("  VAPI_DBG(\"Swapping `%s'@%%p to big endian\", msg);" %
+                self.get_c_name()),
+            "  %s(&msg->header);" % self.header.get_swap_to_be_func_name()
+            if self.header is not None else "",
+            "  %s(&msg->payload);" % self.get_swap_payload_to_be_func_name()
+            if self.has_payload() else "",
+            "}",
+        ])
+
+    def get_swap_to_host_func_def(self):
+        return "\n".join([
+            "%s" % self.get_swap_to_host_func_decl(),
+            "{",
+            ("  VAPI_DBG(\"Swapping `%s'@%%p to host byte order\", msg);" %
+                self.get_c_name()),
+            "  %s(&msg->header);" % self.header.get_swap_to_host_func_name()
+            if self.header is not None else "",
+            "  %s(&msg->payload);" % self.get_swap_payload_to_host_func_name()
+            if self.has_payload() else "",
+            "}",
+        ])
+
+    def get_op_func_name(self):
+        return "vapi_%s" % self.name
+
+    def get_op_func_decl(self):
+        if self.reply.has_payload():
+            return "vapi_error_e %s(%s)" % (
+                self.get_op_func_name(),
+                ",\n  ".join([
+                    'struct vapi_ctx_s *ctx',
+                    '%s *msg' % self.get_c_name(),
+                    'vapi_error_e (*callback)(struct vapi_ctx_s *ctx',
+                    '                         void *callback_ctx',
+                    '                         vapi_error_e rv',
+                    '                         bool is_last',
+                    '                         %s *reply)' %
+                    self.reply.get_payload_struct_name(),
+                    'void *callback_ctx',
+                ])
+            )
+        else:
+            return "vapi_error_e %s(%s)" % (
+                self.get_op_func_name(),
+                ",\n  ".join([
+                    'struct vapi_ctx_s *ctx',
+                    '%s *msg' % self.get_c_name(),
+                    'vapi_error_e (*callback)(struct vapi_ctx_s *ctx',
+                    '                         void *callback_ctx',
+                    '                         vapi_error_e rv',
+                    '                         bool is_last)',
+                    'void *callback_ctx',
+                ])
+            )
+
+    def get_op_func_def(self):
+        return "\n".join([
+            "%s" % self.get_op_func_decl(),
+            "{",
+            "  if (!msg || !callback) {",
+            "    return VAPI_EINVAL;",
+            "  }",
+            "  if (vapi_is_nonblocking(ctx) && vapi_requests_full(ctx)) {",
+            "    return VAPI_EAGAIN;",
+            "  }",
+            "  vapi_error_e rv;",
+            "  if (VAPI_OK != (rv = vapi_producer_lock (ctx))) {",
+            "    return rv;",
+            "  }",
+            "  u32 req_context = vapi_gen_req_context(ctx);",
+            "  msg->header.context = req_context;",
+            "  %s(msg);" % self.get_swap_to_be_func_name(),
+            ("  if (VAPI_OK == (rv = vapi_send_with_control_ping "
+                "(ctx, msg, req_context))) {"
+                if self.is_dump() else
+                "  if (VAPI_OK == (rv = vapi_send (ctx, msg))) {"
+             ),
+            ("    vapi_store_request(ctx, req_context, %s, "
+             "(vapi_cb_t)callback, callback_ctx);" %
+             ("true" if self.is_dump() else "false")),
+            "    if (VAPI_OK != vapi_producer_unlock (ctx)) {",
+            "      abort (); /* this really shouldn't happen */",
+            "    }",
+            "    if (vapi_is_nonblocking(ctx)) {",
+            "      rv = VAPI_OK;",
+            "    } else {",
+            "      rv = vapi_dispatch(ctx);",
+            "    }",
+            "  } else {",
+            "    %s(msg);" % self.get_swap_to_host_func_name(),
+            "    if (VAPI_OK != vapi_producer_unlock (ctx)) {",
+            "      abort (); /* this really shouldn't happen */",
+            "    }",
+            "  }",
+            "  return rv;",
+            "}",
+            "",
+        ])
+
+    def get_event_cb_func_decl(self):
+        if not self.is_reply():
+            raise Exception(
+                "Cannot register event callback for non-reply function")
+        if self.has_payload():
+            return "\n".join([
+                "void vapi_set_%s_event_cb (" %
+                self.get_c_name(),
+                "  struct vapi_ctx_s *ctx, ",
+                ("  vapi_error_e (*callback)(struct vapi_ctx_s *ctx, "
+                 "void *callback_ctx, %s *payload)," %
+                 self.get_payload_struct_name()),
+                "  void *callback_ctx)",
+            ])
+        else:
+            return "\n".join([
+                "void vapi_set_%s_event_cb (" %
+                self.get_c_name(),
+                "  struct vapi_ctx_s *ctx, ",
+                "  vapi_error_e (*callback)(struct vapi_ctx_s *ctx, "
+                "void *callback_ctx),",
+                "  void *callback_ctx)",
+            ])
+
+    def get_event_cb_func_def(self):
+        if not self.is_reply():
+            raise Exception(
+                "Cannot register event callback for non-reply function")
+        return "\n".join([
+            "%s" % self.get_event_cb_func_decl(),
+            "{",
+            ("  vapi_set_event_cb(ctx, %s, (vapi_event_cb)callback, "
+             "callback_ctx);" %
+             self.get_msg_id_name()),
+            "}"])
+
+    def get_c_metadata_struct_name(self):
+        return "__vapi_metadata_%s" % self.name
+
+    def get_c_constructor(self):
+        has_context = False
+        if self.header is not None:
+            has_context = self.header.has_field('context')
+        return '\n'.join([
+            'static void __attribute__((constructor)) __vapi_constructor_%s()'
+            % self.name,
+            '{',
+            '  static const char name[] = "%s";' % self.name,
+            '  static const char name_with_crc[] = "%s_%s";'
+            % (self.name, self.crc[2:]),
+            '  static vapi_message_desc_t %s = {' %
+            self.get_c_metadata_struct_name(),
+            '    name,',
+            '    sizeof(name) - 1,',
+            '    name_with_crc,',
+            '    sizeof(name_with_crc) - 1,',
+            '    true,' if has_context else '    false,',
+            '    offsetof(%s, context),' % self.header.get_c_name()
+            if has_context else '    0,',
+            ('    offsetof(%s, payload),' % self.get_c_name())
+            if self.has_payload() else '-1,',
+            '    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(),
+            '    ~0,',
+            '  };',
+            '',
+            '  %s = vapi_register_msg(&%s);' %
+            (self.get_msg_id_name(), self.get_c_metadata_struct_name()),
+            '  VAPI_DBG("Assigned msg id %%d to %s", %s);' %
+            (self.name, self.get_msg_id_name()),
+            '}',
+        ])
+
+
+vapi_send_with_control_ping = """
+static inline vapi_error_e
+vapi_send_with_control_ping (vapi_ctx_t ctx, void *msg, u32 context)
+{
+  vapi_msg_control_ping *ping = vapi_alloc_control_ping (ctx);
+  if (!ping)
+    {
+      return VAPI_ENOMEM;
+    }
+  ping->header.context = context;
+  vapi_msg_control_ping_hton (ping);
+  return vapi_send2 (ctx, msg, ping);
+}
+"""
+
+
+def gen_json_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("")
+    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("")
+    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("")
+        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("")
+    for m in parser.messages_by_json[j].values():
+        print("extern vapi_msg_id_t %s;" % m.get_msg_id_name())
+    print("")
+    print("#define DEFINE_VAPI_MSG_IDS_%s\\" %
+          j.replace(".", "_").replace("/", "_").replace("-", "_").upper())
+    print("\\\n".join([
+        "  vapi_msg_id_t %s;" % m.get_msg_id_name()
+        for m in parser.messages_by_json[j].values()
+    ]))
+    print("")
+    print("")
+    for t in parser.types_by_json[j].values():
+        try:
+            print("%s" % t.get_c_def())
+            print("")
+        except:
+            pass
+    for m in parser.messages_by_json[j].values():
+        print("%s" % m.get_c_def())
+        print("")
+
+    print("")
+    function_attrs = "static inline "
+    for t in parser.types_by_json[j].values():
+        print("%s%s" % (function_attrs, t.get_swap_to_be_func_def()))
+        print("")
+        print("%s%s" % (function_attrs, t.get_swap_to_host_func_def()))
+        print("")
+    for m in parser.messages_by_json[j].values():
+        if m.has_payload():
+            print("%s%s" % (function_attrs,
+                            m.get_swap_payload_to_be_func_def()))
+            print("")
+            print("%s%s" % (function_attrs,
+                            m.get_swap_payload_to_host_func_def()))
+            print("")
+        print("%s%s" % (function_attrs, m.get_calc_msg_size_func_def()))
+        print("")
+        print("%s%s" % (function_attrs, m.get_swap_to_be_func_def()))
+        print("")
+        print("%s%s" % (function_attrs, m.get_swap_to_host_func_def()))
+        print("")
+    for m in parser.messages_by_json[j].values():
+        if m.is_reply():
+            continue
+        print("%s%s" % (function_attrs, m.get_alloc_func_def()))
+        print("")
+        print("%s%s" % (function_attrs, m.get_op_func_def()))
+        print("")
+    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%s;" % (function_attrs, m.get_event_cb_func_def()))
+        print("")
+    print("")
+
+    if io.name == "vpe.api.vapi.h":
+        print("%s" % vapi_send_with_control_ping)
+        print("")
+
+    print("#endif")
+    sys.stdout = orig_stdout
+
+
+def json_to_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)
+
+
+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 C GEN")
+    logger.setLevel(log_level)
+
+    argparser = argparse.ArgumentParser(description="VPP JSON API parser")
+    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')
+    args = argparser.parse_args()
+
+    jsonparser = JsonParser(logger, args.files,
+                            simple_type_class=CSimpleType,
+                            struct_type_class=CStructType,
+                            field_class=CField,
+                            message_class=CMessage)
+
+    # not using the model of having separate generated header and code files
+    # with generated symbols present in shared library (per discussion with
+    # Damjan), to avoid symbol version issues in .so
+    # gen_c_headers_and_code(jsonparser, logger, args.prefix)
+
+    gen_c_unified_headers(jsonparser, logger, args.prefix)
+
+    for e in jsonparser.exceptions:
+        logger.error(e)
diff --git a/src/vpp-api/vapi/vapi_dbg.h b/src/vpp-api/vapi/vapi_dbg.h
new file mode 100644 (file)
index 0000000..95a8008
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+ *------------------------------------------------------------------
+ * 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 __included_vapi_debug_h__
+#define __included_vapi_debug_h__
+
+/* controls debug prints */
+#define VAPI_DEBUG (0)
+#define VAPI_DEBUG_CONNECT (0)
+#define VAPI_DEBUG_ALLOC (0)
+
+#if VAPI_DEBUG
+#include <stdio.h>
+#define VAPI_DEBUG_FILE_DEF           \
+  static const char *__file = NULL;   \
+  {                                   \
+    __file = strrchr (__FILE__, '/'); \
+    if (__file)                       \
+      {                               \
+        ++__file;                     \
+      }                               \
+    else                              \
+      {                               \
+        __file = __FILE__;            \
+      }                               \
+  }
+
+#define VAPI_DBG(fmt, ...)                                       \
+  do                                                             \
+    {                                                            \
+      VAPI_DEBUG_FILE_DEF                                        \
+      printf ("DBG:%s:%d:%s():" fmt, __file, __LINE__, __func__, \
+              ##__VA_ARGS__);                                    \
+      printf ("\n");                                             \
+      fflush (stdout);                                           \
+    }                                                            \
+  while (0);
+
+#define VAPI_ERR(fmt, ...)                                       \
+  do                                                             \
+    {                                                            \
+      VAPI_DEBUG_FILE_DEF                                        \
+      printf ("ERR:%s:%d:%s():" fmt, __file, __LINE__, __func__, \
+              ##__VA_ARGS__);                                    \
+      printf ("\n");                                             \
+      fflush (stdout);                                           \
+    }                                                            \
+  while (0);
+#else
+#define VAPI_DBG(...)
+#define VAPI_ERR(...)
+#endif
+
+#endif /* __included_vapi_debug_h__ */
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/vpp-api/vapi/vapi_internal.h b/src/vpp-api/vapi/vapi_internal.h
new file mode 100644 (file)
index 0000000..5b85788
--- /dev/null
@@ -0,0 +1,126 @@
+/*
+ *------------------------------------------------------------------
+ * 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_INTERNAL_H
+#define VAPI_INTERNAL_H
+
+#include <string.h>
+#include <vppinfra/types.h>
+
+/**
+ * @file vapi_internal.h
+ *
+ * internal vpp api C declarations
+ *
+ * This file contains internal vpp api C declarations. It's not intended to be
+ * used by the client programmer and the API defined here might change at any
+ * time..
+ */
+
+struct vapi_ctx_s;
+
+typedef struct __attribute__ ((__packed__))
+{
+  u16 _vl_msg_id;
+  u32 context;
+} vapi_type_msg_header1_t;
+
+typedef struct __attribute__ ((__packed__))
+{
+  u16 _vl_msg_id;
+  u32 client_index;
+  u32 context;
+} vapi_type_msg_header2_t;
+
+static inline void
+vapi_type_msg_header1_t_hton (vapi_type_msg_header1_t * h)
+{
+  h->_vl_msg_id = htobe16 (h->_vl_msg_id);
+}
+
+static inline void
+vapi_type_msg_header1_t_ntoh (vapi_type_msg_header1_t * h)
+{
+  h->_vl_msg_id = be16toh (h->_vl_msg_id);
+}
+
+static inline void
+vapi_type_msg_header2_t_hton (vapi_type_msg_header2_t * h)
+{
+  h->_vl_msg_id = htobe16 (h->_vl_msg_id);
+}
+
+static inline void
+vapi_type_msg_header2_t_ntoh (vapi_type_msg_header2_t * h)
+{
+  h->_vl_msg_id = be16toh (h->_vl_msg_id);
+}
+
+
+#include <vapi.h>
+
+typedef vapi_error_e (*vapi_cb_t) (struct vapi_ctx_s *, void *, vapi_error_e,
+                                  bool, void *);
+
+typedef void (*generic_swap_fn_t) (void *payload);
+
+typedef struct
+{
+  const char *name;
+  size_t name_len;
+  const char *name_with_crc;
+  size_t name_with_crc_len;
+  bool has_context;
+  size_t context_offset;
+  size_t payload_offset;
+  size_t size;
+  generic_swap_fn_t swap_to_be;
+  generic_swap_fn_t swap_to_host;
+  vapi_msg_id_t id;            /* assigned at run-time */
+} vapi_message_desc_t;
+
+typedef struct
+{
+  const char *name;
+  int payload_offset;
+  size_t size;
+  void (*swap_to_be) (void *payload);
+  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);
+int vapi_get_client_index (vapi_ctx_t ctx);
+bool vapi_is_nonblocking (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);
+u32 vapi_gen_req_context (vapi_ctx_t ctx);
+void vapi_store_request (vapi_ctx_t ctx, u32 context, bool is_dump,
+                        vapi_cb_t callback, void *callback_ctx);
+int vapi_get_payload_offset (vapi_msg_id_t id);
+void (*vapi_get_swap_to_host_func (vapi_msg_id_t id)) (void *payload);
+void (*vapi_get_swap_to_be_func (vapi_msg_id_t id)) (void *payload);
+size_t vapi_get_message_size (vapi_msg_id_t id);
+size_t vapi_get_context_offset (vapi_msg_id_t id);
+
+vapi_error_e vapi_producer_lock (vapi_ctx_t ctx);
+vapi_error_e vapi_producer_unlock (vapi_ctx_t ctx);
+
+#endif
diff --git a/src/vpp-api/vapi/vapi_json_parser.py b/src/vpp-api/vapi/vapi_json_parser.py
new file mode 100644 (file)
index 0000000..57a2238
--- /dev/null
@@ -0,0 +1,303 @@
+#!/usr/bin/env python3
+
+import json
+
+
+def msg_is_reply(name):
+    return name.endswith('_reply') or name.endswith('_details') \
+        or name.endswith('_event') or name.endswith('_counters')
+
+
+class ParseError (Exception):
+    pass
+
+
+magic_prefix = "vl_api_"
+magic_suffix = "_t"
+
+
+def remove_magic(what):
+    if what.startswith(magic_prefix) and what.endswith(magic_suffix):
+        return what[len(magic_prefix): - len(magic_suffix)]
+    return what
+
+
+class Field:
+
+    def __init__(
+            self,
+            field_name,
+            field_type,
+            array_len=None,
+            nelem_field=None):
+        self.name = field_name
+        self.type = field_type
+        self.len = array_len
+        self.nelem_field = nelem_field
+
+    def __str__(self):
+        if self.len is None:
+            return "name: %s, type: %s" % (self.name, self.type)
+        elif self.len > 0:
+            return "name: %s, type: %s, length: %s" % (self.name, self.type,
+                                                       self.len)
+        else:
+            return ("name: %s, type: %s, variable length stored in: %s" %
+                    (self.name, self.type, self.nelem_field))
+
+
+class Type:
+    def __init__(self, name):
+        self.name = name
+
+
+class SimpleType (Type):
+
+    def __init__(self, name):
+        super().__init__(name)
+
+    def __str__(self):
+        return self.name
+
+
+def get_msg_header_defs(struct_type_class, field_class, typedict):
+    return [
+        struct_type_class(['msg_header1_t',
+                           ['u16', '_vl_msg_id'],
+                           ['u32', 'context'],
+                           ],
+                          typedict, field_class
+                          ),
+        struct_type_class(['msg_header2_t',
+                           ['u16', '_vl_msg_id'],
+                           ['u32', 'client_index'],
+                           ['u32', 'context'],
+                           ],
+                          typedict, field_class
+                          ),
+    ]
+
+
+class Struct:
+
+    def __init__(self, name, fields):
+        self.name = name
+        self.fields = fields
+        self.field_names = [n.name for n in self.fields]
+
+
+class Message:
+
+    def __init__(self, logger, definition, typedict,
+                 struct_type_class, simple_type_class, field_class):
+        self.logger = logger
+        m = definition
+        logger.debug("Parsing message definition `%s'" % m)
+        name = m[0]
+        self.name = name
+        logger.debug("Message name is `%s'" % name)
+        ignore = True
+        self.header = None
+        fields = []
+        for header in get_msg_header_defs(struct_type_class, field_class,
+                                          typedict):
+            logger.debug("Probing header `%s'" % header.name)
+            if header.is_part_of_def(m[1:]):
+                self.header = header
+                logger.debug("Found header `%s'" % header.name)
+                fields.append(field_class(field_name='header',
+                                          field_type=self.header))
+                ignore = False
+                break
+        if ignore and not msg_is_reply(name):
+            raise ParseError("While parsing message `%s': could not find all "
+                             "common header fields" % name)
+        for field in m[1:]:
+            if len(field) == 1 and 'crc' in field:
+                self.crc = field['crc']
+                logger.debug("Found CRC `%s'" % self.crc)
+                continue
+            else:
+                field_type = field[0]
+                if field_type in typedict:
+                    field_type = typedict[field_type]
+                else:
+                    field_type = typedict[remove_magic(field_type)]
+                if len(field) == 2:
+                    if self.header is not None and\
+                            self.header.has_field(field[1]):
+                        continue
+                    p = field_class(field_name=field[1],
+                                    field_type=field_type)
+                elif len(field) == 3:
+                    if field[2] == 0:
+                        raise ParseError(
+                            "While parsing message `%s': variable length "
+                            "array `%s' doesn't have reference to member "
+                            "containing the actual length" % (
+                                name, field[1]))
+                    p = field_class(
+                        field_name=field[1],
+                        field_type=field_type,
+                        array_len=field[2])
+                elif len(field) == 4:
+                    nelem_field = None
+                    for f in fields:
+                        if f.name == field[3]:
+                            nelem_field = f
+                    if nelem_field is None:
+                        raise ParseError(
+                            "While parsing message `%s': couldn't find "
+                            "variable length array `%s' member containing "
+                            "the actual length `%s'" % (
+                                name, field[1], field[3]))
+                    p = field_class(
+                        field_name=field[1],
+                        field_type=field_type,
+                        array_len=field[2],
+                        nelem_field=nelem_field)
+                else:
+                    raise Exception("Don't know how to parse message "
+                                    "definition for message `%s': `%s'" %
+                                    (m, m[1:]))
+                logger.debug("Parsed field `%s'" % p)
+                fields.append(p)
+        self.fields = fields
+
+    def is_dump(self):
+        return self.name.endswith('_dump')
+
+    def is_reply(self):
+        return msg_is_reply(self.name)
+
+
+class StructType (Type, Struct):
+
+    def __init__(self, definition, typedict, field_class):
+        t = definition
+        name = t[0]
+        fields = []
+        for field in t[1:]:
+            if len(field) == 1 and 'crc' in field:
+                self.crc = field['crc']
+                continue
+            elif len(field) == 2:
+                p = field_class(field_name=field[1],
+                                field_type=typedict[field[0]])
+            elif len(field) == 3:
+                if field[2] == 0:
+                    raise ParseError("While parsing type `%s': array `%s' has "
+                                     "variable length" % (name, field[1]))
+                p = field_class(field_name=field[1],
+                                field_type=typedict[field[0]],
+                                array_len=field[2])
+            else:
+                raise ParseError(
+                    "Don't know how to parse type definition for "
+                    "type `%s': `%s'" % (t, t[1:]))
+            fields.append(p)
+        Type.__init__(self, name)
+        Struct.__init__(self, name, fields)
+
+    def has_field(self, name):
+        return name in self.field_names
+
+    def is_part_of_def(self, definition):
+        for idx in range(len(self.fields)):
+            field = definition[idx]
+            p = self.fields[idx]
+            if field[1] != p.name:
+                return False
+            if field[0] != p.type.name:
+                raise ParseError(
+                    "Unexpected field type `%s' (should be `%s'), "
+                    "while parsing msg/def/field `%s/%s/%s'" %
+                    (field[0], p.type, p.name, definition, field))
+        return True
+
+
+class JsonParser:
+    def __init__(self, logger, files, simple_type_class=SimpleType,
+                 struct_type_class=StructType, field_class=Field,
+                 message_class=Message):
+        self.messages = {}
+        self.types = {
+            x: simple_type_class(x) for x in [
+                'i8', 'i16', 'i32', 'i64',
+                'u8', 'u16', 'u32', 'u64',
+                'f64'
+            ]
+        }
+
+        self.simple_type_class = simple_type_class
+        self.struct_type_class = struct_type_class
+        self.field_class = field_class
+        self.message_class = message_class
+
+        self.exceptions = []
+        self.json_files = []
+        self.types_by_json = {}
+        self.messages_by_json = {}
+        self.logger = logger
+        for f in files:
+            self.parse_json_file(f)
+        self.finalize_parsing()
+
+    def parse_json_file(self, path):
+        self.logger.info("Parsing json api file: `%s'" % path)
+        self.json_files.append(path)
+        self.types_by_json[path] = {}
+        self.messages_by_json[path] = {}
+        with open(path) as f:
+            j = json.load(f)
+            for t in j['types']:
+                try:
+                    type_ = self.struct_type_class(t, self.types,
+                                                   self.field_class)
+                    if type_.name in self.types:
+                        raise ParseError("Duplicate type `%s'" % type_.name)
+                except ParseError as e:
+                    self.exceptions.append(e)
+                    continue
+                self.types[type_.name] = type_
+                self.types_by_json[path][type_.name] = type_
+            for m in j['messages']:
+                try:
+                    msg = self.message_class(self.logger, m, self.types,
+                                             self.struct_type_class,
+                                             self.simple_type_class,
+                                             self.field_class)
+                    if msg.name in self.messages:
+                        raise ParseError("Duplicate message `%s'" % msg.name)
+                except ParseError as e:
+                    self.exceptions.append(e)
+                    continue
+                self.messages[msg.name] = msg
+                self.messages_by_json[path][msg.name] = msg
+
+    def get_reply(self, message):
+        if self.messages[message].is_dump():
+            return self.messages["%s_details" % message[:-len("_dump")]]
+        return self.messages["%s_reply" % message]
+
+    def finalize_parsing(self):
+        if len(self.messages) == 0:
+            for e in self.exceptions:
+                self.logger.error(e)
+            raise Exception("No messages parsed.")
+        for jn, j in self.messages_by_json.items():
+            remove = []
+            for n, m in j.items():
+                try:
+                    if not m.is_reply():
+                        try:
+                            m.reply = self.get_reply(n)
+                        except:
+                            raise ParseError(
+                                "Cannot find reply to message `%s'" % n)
+                except ParseError as e:
+                    self.exceptions.append(e)
+                    remove.append(n)
+
+            self.messages_by_json[jn] = {
+                k: v for k, v in j.items() if k not in remove}
index 72b4dac..132ebee 100644 (file)
@@ -107,7 +107,11 @@ sanity: verify-no-running-vpp
                 echo \"*******************************************************************\" &&\
                 false)"
 
-test: verify-python-path $(PAPI_INSTALL_DONE) sanity reset 
+.PHONY: ext
+ext:
+       make -C ext
+
+test: verify-python-path $(PAPI_INSTALL_DONE) ext sanity reset
        $(call retest-func)
 
 retest: verify-python-path sanity reset
diff --git a/test/ext/Makefile b/test/ext/Makefile
new file mode 100644 (file)
index 0000000..4a45fef
--- /dev/null
@@ -0,0 +1,17 @@
+BINDIR = $(BR)/vapi_test/
+BIN = $(addprefix $(BINDIR), vapi_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/
+
+all: $(BIN)
+
+$(BINDIR):
+       mkdir -p $(BINDIR)
+
+SRC = vapi_test.c
+
+$(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)
+
+clean:
+       rm -rf $(BINDIR)
diff --git a/test/ext/vapi_test.c b/test/ext/vapi_test.c
new file mode 100644 (file)
index 0000000..eca6be7
--- /dev/null
@@ -0,0 +1,1152 @@
+/*
+ *------------------------------------------------------------------
+ * 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 <stdio.h>
+#include <endian.h>
+#include <stdlib.h>
+#include <unistd.h>
+#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>
+
+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;
+
+static char *app_name = NULL;
+static char *api_prefix = NULL;
+static const int max_outstanding_requests = 64;
+static const int response_queue_size = 32;
+
+START_TEST (test_invalid_values)
+{
+  vapi_ctx_t ctx;
+  vapi_error_e rv = vapi_ctx_alloc (&ctx);
+  ck_assert_int_eq (VAPI_OK, rv);
+  vapi_msg_show_version *sv = vapi_alloc_show_version (ctx);
+  ck_assert_ptr_eq (NULL, sv);
+  rv = vapi_send (ctx, sv);
+  ck_assert_int_eq (VAPI_EINVAL, rv);
+  rv = vapi_connect (ctx, app_name, api_prefix, max_outstanding_requests,
+                    response_queue_size, VAPI_MODE_BLOCKING);
+  ck_assert_int_eq (VAPI_OK, rv);
+  rv = vapi_send (ctx, NULL);
+  ck_assert_int_eq (VAPI_EINVAL, rv);
+  rv = vapi_send (NULL, NULL);
+  ck_assert_int_eq (VAPI_EINVAL, rv);
+  rv = vapi_recv (NULL, NULL, NULL);
+  ck_assert_int_eq (VAPI_EINVAL, rv);
+  rv = vapi_recv (ctx, NULL, NULL);
+  ck_assert_int_eq (VAPI_EINVAL, rv);
+  vapi_msg_show_version_reply *reply;
+  rv = vapi_recv (ctx, (void **) &reply, NULL);
+  ck_assert_int_eq (VAPI_EINVAL, rv);
+  rv = vapi_disconnect (ctx);
+  ck_assert_int_eq (VAPI_OK, rv);
+  vapi_ctx_free (ctx);
+}
+
+END_TEST;
+
+START_TEST (test_hton_1)
+{
+  const u16 _vl_msg_id = 1;
+  vapi_type_msg_header1_t h;
+  h._vl_msg_id = _vl_msg_id;
+  vapi_type_msg_header1_t_hton (&h);
+  ck_assert_int_eq (be16toh (h._vl_msg_id), _vl_msg_id);
+}
+
+END_TEST;
+
+START_TEST (test_hton_2)
+{
+  const u16 _vl_msg_id = 1;
+  const u32 client_index = 3;
+  vapi_type_msg_header2_t h;
+  h._vl_msg_id = _vl_msg_id;
+  h.client_index = client_index;
+  vapi_type_msg_header2_t_hton (&h);
+  ck_assert_int_eq (be16toh (h._vl_msg_id), _vl_msg_id);
+  ck_assert_int_eq (h.client_index, client_index);
+}
+
+END_TEST;
+
+START_TEST (test_hton_3)
+{
+  const size_t data_size = 10;
+  vapi_msg_vnet_interface_combined_counters *m =
+    malloc (sizeof (vapi_msg_vnet_interface_combined_counters) +
+           data_size * sizeof (vapi_type_vlib_counter));
+  ck_assert_ptr_ne (NULL, m);
+  vapi_payload_vnet_interface_combined_counters *p = &m->payload;
+  const u16 _vl_msg_id = 1;
+  p->_vl_msg_id = _vl_msg_id;
+  const u32 first_sw_if_index = 2;
+  p->first_sw_if_index = first_sw_if_index;
+  p->count = data_size;
+  const u64 packets = 1234;
+  const u64 bytes = 2345;
+  int i;
+  for (i = 0; i < data_size; ++i)
+    {
+      p->data[i].packets = packets;
+      p->data[i].bytes = bytes;
+    }
+  vapi_msg_vnet_interface_combined_counters_hton (m);
+  ck_assert_int_eq (_vl_msg_id, be16toh (p->_vl_msg_id));
+  ck_assert_int_eq (first_sw_if_index, be32toh (p->first_sw_if_index));
+  ck_assert_int_eq (data_size, be32toh (p->count));
+  for (i = 0; i < data_size; ++i)
+    {
+      ck_assert_int_eq (packets, be64toh (p->data[i].packets));
+      ck_assert_int_eq (bytes, be64toh (p->data[i].bytes));
+    }
+  free (p);
+}
+
+END_TEST;
+
+#define verify_hton_swap(expr, value)           \
+  if (4 == sizeof (expr))                       \
+    {                                           \
+      ck_assert_int_eq (expr, htobe32 (value)); \
+    }                                           \
+  else if (2 == sizeof (expr))                  \
+    {                                           \
+      ck_assert_int_eq (expr, htobe16 (value)); \
+    }                                           \
+  else                                          \
+    {                                           \
+      ck_assert_int_eq (expr, value);           \
+    }
+
+START_TEST (test_hton_4)
+{
+  const int vla_count = 3;
+  char x[sizeof (vapi_msg_bridge_domain_details) +
+        vla_count * sizeof (vapi_type_bridge_domain_sw_if)];
+  vapi_msg_bridge_domain_details *d = (void *) x;
+  int cnt = 1;
+  d->header._vl_msg_id = cnt++;
+  d->header.context = cnt++;
+  d->payload.bd_id = cnt++;
+  d->payload.flood = cnt++;
+  d->payload.uu_flood = cnt++;
+  d->payload.forward = cnt++;
+  d->payload.learn = cnt++;
+  d->payload.arp_term = cnt++;
+  d->payload.mac_age = cnt++;
+  d->payload.bvi_sw_if_index = cnt++;
+  d->payload.n_sw_ifs = vla_count;
+  int i;
+  for (i = 0; i < vla_count; ++i)
+    {
+      vapi_type_bridge_domain_sw_if *det = &d->payload.sw_if_details[i];
+      det->context = cnt++;
+      det->sw_if_index = cnt++;
+      det->shg = cnt++;
+    }
+  ck_assert_int_eq (sizeof (x), vapi_calc_bridge_domain_details_msg_size (d));
+  vapi_msg_bridge_domain_details_hton (d);
+  int tmp = 1;
+  verify_hton_swap (d->header._vl_msg_id, tmp);
+  ++tmp;
+  ck_assert_int_eq (d->header.context, tmp);
+  ++tmp;
+  verify_hton_swap (d->payload.bd_id, tmp);
+  ++tmp;
+  verify_hton_swap (d->payload.flood, tmp);
+  ++tmp;
+  verify_hton_swap (d->payload.uu_flood, tmp);
+  ++tmp;
+  verify_hton_swap (d->payload.forward, tmp);
+  ++tmp;
+  verify_hton_swap (d->payload.learn, tmp);
+  ++tmp;
+  verify_hton_swap (d->payload.arp_term, tmp);
+  ++tmp;
+  verify_hton_swap (d->payload.mac_age, tmp);
+  ++tmp;
+  verify_hton_swap (d->payload.bvi_sw_if_index, tmp);
+  ++tmp;
+  ck_assert_int_eq (d->payload.n_sw_ifs, htobe32 (vla_count));
+  for (i = 0; i < vla_count; ++i)
+    {
+      vapi_type_bridge_domain_sw_if *det = &d->payload.sw_if_details[i];
+      verify_hton_swap (det->context, tmp);
+      ++tmp;
+      verify_hton_swap (det->sw_if_index, tmp);
+      ++tmp;
+      verify_hton_swap (det->shg, tmp);
+      ++tmp;
+    }
+  vapi_msg_bridge_domain_details_ntoh (d);
+  tmp = 1;
+  ck_assert_int_eq (d->header._vl_msg_id, tmp);
+  ++tmp;
+  ck_assert_int_eq (d->header.context, tmp);
+  ++tmp;
+  ck_assert_int_eq (d->payload.bd_id, tmp);
+  ++tmp;
+  ck_assert_int_eq (d->payload.flood, tmp);
+  ++tmp;
+  ck_assert_int_eq (d->payload.uu_flood, tmp);
+  ++tmp;
+  ck_assert_int_eq (d->payload.forward, tmp);
+  ++tmp;
+  ck_assert_int_eq (d->payload.learn, tmp);
+  ++tmp;
+  ck_assert_int_eq (d->payload.arp_term, tmp);
+  ++tmp;
+  ck_assert_int_eq (d->payload.mac_age, tmp);
+  ++tmp;
+  ck_assert_int_eq (d->payload.bvi_sw_if_index, tmp);
+  ++tmp;
+  ck_assert_int_eq (d->payload.n_sw_ifs, vla_count);
+  for (i = 0; i < vla_count; ++i)
+    {
+      vapi_type_bridge_domain_sw_if *det = &d->payload.sw_if_details[i];
+      ck_assert_int_eq (det->context, tmp);
+      ++tmp;
+      ck_assert_int_eq (det->sw_if_index, tmp);
+      ++tmp;
+      ck_assert_int_eq (det->shg, tmp);
+      ++tmp;
+    }
+  ck_assert_int_eq (sizeof (x), vapi_calc_bridge_domain_details_msg_size (d));
+}
+
+END_TEST;
+
+START_TEST (test_ntoh_1)
+{
+  const u16 _vl_msg_id = 1;
+  vapi_type_msg_header1_t h;
+  h._vl_msg_id = _vl_msg_id;
+  vapi_type_msg_header1_t_ntoh (&h);
+  ck_assert_int_eq (htobe16 (h._vl_msg_id), _vl_msg_id);
+}
+
+END_TEST;
+
+START_TEST (test_ntoh_2)
+{
+  const u16 _vl_msg_id = 1;
+  const u32 client_index = 3;
+  vapi_type_msg_header2_t h;
+  h._vl_msg_id = _vl_msg_id;
+  h.client_index = client_index;
+  vapi_type_msg_header2_t_ntoh (&h);
+  ck_assert_int_eq (htobe16 (h._vl_msg_id), _vl_msg_id);
+  ck_assert_int_eq (h.client_index, client_index);
+}
+
+END_TEST;
+
+START_TEST (test_ntoh_3)
+{
+  const size_t data_size = 10;
+  vapi_msg_vnet_interface_combined_counters *m =
+    malloc (sizeof (vapi_msg_vnet_interface_combined_counters) +
+           data_size * sizeof (vapi_type_vlib_counter));
+  ck_assert_ptr_ne (NULL, m);
+  vapi_payload_vnet_interface_combined_counters *p = &m->payload;
+  const u16 _vl_msg_id = 1;
+  p->_vl_msg_id = _vl_msg_id;
+  const u32 first_sw_if_index = 2;
+  p->first_sw_if_index = first_sw_if_index;
+  const size_t be_data_size = htobe32 (data_size);
+  p->count = be_data_size;
+  const u64 packets = 1234;
+  const u64 bytes = 2345;
+  int i;
+  for (i = 0; i < data_size; ++i)
+    {
+      p->data[i].packets = packets;
+      p->data[i].bytes = bytes;
+    }
+  vapi_msg_vnet_interface_combined_counters_ntoh (m);
+  ck_assert_int_eq (_vl_msg_id, be16toh (p->_vl_msg_id));
+  ck_assert_int_eq (first_sw_if_index, be32toh (p->first_sw_if_index));
+  ck_assert_int_eq (be_data_size, be32toh (p->count));
+  for (i = 0; i < data_size; ++i)
+    {
+      ck_assert_int_eq (packets, htobe64 (p->data[i].packets));
+      ck_assert_int_eq (bytes, htobe64 (p->data[i].bytes));
+    }
+  free (p);
+}
+
+END_TEST;
+
+#define verify_ntoh_swap(expr, value)           \
+  if (4 == sizeof (expr))                       \
+    {                                           \
+      ck_assert_int_eq (expr, be32toh (value)); \
+    }                                           \
+  else if (2 == sizeof (expr))                  \
+    {                                           \
+      ck_assert_int_eq (expr, be16toh (value)); \
+    }                                           \
+  else                                          \
+    {                                           \
+      ck_assert_int_eq (expr, value);           \
+    }
+
+START_TEST (test_ntoh_4)
+{
+  const int vla_count = 3;
+  char x[sizeof (vapi_msg_bridge_domain_details) +
+        vla_count * sizeof (vapi_type_bridge_domain_sw_if)];
+  vapi_msg_bridge_domain_details *d = (void *) x;
+  int cnt = 1;
+  d->header._vl_msg_id = cnt++;
+  d->header.context = cnt++;
+  d->payload.bd_id = cnt++;
+  d->payload.flood = cnt++;
+  d->payload.uu_flood = cnt++;
+  d->payload.forward = cnt++;
+  d->payload.learn = cnt++;
+  d->payload.arp_term = cnt++;
+  d->payload.mac_age = cnt++;
+  d->payload.bvi_sw_if_index = cnt++;
+  d->payload.n_sw_ifs = htobe32 (vla_count);
+  int i;
+  for (i = 0; i < vla_count; ++i)
+    {
+      vapi_type_bridge_domain_sw_if *det = &d->payload.sw_if_details[i];
+      det->context = cnt++;
+      det->sw_if_index = cnt++;
+      det->shg = cnt++;
+    }
+  vapi_msg_bridge_domain_details_ntoh (d);
+  ck_assert_int_eq (sizeof (x), vapi_calc_bridge_domain_details_msg_size (d));
+  int tmp = 1;
+  verify_ntoh_swap (d->header._vl_msg_id, tmp);
+  ++tmp;
+  ck_assert_int_eq (d->header.context, tmp);
+  ++tmp;
+  verify_ntoh_swap (d->payload.bd_id, tmp);
+  ++tmp;
+  verify_ntoh_swap (d->payload.flood, tmp);
+  ++tmp;
+  verify_ntoh_swap (d->payload.uu_flood, tmp);
+  ++tmp;
+  verify_ntoh_swap (d->payload.forward, tmp);
+  ++tmp;
+  verify_ntoh_swap (d->payload.learn, tmp);
+  ++tmp;
+  verify_ntoh_swap (d->payload.arp_term, tmp);
+  ++tmp;
+  verify_ntoh_swap (d->payload.mac_age, tmp);
+  ++tmp;
+  verify_ntoh_swap (d->payload.bvi_sw_if_index, tmp);
+  ++tmp;
+  ck_assert_int_eq (d->payload.n_sw_ifs, vla_count);
+  for (i = 0; i < vla_count; ++i)
+    {
+      vapi_type_bridge_domain_sw_if *det = &d->payload.sw_if_details[i];
+      verify_ntoh_swap (det->context, tmp);
+      ++tmp;
+      verify_ntoh_swap (det->sw_if_index, tmp);
+      ++tmp;
+      verify_ntoh_swap (det->shg, tmp);
+      ++tmp;
+    }
+  vapi_msg_bridge_domain_details_hton (d);
+  tmp = 1;
+  ck_assert_int_eq (d->header._vl_msg_id, tmp);
+  ++tmp;
+  ck_assert_int_eq (d->header.context, tmp);
+  ++tmp;
+  ck_assert_int_eq (d->payload.bd_id, tmp);
+  ++tmp;
+  ck_assert_int_eq (d->payload.flood, tmp);
+  ++tmp;
+  ck_assert_int_eq (d->payload.uu_flood, tmp);
+  ++tmp;
+  ck_assert_int_eq (d->payload.forward, tmp);
+  ++tmp;
+  ck_assert_int_eq (d->payload.learn, tmp);
+  ++tmp;
+  ck_assert_int_eq (d->payload.arp_term, tmp);
+  ++tmp;
+  ck_assert_int_eq (d->payload.mac_age, tmp);
+  ++tmp;
+  ck_assert_int_eq (d->payload.bvi_sw_if_index, tmp);
+  ++tmp;
+  ck_assert_int_eq (d->payload.n_sw_ifs, htobe32 (vla_count));
+  for (i = 0; i < vla_count; ++i)
+    {
+      vapi_type_bridge_domain_sw_if *det = &d->payload.sw_if_details[i];
+      ck_assert_int_eq (det->context, tmp);
+      ++tmp;
+      ck_assert_int_eq (det->sw_if_index, tmp);
+      ++tmp;
+      ck_assert_int_eq (det->shg, tmp);
+      ++tmp;
+    }
+}
+
+END_TEST;
+
+vapi_error_e
+show_version_cb (vapi_ctx_t ctx, void *caller_ctx,
+                vapi_error_e rv, bool is_last,
+                vapi_payload_show_version_reply * p)
+{
+  ck_assert_int_eq (VAPI_OK, rv);
+  ck_assert_int_eq (true, is_last);
+  ck_assert_str_eq ("vpe", (char *) p->program);
+  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);
+  ++*(int *) caller_ctx;
+  return VAPI_OK;
+}
+
+typedef struct
+{
+  int called;
+  int expected_retval;
+  u32 *sw_if_index_storage;
+} test_create_loopback_ctx_t;
+
+vapi_error_e
+loopback_create_cb (vapi_ctx_t ctx, void *caller_ctx,
+                   vapi_error_e rv, bool is_last,
+                   vapi_payload_create_loopback_reply * p)
+{
+  test_create_loopback_ctx_t *clc = caller_ctx;
+  ck_assert_int_eq (clc->expected_retval, p->retval);
+  *clc->sw_if_index_storage = p->sw_if_index;
+  ++clc->called;
+  return VAPI_OK;
+}
+
+typedef struct
+{
+  int called;
+  int expected_retval;
+  u32 *sw_if_index_storage;
+} test_delete_loopback_ctx_t;
+
+vapi_error_e
+loopback_delete_cb (vapi_ctx_t ctx, void *caller_ctx,
+                   vapi_error_e rv, bool is_last,
+                   vapi_payload_delete_loopback_reply * p)
+{
+  test_delete_loopback_ctx_t *dlc = caller_ctx;
+  ck_assert_int_eq (dlc->expected_retval, p->retval);
+  ++dlc->called;
+  return VAPI_OK;
+}
+
+START_TEST (test_connect)
+{
+  vapi_ctx_t ctx;
+  vapi_error_e rv = vapi_ctx_alloc (&ctx);
+  ck_assert_int_eq (VAPI_OK, rv);
+  rv = vapi_connect (ctx, app_name, api_prefix, max_outstanding_requests,
+                    response_queue_size, VAPI_MODE_BLOCKING);
+  ck_assert_int_eq (VAPI_OK, rv);
+  rv = vapi_disconnect (ctx);
+  ck_assert_int_eq (VAPI_OK, rv);
+  vapi_ctx_free (ctx);
+}
+
+END_TEST;
+
+vapi_ctx_t ctx;
+
+void
+setup_blocking (void)
+{
+  vapi_error_e rv = vapi_ctx_alloc (&ctx);
+  ck_assert_int_eq (VAPI_OK, rv);
+  rv = vapi_connect (ctx, app_name, api_prefix, max_outstanding_requests,
+                    response_queue_size, VAPI_MODE_BLOCKING);
+  ck_assert_int_eq (VAPI_OK, rv);
+}
+
+void
+setup_nonblocking (void)
+{
+  vapi_error_e rv = vapi_ctx_alloc (&ctx);
+  ck_assert_int_eq (VAPI_OK, rv);
+  rv = vapi_connect (ctx, app_name, api_prefix, max_outstanding_requests,
+                    response_queue_size, VAPI_MODE_NONBLOCKING);
+  ck_assert_int_eq (VAPI_OK, rv);
+}
+
+void
+teardown (void)
+{
+  vapi_disconnect (ctx);
+  vapi_ctx_free (ctx);
+}
+
+START_TEST (test_show_version_1)
+{
+  printf ("--- Basic show version message - reply test ---\n");
+  vapi_msg_show_version *sv = vapi_alloc_show_version (ctx);
+  ck_assert_ptr_ne (NULL, sv);
+  vapi_msg_show_version_hton (sv);
+  vapi_error_e rv = vapi_send (ctx, sv);
+  ck_assert_int_eq (VAPI_OK, rv);
+  vapi_msg_show_version_reply *resp;
+  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);
+  vapi_msg_free (ctx, resp);
+}
+
+END_TEST;
+
+START_TEST (test_show_version_2)
+{
+  int called = 0;
+  printf ("--- Show version via blocking callback API ---\n");
+  const int attempts = response_queue_size * 4;
+  int i = 0;
+  for (i = 0; i < attempts; ++i)
+    {
+      vapi_msg_show_version *sv = vapi_alloc_show_version (ctx);
+      ck_assert_ptr_ne (NULL, sv);
+      vapi_error_e rv = vapi_show_version (ctx, sv, show_version_cb, &called);
+      ck_assert_int_eq (VAPI_OK, rv);
+    }
+  ck_assert_int_eq (attempts, called);
+}
+
+END_TEST;
+
+typedef struct
+{
+  bool last_called;
+  size_t num_ifs;
+  u32 *sw_if_indexes;
+  bool *seen;
+  int called;
+} sw_interface_dump_ctx;
+
+vapi_error_e
+sw_interface_dump_cb (struct vapi_ctx_s *ctx, void *callback_ctx,
+                     vapi_error_e rv, bool is_last,
+                     vapi_payload_sw_interface_details * reply)
+{
+  sw_interface_dump_ctx *dctx = callback_ctx;
+  ck_assert_int_eq (false, dctx->last_called);
+  if (is_last)
+    {
+      ck_assert (NULL == reply);
+      dctx->last_called = true;
+    }
+  else
+    {
+      ck_assert (reply);
+      printf ("Interface dump entry: [%u]: %s\n", reply->sw_if_index,
+             reply->interface_name);
+      size_t i = 0;
+      for (i = 0; i < dctx->num_ifs; ++i)
+       {
+         if (dctx->sw_if_indexes[i] == reply->sw_if_index)
+           {
+             ck_assert_int_eq (false, dctx->seen[i]);
+             dctx->seen[i] = true;
+           }
+       }
+    }
+  ++dctx->called;
+  return VAPI_OK;
+}
+
+START_TEST (test_loopbacks_1)
+{
+  printf ("--- Create/delete loopbacks using blocking API ---\n");
+  const size_t 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));
+  test_create_loopback_ctx_t clcs[num_ifs];
+  memset (&clcs, 0, sizeof (clcs));
+  test_delete_loopback_ctx_t dlcs[num_ifs];
+  memset (&dlcs, 0, sizeof (dlcs));
+  int i;
+  for (i = 0; i < num_ifs; ++i)
+    {
+      memcpy (&mac_addresses[i], "\1\2\3\4\5\6", 6);
+      mac_addresses[i][5] = i;
+      clcs[i].sw_if_index_storage = &sw_if_indexes[i];
+    }
+  for (i = 0; i < num_ifs; ++i)
+    {
+      vapi_msg_create_loopback *cl = vapi_alloc_create_loopback (ctx);
+      memcpy (cl->payload.mac_address, mac_addresses[i],
+             sizeof (cl->payload.mac_address));
+      vapi_error_e rv =
+       vapi_create_loopback (ctx, cl, loopback_create_cb, &clcs[i]);
+      ck_assert_int_eq (VAPI_OK, rv);
+    }
+  for (i = 0; i < num_ifs; ++i)
+    {
+      ck_assert_int_eq (1, clcs[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],
+             sw_if_indexes[i]);
+    }
+  bool seen[num_ifs];
+  sw_interface_dump_ctx dctx = { false, num_ifs, sw_if_indexes, seen, 0 };
+  vapi_msg_sw_interface_dump *dump;
+  vapi_error_e rv;
+  const int attempts = response_queue_size * 4;
+  for (i = 0; i < attempts; ++i)
+    {
+      dctx.last_called = false;
+      memset (&seen, 0, sizeof (seen));
+      dump = vapi_alloc_sw_interface_dump (ctx);
+      dump->payload.name_filter_valid = 0;
+      memset (dump->payload.name_filter, 0,
+             sizeof (dump->payload.name_filter));
+      while (VAPI_EAGAIN ==
+            (rv =
+             vapi_sw_interface_dump (ctx, dump, sw_interface_dump_cb,
+                                     &dctx)))
+       ;
+      ck_assert_int_eq (true, dctx.last_called);
+      int j = 0;
+      for (j = 0; j < num_ifs; ++j)
+       {
+         ck_assert_int_eq (true, seen[j]);
+       }
+    }
+  memset (&seen, 0, sizeof (seen));
+  for (i = 0; i < num_ifs; ++i)
+    {
+      vapi_msg_delete_loopback *dl = vapi_alloc_delete_loopback (ctx);
+      dl->payload.sw_if_index = sw_if_indexes[i];
+      vapi_error_e rv =
+       vapi_delete_loopback (ctx, dl, loopback_delete_cb, &dlcs[i]);
+      ck_assert_int_eq (VAPI_OK, rv);
+    }
+  for (i = 0; i < num_ifs; ++i)
+    {
+      ck_assert_int_eq (1, dlcs[i].called);
+      printf ("Deleted loopback with sw_if_index %u\n", sw_if_indexes[i]);
+    }
+  dctx.last_called = false;
+  memset (&seen, 0, sizeof (seen));
+  dump = vapi_alloc_sw_interface_dump (ctx);
+  dump->payload.name_filter_valid = 0;
+  memset (dump->payload.name_filter, 0, sizeof (dump->payload.name_filter));
+  while (VAPI_EAGAIN ==
+        (rv =
+         vapi_sw_interface_dump (ctx, dump, sw_interface_dump_cb, &dctx)))
+    ;
+  ck_assert_int_eq (true, dctx.last_called);
+  for (i = 0; i < num_ifs; ++i)
+    {
+      ck_assert_int_eq (false, seen[i]);
+    }
+}
+
+END_TEST;
+
+START_TEST (test_show_version_3)
+{
+  printf ("--- Show version via async callback ---\n");
+  int called = 0;
+  vapi_error_e rv;
+  vapi_msg_show_version *sv = vapi_alloc_show_version (ctx);
+  ck_assert_ptr_ne (NULL, sv);
+  while (VAPI_EAGAIN ==
+        (rv = vapi_show_version (ctx, sv, show_version_cb, &called)))
+    ;
+  ck_assert_int_eq (VAPI_OK, rv);
+  ck_assert_int_eq (0, called);
+  rv = vapi_dispatch (ctx);
+  ck_assert_int_eq (VAPI_OK, rv);
+  ck_assert_int_eq (1, called);
+  called = 0;
+  rv = vapi_dispatch (ctx);
+  ck_assert_int_eq (VAPI_OK, rv);
+  ck_assert_int_eq (0, called);
+}
+
+END_TEST;
+
+START_TEST (test_show_version_4)
+{
+  printf ("--- Show version via async callback - multiple messages ---\n");
+  vapi_error_e rv;
+  const size_t num_req = 5;
+  int contexts[num_req];
+  memset (contexts, 0, sizeof (contexts));
+  int i;
+  for (i = 0; i < num_req; ++i)
+    {
+      vapi_msg_show_version *sv = vapi_alloc_show_version (ctx);
+      ck_assert_ptr_ne (NULL, sv);
+      while (VAPI_EAGAIN ==
+            (rv =
+             vapi_show_version (ctx, sv, show_version_cb, &contexts[i])))
+       ;
+      ck_assert_int_eq (VAPI_OK, rv);
+      int j;
+      for (j = 0; j < num_req; ++j)
+       {
+         ck_assert_int_eq (0, contexts[j]);
+       }
+    }
+  rv = vapi_dispatch (ctx);
+  ck_assert_int_eq (VAPI_OK, rv);
+  for (i = 0; i < num_req; ++i)
+    {
+      ck_assert_int_eq (1, contexts[i]);
+    }
+  memset (contexts, 0, sizeof (contexts));
+  rv = vapi_dispatch (ctx);
+  ck_assert_int_eq (VAPI_OK, rv);
+  for (i = 0; i < num_req; ++i)
+    {
+      ck_assert_int_eq (0, contexts[i]);
+    }
+}
+
+END_TEST;
+
+START_TEST (test_loopbacks_2)
+{
+  printf ("--- Create/delete loopbacks using non-blocking API ---\n");
+  vapi_error_e rv;
+  const size_t 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));
+  test_create_loopback_ctx_t clcs[num_ifs];
+  memset (&clcs, 0, sizeof (clcs));
+  test_delete_loopback_ctx_t dlcs[num_ifs];
+  memset (&dlcs, 0, sizeof (dlcs));
+  int i;
+  for (i = 0; i < num_ifs; ++i)
+    {
+      memcpy (&mac_addresses[i], "\1\2\3\4\5\6", 6);
+      mac_addresses[i][5] = i;
+      clcs[i].sw_if_index_storage = &sw_if_indexes[i];
+    }
+  for (i = 0; i < num_ifs; ++i)
+    {
+      vapi_msg_create_loopback *cl = vapi_alloc_create_loopback (ctx);
+      memcpy (cl->payload.mac_address, mac_addresses[i],
+             sizeof (cl->payload.mac_address));
+      while (VAPI_EAGAIN ==
+            (rv =
+             vapi_create_loopback (ctx, cl, loopback_create_cb, &clcs[i])))
+       ;
+      ck_assert_int_eq (VAPI_OK, rv);
+    }
+  rv = vapi_dispatch (ctx);
+  ck_assert_int_eq (VAPI_OK, rv);
+  for (i = 0; i < num_ifs; ++i)
+    {
+      ck_assert_int_eq (1, clcs[i].called);
+      printf ("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]);
+    }
+  bool seen[num_ifs];
+  memset (&seen, 0, sizeof (seen));
+  sw_interface_dump_ctx dctx = { false, num_ifs, sw_if_indexes, seen, 0 };
+  vapi_msg_sw_interface_dump *dump = vapi_alloc_sw_interface_dump (ctx);
+  dump->payload.name_filter_valid = 0;
+  memset (dump->payload.name_filter, 0, sizeof (dump->payload.name_filter));
+  while (VAPI_EAGAIN ==
+        (rv =
+         vapi_sw_interface_dump (ctx, dump, sw_interface_dump_cb, &dctx)))
+    ;
+  for (i = 0; i < num_ifs; ++i)
+    {
+      ck_assert_int_eq (false, seen[i]);
+    }
+  memset (&seen, 0, sizeof (seen));
+  ck_assert_int_eq (false, dctx.last_called);
+  rv = vapi_dispatch (ctx);
+  ck_assert_int_eq (VAPI_OK, rv);
+  for (i = 0; i < num_ifs; ++i)
+    {
+      ck_assert_int_eq (true, seen[i]);
+    }
+  memset (&seen, 0, sizeof (seen));
+  ck_assert_int_eq (true, dctx.last_called);
+  for (i = 0; i < num_ifs; ++i)
+    {
+      vapi_msg_delete_loopback *dl = vapi_alloc_delete_loopback (ctx);
+      dl->payload.sw_if_index = sw_if_indexes[i];
+      while (VAPI_EAGAIN ==
+            (rv =
+             vapi_delete_loopback (ctx, dl, loopback_delete_cb, &dlcs[i])))
+       ;
+      ck_assert_int_eq (VAPI_OK, rv);
+    }
+  rv = vapi_dispatch (ctx);
+  ck_assert_int_eq (VAPI_OK, rv);
+  for (i = 0; i < num_ifs; ++i)
+    {
+      ck_assert_int_eq (1, dlcs[i].called);
+      printf ("Deleted loopback with sw_if_index %u\n", sw_if_indexes[i]);
+    }
+  memset (&seen, 0, sizeof (seen));
+  dctx.last_called = false;
+  dump = vapi_alloc_sw_interface_dump (ctx);
+  dump->payload.name_filter_valid = 0;
+  memset (dump->payload.name_filter, 0, sizeof (dump->payload.name_filter));
+  while (VAPI_EAGAIN ==
+        (rv =
+         vapi_sw_interface_dump (ctx, dump, sw_interface_dump_cb, &dctx)))
+    ;
+  rv = vapi_dispatch (ctx);
+  ck_assert_int_eq (VAPI_OK, rv);
+  for (i = 0; i < num_ifs; ++i)
+    {
+      ck_assert_int_eq (false, seen[i]);
+    }
+  memset (&seen, 0, sizeof (seen));
+  ck_assert_int_eq (true, dctx.last_called);
+}
+
+END_TEST;
+
+vapi_error_e
+interface_simple_stats_cb (vapi_ctx_t ctx, void *callback_ctx,
+                          vapi_error_e rv, bool is_last,
+                          vapi_payload_want_interface_simple_stats_reply *
+                          payload)
+{
+  return VAPI_OK;
+}
+
+vapi_error_e
+simple_counters_cb (vapi_ctx_t ctx, void *callback_ctx,
+                   vapi_payload_vnet_interface_simple_counters * payload)
+{
+  int *called = callback_ctx;
+  ++*called;
+  printf ("simple counters: first_sw_if_index=%u\n",
+         payload->first_sw_if_index);
+  return VAPI_OK;
+}
+
+START_TEST (test_stats_1)
+{
+  printf ("--- Receive stats using generic blocking API ---\n");
+  vapi_msg_want_interface_simple_stats *ws =
+    vapi_alloc_want_interface_simple_stats (ctx);
+  ws->payload.enable_disable = 1;
+  ws->payload.pid = getpid ();
+  vapi_error_e rv;
+  rv = vapi_want_interface_simple_stats (ctx, ws, interface_simple_stats_cb,
+                                        NULL);
+  ck_assert_int_eq (VAPI_OK, rv);
+  int called = 0;
+  vapi_set_event_cb (ctx, vapi_msg_id_vnet_interface_simple_counters,
+                    (vapi_event_cb) simple_counters_cb, &called);
+  rv = vapi_dispatch_one (ctx);
+  ck_assert_int_eq (VAPI_OK, rv);
+  ck_assert_int_eq (1, called);
+}
+
+END_TEST;
+
+START_TEST (test_stats_2)
+{
+  printf ("--- Receive stats using stat-specific blocking API ---\n");
+  vapi_msg_want_interface_simple_stats *ws =
+    vapi_alloc_want_interface_simple_stats (ctx);
+  ws->payload.enable_disable = 1;
+  ws->payload.pid = getpid ();
+  vapi_error_e rv;
+  rv = vapi_want_interface_simple_stats (ctx, ws, interface_simple_stats_cb,
+                                        NULL);
+  ck_assert_int_eq (VAPI_OK, rv);
+  int called = 0;
+  vapi_set_vapi_msg_vnet_interface_simple_counters_event_cb (ctx,
+                                                            simple_counters_cb,
+                                                            &called);
+  rv = vapi_dispatch_one (ctx);
+  ck_assert_int_eq (VAPI_OK, rv);
+  ck_assert_int_eq (1, called);
+}
+
+END_TEST;
+
+vapi_error_e
+generic_cb (vapi_ctx_t ctx, void *callback_ctx, vapi_msg_id_t id, void *msg)
+{
+  int *called = callback_ctx;
+  ck_assert_int_eq (0, *called);
+  ++*called;
+  ck_assert_int_eq (id, vapi_msg_id_show_version_reply);
+  ck_assert_ptr_ne (NULL, msg);
+  vapi_msg_show_version_reply *reply = msg;
+  ck_assert_str_eq ("vpe", (char *) reply->payload.program);
+  return VAPI_OK;
+}
+
+START_TEST (test_show_version_5)
+{
+  printf ("--- Receive show version using generic callback - nonblocking "
+         "API ---\n");
+  vapi_error_e rv;
+  vapi_msg_show_version *sv = vapi_alloc_show_version (ctx);
+  ck_assert_ptr_ne (NULL, sv);
+  vapi_msg_show_version_hton (sv);
+  while (VAPI_EAGAIN == (rv = vapi_send (ctx, sv)))
+    ;
+  ck_assert_int_eq (VAPI_OK, rv);
+  int called = 0;
+  vapi_set_generic_event_cb (ctx, generic_cb, &called);
+  ck_assert_int_eq (VAPI_OK, rv);
+  rv = vapi_dispatch_one (ctx);
+  ck_assert_int_eq (VAPI_OK, rv);
+  ck_assert_int_eq (1, called);
+  sv = vapi_alloc_show_version (ctx);
+  ck_assert_ptr_ne (NULL, sv);
+  vapi_msg_show_version_hton (sv);
+  while (VAPI_EAGAIN == (rv = vapi_send (ctx, sv)))
+    ;
+  ck_assert_int_eq (VAPI_OK, rv);
+  vapi_clear_generic_event_cb (ctx);
+  rv = vapi_dispatch_one (ctx);
+  ck_assert_int_eq (VAPI_OK, rv);
+  ck_assert_int_eq (1, called);        /* needs to remain unchanged */
+}
+
+END_TEST;
+
+vapi_error_e
+combined_counters_cb (struct vapi_ctx_s *ctx, void *callback_ctx,
+                     vapi_payload_vnet_interface_combined_counters * payload)
+{
+  int *called = callback_ctx;
+  ++*called;
+  printf ("combined counters: first_sw_if_index=%u\n",
+         payload->first_sw_if_index);
+  return VAPI_OK;
+}
+
+vapi_error_e
+stats_cb (vapi_ctx_t ctx, void *callback_ctx, vapi_error_e rv,
+         bool is_last, vapi_payload_want_stats_reply * payload)
+{
+  return VAPI_OK;
+}
+
+START_TEST (test_stats_3)
+{
+  printf ("--- Receive multiple stats using stat-specific non-blocking API "
+         "---\n");
+  vapi_msg_want_stats *ws = vapi_alloc_want_stats (ctx);
+  ws->payload.enable_disable = 1;
+  ws->payload.pid = getpid ();
+  vapi_error_e rv;
+  rv = vapi_want_stats (ctx, ws, stats_cb, NULL);
+  ck_assert_int_eq (VAPI_OK, rv);
+  int called = 0;
+  int called2 = 0;
+  vapi_set_vapi_msg_vnet_interface_simple_counters_event_cb (ctx,
+                                                            simple_counters_cb,
+                                                            &called);
+  vapi_set_vapi_msg_vnet_interface_combined_counters_event_cb (ctx,
+                                                              combined_counters_cb,
+                                                              &called2);
+  while (!called || !called2)
+    {
+      if (VAPI_EAGAIN != (rv = vapi_dispatch_one (ctx)))
+       {
+         ck_assert_int_eq (VAPI_OK, rv);
+       }
+    }
+}
+
+END_TEST;
+
+vapi_error_e
+show_version_no_cb (vapi_ctx_t ctx, void *caller_ctx,
+                   vapi_error_e rv, bool is_last,
+                   vapi_payload_show_version_reply * p)
+{
+  ck_assert_int_eq (VAPI_ENORESP, rv);
+  ck_assert_int_eq (true, is_last);
+  ck_assert_ptr_eq (NULL, p);
+  ++*(int *) caller_ctx;
+  return VAPI_OK;
+}
+
+START_TEST (test_no_response_1)
+{
+  printf ("--- Simulate no response to regular message ---\n");
+  vapi_error_e rv;
+  vapi_msg_show_version *sv = vapi_alloc_show_version (ctx);
+  ck_assert_ptr_ne (NULL, sv);
+  sv->header._vl_msg_id = ~0;  /* malformed ID causes vpp to drop the msg */
+  int called = 0;
+  while (VAPI_EAGAIN ==
+        (rv = vapi_show_version (ctx, sv, show_version_no_cb, &called)))
+    ;
+  ck_assert_int_eq (VAPI_OK, rv);
+  sv = vapi_alloc_show_version (ctx);
+  ck_assert_ptr_ne (NULL, sv);
+  while (VAPI_EAGAIN ==
+        (rv = vapi_show_version (ctx, sv, show_version_cb, &called)))
+    ;
+  ck_assert_int_eq (VAPI_OK, rv);
+  rv = vapi_dispatch (ctx);
+  ck_assert_int_eq (VAPI_OK, rv);
+  ck_assert_int_eq (2, called);
+}
+
+END_TEST;
+
+vapi_error_e
+no_msg_cb (struct vapi_ctx_s *ctx, void *callback_ctx,
+          vapi_error_e rv, bool is_last,
+          vapi_payload_sw_interface_details * reply)
+{
+  int *called = callback_ctx;
+  ++*called;
+  ck_assert_int_eq (VAPI_OK, rv);
+  ck_assert_int_eq (true, is_last);
+  ck_assert_ptr_eq (NULL, reply);
+  return VAPI_OK;
+}
+
+START_TEST (test_no_response_2)
+{
+  printf ("--- Simulate no response to dump message ---\n");
+  vapi_error_e rv;
+  vapi_msg_sw_interface_dump *dump = vapi_alloc_sw_interface_dump (ctx);
+  dump->header._vl_msg_id = ~0;        /* malformed ID causes vpp to drop the msg */
+  int no_called = 0;
+  while (VAPI_EAGAIN ==
+        (rv = vapi_sw_interface_dump (ctx, dump, no_msg_cb, &no_called)))
+    ;
+  ck_assert_int_eq (VAPI_OK, rv);
+  rv = vapi_dispatch (ctx);
+  ck_assert_int_eq (VAPI_OK, rv);
+  ck_assert_int_eq (1, no_called);
+}
+
+END_TEST;
+Suite *
+test_suite (void)
+{
+  Suite *s = suite_create ("VAPI test");
+
+  TCase *tc_negative = tcase_create ("Negative tests");
+  tcase_add_test (tc_negative, test_invalid_values);
+  suite_add_tcase (s, tc_negative);
+
+  TCase *tc_swap = tcase_create ("Byteswap tests");
+  tcase_add_test (tc_swap, test_hton_1);
+  tcase_add_test (tc_swap, test_hton_2);
+  tcase_add_test (tc_swap, test_hton_3);
+  tcase_add_test (tc_swap, test_hton_4);
+  tcase_add_test (tc_swap, test_ntoh_1);
+  tcase_add_test (tc_swap, test_ntoh_2);
+  tcase_add_test (tc_swap, test_ntoh_3);
+  tcase_add_test (tc_swap, test_ntoh_4);
+  suite_add_tcase (s, tc_swap);
+
+  TCase *tc_connect = tcase_create ("Connect");
+  tcase_add_test (tc_connect, test_connect);
+  suite_add_tcase (s, tc_connect);
+
+  TCase *tc_block = tcase_create ("Blocking API");
+  tcase_set_timeout (tc_block, 25);
+  tcase_add_checked_fixture (tc_block, setup_blocking, teardown);
+  tcase_add_test (tc_block, test_show_version_1);
+  tcase_add_test (tc_block, test_show_version_2);
+  tcase_add_test (tc_block, test_loopbacks_1);
+  tcase_add_test (tc_block, test_stats_1);
+  tcase_add_test (tc_block, test_stats_2);
+  suite_add_tcase (s, tc_block);
+
+  TCase *tc_nonblock = tcase_create ("Nonblocking API");
+  tcase_set_timeout (tc_nonblock, 25);
+  tcase_add_checked_fixture (tc_nonblock, setup_nonblocking, teardown);
+  tcase_add_test (tc_nonblock, test_show_version_3);
+  tcase_add_test (tc_nonblock, test_show_version_4);
+  tcase_add_test (tc_nonblock, test_show_version_5);
+  tcase_add_test (tc_nonblock, test_loopbacks_2);
+  tcase_add_test (tc_nonblock, test_stats_3);
+  tcase_add_test (tc_nonblock, test_no_response_1);
+  tcase_add_test (tc_nonblock, test_no_response_2);
+  suite_add_tcase (s, tc_nonblock);
+
+  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 17dc7c3..51f5d5c 100755 (executable)
@@ -3,14 +3,15 @@
 function usage() {
  echo "$0" 1>&2
  echo "" 1>&2
- echo "Usage: $0 [-p <pre-exec-cmd>] [-m <email>] -- <make test options>" 1>&2
+ echo "Usage: $0 [-p <pre-exec-cmd>] [-m <email>] -- <make test options|verify>" 1>&2
  echo "" 1>&2
  echo "Parameters:" 1>&2
  echo "    -p <pre-exec-cmd> - run a command before each test loop (e.g. 'git pull')" 1>&2
  echo "    -m <email>        - if set, email is sent to this address on failure" 1>&2
  echo "" 1>&2
- echo "Example:" 1>&2
- echo "    $0 -m <somebody@cisco.com> -- test-debug TEST=l2bd"
+ echo "Examples:" 1>&2
+ echo "    $0 -m <somebody@cisco.com> -- test-debug TEST=l2bd" 1>&2
+ echo "    $0 -m <somebody@cisco.com> -- verify" 1>&2
  exit 1;
 }
 
@@ -44,8 +45,11 @@ shift $((OPTIND-1))
 
 if ! echo $* | grep test >/dev/null
 then
-        echo "Error: command line doesn't look right - should contain \`test' token..." >&2
-       usage
+       if ! echo $* | grep verify >/dev/null
+       then
+               echo "Error: command line doesn't look right - should contain \`test' or \`verify' token..." >&2
+               usage
+       fi
 fi
 
 function finish {
diff --git a/test/test_vapi.py b/test/test_vapi.py
new file mode 100644 (file)
index 0000000..86c1ee0
--- /dev/null
@@ -0,0 +1,78 @@
+#!/usr/bin/env python
+""" VAPI test """
+
+from __future__ import division
+import unittest
+import os
+import signal
+import subprocess
+from threading import Thread
+from log import single_line_delim
+from framework import VppTestCase, running_extended_tests, VppTestRunner
+
+
+class Worker(Thread):
+    def __init__(self, args, logger):
+        self.logger = logger
+        self.args = args
+        self.result = None
+        super(Worker, self).__init__()
+
+    def run(self):
+        executable = self.args[0]
+        self.logger.debug("Running executable w/args `%s'" % self.args)
+        env = os.environ.copy()
+        env["CK_LOG_FILE_NAME"] = "-"
+        self.process = subprocess.Popen(
+            self.args, shell=False, env=env, preexec_fn=os.setpgrp,
+            stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+        out, err = self.process.communicate()
+        self.logger.debug("Finished running `%s'" % executable)
+        self.logger.info("Return code is `%s'" % self.process.returncode)
+        self.logger.info(single_line_delim)
+        self.logger.info("Executable `%s' wrote to stdout:" % executable)
+        self.logger.info(single_line_delim)
+        self.logger.info(out)
+        self.logger.info(single_line_delim)
+        self.logger.info("Executable `%s' wrote to stderr:" % executable)
+        self.logger.info(single_line_delim)
+        self.logger.error(err)
+        self.logger.info(single_line_delim)
+        self.result = self.process.returncode
+
+
+@unittest.skipUnless(running_extended_tests(), "part of extended tests")
+class VAPITestCase(VppTestCase):
+    """ VAPI test """
+
+    def test_vapi(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
+        worker = Worker(
+            [executable, "vapi client", self.shm_prefix], self.logger)
+        worker.start()
+        timeout = 45
+        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")
+
+
+if __name__ == '__main__':
+    unittest.main(testRunner=VppTestRunner)