aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKlement Sekera <ksekera@cisco.com>2017-05-04 06:15:18 +0200
committerNeale Ranns <nranns@cisco.com>2017-09-19 12:23:44 +0000
commit8f2a4eafeaa439432107563033728e09665c16d9 (patch)
treea8db2c9f35e169a2eb48a7f5aebb88f146029f5f
parentf4215a65cb5858b276b0b096273270e7c017209d (diff)
Add new C API
Change-Id: I717ce3cd7c867c155de149ec56623269d26d0ff7 Signed-off-by: Klement Sekera <ksekera@cisco.com>
-rw-r--r--.gitignore1
-rw-r--r--Makefile11
-rw-r--r--src/Makefile.am2
-rw-r--r--src/configure.ac2
-rw-r--r--src/vlibmemory/unix_shared_memory_queue.c65
-rw-r--r--src/vlibmemory/unix_shared_memory_queue.h11
-rw-r--r--src/vpp-api/vapi/Makefile.am63
-rw-r--r--src/vpp-api/vapi/libvapiclient.map41
-rw-r--r--src/vpp-api/vapi/vapi.c895
-rw-r--r--src/vpp-api/vapi/vapi.h285
-rwxr-xr-xsrc/vpp-api/vapi/vapi_c_gen.py809
-rw-r--r--src/vpp-api/vapi/vapi_dbg.h76
-rw-r--r--src/vpp-api/vapi/vapi_internal.h126
-rw-r--r--src/vpp-api/vapi/vapi_json_parser.py303
-rw-r--r--test/Makefile6
-rw-r--r--test/ext/Makefile17
-rw-r--r--test/ext/vapi_test.c1152
-rwxr-xr-xtest/scripts/test-loop.sh14
-rw-r--r--test/test_vapi.py78
19 files changed, 3943 insertions, 14 deletions
diff --git a/.gitignore b/.gitignore
index ba4e104aeba..5a6266d79fa 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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
diff --git a/Makefile b/Makefile
index c08115d47e5..c46fa6bbd05 100644
--- 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
diff --git a/src/Makefile.am b/src/Makefile.am
index 41076e0ec7e..7b35e50c37a 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -80,6 +80,8 @@ if ENABLE_JAPI
SUBDIRS += vpp-api/java
endif
+SUBDIRS += vpp-api/vapi
+
###############################################################################
# API
###############################################################################
diff --git a/src/configure.ac b/src/configure.ac
index 6b6d9636400..2efb23ad690 100644
--- a/src/configure.ac
+++ b/src/configure.ac
@@ -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
diff --git a/src/vlibmemory/unix_shared_memory_queue.c b/src/vlibmemory/unix_shared_memory_queue.c
index e86edec3c65..4db4851c111 100644
--- a/src/vlibmemory/unix_shared_memory_queue.c
+++ b/src/vlibmemory/unix_shared_memory_queue.c
@@ -235,6 +235,71 @@ unix_shared_memory_queue_add (unix_shared_memory_queue_t * q,
}
/*
+ * 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
*/
int
diff --git a/src/vlibmemory/unix_shared_memory_queue.h b/src/vlibmemory/unix_shared_memory_queue.h
index 13800065beb..27de3218223 100644
--- a/src/vlibmemory/unix_shared_memory_queue.h
+++ b/src/vlibmemory/unix_shared_memory_queue.h
@@ -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
index 00000000000..ce681c38459
--- /dev/null
+++ b/src/vpp-api/vapi/Makefile.am
@@ -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
index 00000000000..537330026c5
--- /dev/null
+++ b/src/vpp-api/vapi/libvapiclient.map
@@ -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
index 00000000000..b9c81a1307a
--- /dev/null
+++ b/src/vpp-api/vapi/vapi.c
@@ -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
index 00000000000..1e1d567a0db
--- /dev/null
+++ b/src/vpp-api/vapi/vapi.h
@@ -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
index 00000000000..2bc1eef87e5
--- /dev/null
+++ b/src/vpp-api/vapi/vapi_c_gen.py
@@ -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
index 00000000000..95a8008915b
--- /dev/null
+++ b/src/vpp-api/vapi/vapi_dbg.h
@@ -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
index 00000000000..5b85788db8a
--- /dev/null
+++ b/src/vpp-api/vapi/vapi_internal.h
@@ -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
index 00000000000..57a2238322b
--- /dev/null
+++ b/src/vpp-api/vapi/vapi_json_parser.py
@@ -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}
diff --git a/test/Makefile b/test/Makefile
index 72b4dac7454..132ebee6006 100644
--- a/test/Makefile
+++ b/test/Makefile
@@ -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
index 00000000000..4a45fef6782
--- /dev/null
+++ b/test/ext/Makefile
@@ -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
index 00000000000..eca6be7d6cc
--- /dev/null
+++ b/test/ext/vapi_test.c
@@ -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:
+ */
diff --git a/test/scripts/test-loop.sh b/test/scripts/test-loop.sh
index 17dc7c39cdf..51f5d5cef58 100755
--- a/test/scripts/test-loop.sh
+++ b/test/scripts/test-loop.sh
@@ -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
index 00000000000..86c1ee06fc7
--- /dev/null
+++ b/test/test_vapi.py
@@ -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)