aboutsummaryrefslogtreecommitdiffstats
path: root/src/vpp-api/vapi
diff options
context:
space:
mode:
authorSachin Saxena <sachin.saxena@freescale.com>2018-02-28 20:28:52 +0530
committerSachin Saxena <sachin.saxena@nxp.com>2018-02-28 20:34:56 +0530
commit0689fce93ba269c48f83a2f70f971b3976d04c90 (patch)
tree4cc2908df3598507cc1828ac19d8c43b22450ffa /src/vpp-api/vapi
parent746b57564deede624261ab8a96c94f562f24d22c (diff)
parentd594711a5d79859a7d0bde83a516f7ab52051d9b (diff)
Merge branch 'stable/1710' of https://gerrit.fd.io/r/vpp into 17101710
Diffstat (limited to 'src/vpp-api/vapi')
-rw-r--r--src/vpp-api/vapi/Makefile.am74
-rw-r--r--src/vpp-api/vapi/libvapiclient.map44
-rw-r--r--src/vpp-api/vapi/vapi.c933
-rw-r--r--src/vpp-api/vapi/vapi.h263
-rw-r--r--src/vpp-api/vapi/vapi.hpp905
-rwxr-xr-xsrc/vpp-api/vapi/vapi_c_gen.py693
-rw-r--r--src/vpp-api/vapi/vapi_common.h61
-rwxr-xr-xsrc/vpp-api/vapi/vapi_cpp_gen.py263
-rw-r--r--src/vpp-api/vapi/vapi_dbg.h77
-rw-r--r--src/vpp-api/vapi/vapi_doc.md155
-rw-r--r--src/vpp-api/vapi/vapi_internal.h138
-rw-r--r--src/vpp-api/vapi/vapi_json_parser.py305
12 files changed, 3911 insertions, 0 deletions
diff --git a/src/vpp-api/vapi/Makefile.am b/src/vpp-api/vapi/Makefile.am
new file mode 100644
index 00000000..74b2b47e
--- /dev/null
+++ b/src/vpp-api/vapi/Makefile.am
@@ -0,0 +1,74 @@
+# 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/
+
+AM_LDFLAGS = -shared -avoid-version -rpath /none -no-undefined
+
+bin_PROGRAMS =
+noinst_LTLIBRARIES =
+CLEANDIRS =
+
+vapi/%.api.vapi.h: %.api.json vapi_c_gen.py vapi_json_parser.py
+ @echo " VAPI C GEN $< " $@ ; \
+ mkdir -p `dirname $@` ; \
+ $(top_srcdir)/vpp-api/vapi/vapi_c_gen.py --prefix=vapi $<
+
+vapi/%.api.vapi.hpp: %.api.json vapi_cpp_gen.py vapi_c_gen.py vapi_json_parser.py
+ @echo " VAPI CPP GEN $< " $@ ; \
+ mkdir -p `dirname $@` ; \
+ $(top_srcdir)/vpp-api/vapi/vapi_cpp_gen.py --prefix=vapi --gen-h-prefix=vapi $<
+
+%.api.json:
+ find $(top_builddir) -name '$@' | xargs ln -s
+
+BUILT_SOURCES = $(shell find $(top_builddir) -name '*.api.json' | xargs -n1 basename) \
+ $(patsubst %.api.json,vapi/%.api.vapi.h,$(JSON_FILES)) \
+ $(patsubst %.api.json,vapi/%.api.vapi.hpp,$(JSON_FILES))
+
+vapi.c: $(BUILT_SOURCES)
+
+JSON_FILES = $(wildcard *.api.json)
+
+lib_LTLIBRARIES = libvapiclient.la
+
+libvapiclient_la_SOURCES = vapi.c
+
+libvapiclient_la_DEPENDENCIES = libvapiclient.map
+
+libvapiclient_la_LIBADD = -lpthread -lm -lrt \
+ $(top_builddir)/libvppinfra.la \
+ $(top_builddir)/libvlibmemoryclient.la \
+ $(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
+
+vapiincludedir = $(includedir)/vapi
+
+vapiinclude_HEADERS = vapi.h \
+ vapi.hpp \
+ vapi_dbg.h \
+ vapi_common.h \
+ vapi_internal.h \
+ $(patsubst %.api.json,vapi/%.api.vapi.h,$(JSON_FILES)) \
+ $(patsubst %.api.json,vapi/%.api.vapi.hpp,$(JSON_FILES))
+
+# vi:syntax=automake
diff --git a/src/vpp-api/vapi/libvapiclient.map b/src/vpp-api/vapi/libvapiclient.map
new file mode 100644
index 00000000..6b58d1e9
--- /dev/null
+++ b/src/vpp-api/vapi/libvapiclient.map
@@ -0,0 +1,44 @@
+
+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_empty;
+ 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;
+ vapi_get_message_count;
+ vapi_get_msg_name;
+
+ local: *;
+};
diff --git a/src/vpp-api/vapi/vapi.c b/src/vpp-api/vapi/vapi.c
new file mode 100644
index 00000000..3150d2b4
--- /dev/null
+++ b/src/vpp-api/vapi/vapi.c
@@ -0,0 +1,933 @@
+/*
+ *------------------------------------------------------------------
+ * 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);
+}
+
+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_msg_id_t
+vapi_lookup_vapi_msg_id_t (vapi_ctx_t ctx, u16 vl_msg_id)
+{
+ if (vl_msg_id <= ctx->vl_msg_id_max)
+ {
+ return ctx->vl_msg_id_to_vapi_msg_t[vl_msg_id];
+ }
+ return ~0;
+}
+
+vapi_error_e
+vapi_ctx_alloc (vapi_ctx_t * result)
+{
+ 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);
+ /* coverity[MISSING_LOCK] - 177211 requests_mutex is not needed here */
+ 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@%p:%u[%s]", msg, msgid,
+ __vapi_metadata.msgs[id]->name);
+ }
+ else
+ {
+ VAPI_DBG ("send msg@%p:%u[UNKNOWN]", msg, msgid);
+ }
+ }
+ else
+ {
+ VAPI_DBG ("send msg@%p:%u[UNKNOWN]", msg, 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);
+#if VAPI_DEBUG
+ unsigned msgid = be16toh (*(u16 *) * msg);
+ if (msgid <= ctx->vl_msg_id_max)
+ {
+ vapi_msg_id_t id = ctx->vl_msg_id_to_vapi_msg_t[msgid];
+ if (id < __vapi_metadata.count)
+ {
+ VAPI_DBG ("recv msg@%p:%u[%s]", *msg, msgid,
+ __vapi_metadata.msgs[id]->name);
+ }
+ else
+ {
+ VAPI_DBG ("recv msg@%p:%u[UNKNOWN]", *msg, msgid);
+ }
+ }
+ else
+ {
+ VAPI_DBG ("recv msg@%p:%u[UNKNOWN]", *msg, msgid);
+ }
+#endif
+ }
+ else
+ {
+ rv = VAPI_EAGAIN;
+ }
+ return rv;
+}
+
+vapi_error_e
+vapi_wait (vapi_ctx_t ctx, vapi_wait_mode_e mode)
+{
+ 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;
+}
+
+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);
+}
+
+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;
+}
+
+size_t
+vapi_get_message_count ()
+{
+ return __vapi_metadata.count;
+}
+
+const char *
+vapi_get_msg_name (vapi_msg_id_t id)
+{
+ return __vapi_metadata.msgs[id]->name;
+}
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * 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 00000000..245bf654
--- /dev/null
+++ b/src/vpp-api/vapi/vapi.h
@@ -0,0 +1,263 @@
+/*
+ *------------------------------------------------------------------
+ * 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>
+#include <vapi/vapi_common.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+/**
+ * @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 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);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/vpp-api/vapi/vapi.hpp b/src/vpp-api/vapi/vapi.hpp
new file mode 100644
index 00000000..3be78b41
--- /dev/null
+++ b/src/vpp-api/vapi/vapi.hpp
@@ -0,0 +1,905 @@
+/*
+ *------------------------------------------------------------------
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *------------------------------------------------------------------
+ */
+
+#ifndef vapi_hpp_included
+#define vapi_hpp_included
+
+#include <cstddef>
+#include <vector>
+#include <mutex>
+#include <queue>
+#include <cassert>
+#include <functional>
+#include <algorithm>
+#include <atomic>
+#include <vppinfra/types.h>
+#include <vapi/vapi.h>
+#include <vapi/vapi_internal.h>
+#include <vapi/vapi_dbg.h>
+#include <vapi/vpe.api.vapi.h>
+
+#if VAPI_CPP_DEBUG_LEAKS
+#include <unordered_set>
+#endif
+
+/**
+ * @file
+ * @brief C++ VPP API
+ */
+
+namespace vapi
+{
+
+class Connection;
+
+template <typename Req, typename Resp, typename... Args> class Request;
+template <typename M> class Msg;
+template <typename M> void vapi_swap_to_be (M *msg);
+template <typename M> void vapi_swap_to_host (M *msg);
+template <typename M, typename... Args>
+M *vapi_alloc (Connection &con, Args...);
+template <typename M> vapi_msg_id_t vapi_get_msg_id_t ();
+template <typename M> class Event_registration;
+
+class Unexpected_msg_id_exception : public std::exception
+{
+public:
+ virtual const char *what () const throw ()
+ {
+ return "unexpected message id";
+ }
+};
+
+class Msg_not_available_exception : public std::exception
+{
+public:
+ virtual const char *what () const throw ()
+ {
+ return "message unavailable";
+ }
+};
+
+typedef enum {
+ /** response not ready yet */
+ RESPONSE_NOT_READY,
+
+ /** response to request is ready */
+ RESPONSE_READY,
+
+ /** no response to request (will never come) */
+ RESPONSE_NO_RESPONSE,
+} vapi_response_state_e;
+
+/**
+ * Class representing common functionality of a request - response state
+ * and context
+ */
+class Common_req
+{
+public:
+ virtual ~Common_req (){};
+
+ Connection &get_connection ()
+ {
+ return con;
+ };
+
+ vapi_response_state_e get_response_state (void) const
+ {
+ return response_state;
+ }
+
+private:
+ Connection &con;
+ Common_req (Connection &con) : con{con}, response_state{RESPONSE_NOT_READY}
+ {
+ }
+
+ void set_response_state (vapi_response_state_e state)
+ {
+ response_state = state;
+ }
+
+ virtual std::tuple<vapi_error_e, bool> assign_response (vapi_msg_id_t id,
+ void *shm_data) = 0;
+
+ void set_context (u32 context)
+ {
+ this->context = context;
+ }
+
+ u32 get_context ()
+ {
+ return context;
+ }
+
+ u32 context;
+ vapi_response_state_e response_state;
+
+ friend class Connection;
+
+ template <typename M> friend class Msg;
+
+ template <typename Req, typename Resp, typename... Args>
+ friend class Request;
+
+ template <typename Req, typename Resp, typename... Args> friend class Dump;
+
+ template <typename M> friend class Event_registration;
+};
+
+/**
+ * Class representing a connection to VPP
+ *
+ * After creating a Connection object, call connect() to actually connect
+ * to VPP. Use is_msg_available to discover whether a specific message is known
+ * and supported by the VPP connected to.
+ */
+class Connection
+{
+public:
+ Connection (void) : vapi_ctx{0}, event_count{0}
+ {
+
+ vapi_error_e rv = VAPI_OK;
+ if (!vapi_ctx)
+ {
+ if (VAPI_OK != (rv = vapi_ctx_alloc (&vapi_ctx)))
+ {
+ throw std::bad_alloc ();
+ }
+ }
+ events.reserve (vapi_get_message_count () + 1);
+ }
+
+ Connection (const Connection &) = delete;
+
+ ~Connection (void)
+ {
+ vapi_ctx_free (vapi_ctx);
+#if VAPI_CPP_DEBUG_LEAKS
+ for (auto x : shm_data_set)
+ {
+ printf ("Leaked shm_data@%p!\n", x);
+ }
+#endif
+ }
+
+ /**
+ * @brief check if message identified by it's message id is known by the
+ * vpp to which the connection is open
+ */
+ bool is_msg_available (vapi_msg_id_t type)
+ {
+ return vapi_is_msg_available (vapi_ctx, type);
+ }
+
+ /**
+ * @brief connect to vpp
+ *
+ * @param name application name
+ * @param chroot_prefix shared memory prefix
+ * @param max_queued_request max number of outstanding requests queued
+ *
+ * @return VAPI_OK on success, other error code on error
+ */
+ vapi_error_e connect (const char *name, const char *chroot_prefix,
+ int max_outstanding_requests, int response_queue_size)
+ {
+ return vapi_connect (vapi_ctx, name, chroot_prefix,
+ max_outstanding_requests, response_queue_size,
+ VAPI_MODE_BLOCKING);
+ }
+
+ /**
+ * @brief disconnect from vpp
+ *
+ * @return VAPI_OK on success, other error code on error
+ */
+ vapi_error_e disconnect ()
+ {
+ auto x = requests.size ();
+ while (x > 0)
+ {
+ VAPI_DBG ("popping request @%p", requests.front ());
+ requests.pop_front ();
+ --x;
+ }
+ return vapi_disconnect (vapi_ctx);
+ };
+
+ /**
+ * @brief get event file descriptor
+ *
+ * @note this file descriptor becomes readable when messages (from vpp)
+ * are waiting in queue
+ *
+ * @param[out] fd pointer to result variable
+ *
+ * @return VAPI_OK on success, other error code on error
+ */
+ vapi_error_e get_fd (int *fd)
+ {
+ return vapi_get_fd (vapi_ctx, fd);
+ }
+
+ /**
+ * @brief wait for responses from vpp and assign them to appropriate objects
+ *
+ * @param limit stop dispatch after the limit object received it's response
+ *
+ * @return VAPI_OK on success, other error code on error
+ */
+ vapi_error_e dispatch (const Common_req *limit = nullptr)
+ {
+ std::lock_guard<std::mutex> lock (dispatch_mutex);
+ vapi_error_e rv = VAPI_OK;
+ bool loop_again = true;
+ while (loop_again)
+ {
+ void *shm_data;
+ size_t shm_data_size;
+ rv = vapi_recv (vapi_ctx, &shm_data, &shm_data_size);
+ if (VAPI_OK != rv)
+ {
+ return rv;
+ }
+#if VAPI_CPP_DEBUG_LEAKS
+ on_shm_data_alloc (shm_data);
+#endif
+ std::lock_guard<std::recursive_mutex> requests_lock (requests_mutex);
+ std::lock_guard<std::recursive_mutex> events_lock (events_mutex);
+ vapi_msg_id_t id = vapi_lookup_vapi_msg_id_t (
+ vapi_ctx, be16toh (*static_cast<u16 *> (shm_data)));
+ bool has_context = vapi_msg_is_with_context (id);
+ bool break_dispatch = false;
+ Common_req *matching_req = nullptr;
+ if (has_context)
+ {
+ u32 context = *reinterpret_cast<u32 *> (
+ (static_cast<u8 *> (shm_data) + vapi_get_context_offset (id)));
+ const auto x = requests.front ();
+ matching_req = x;
+ if (context == x->context)
+ {
+ std::tie (rv, break_dispatch) =
+ x->assign_response (id, shm_data);
+ }
+ else
+ {
+ std::tie (rv, break_dispatch) =
+ x->assign_response (id, nullptr);
+ }
+ if (break_dispatch)
+ {
+ requests.pop_front ();
+ }
+ }
+ else
+ {
+ if (events[id])
+ {
+ std::tie (rv, break_dispatch) =
+ events[id]->assign_response (id, shm_data);
+ matching_req = events[id];
+ }
+ else
+ {
+ msg_free (shm_data);
+ }
+ }
+ if ((matching_req && matching_req == limit && break_dispatch) ||
+ VAPI_OK != rv)
+ {
+ return rv;
+ }
+ loop_again = !requests.empty () || (event_count > 0);
+ }
+ return rv;
+ }
+
+ /**
+ * @brief convenience wrapper function
+ */
+ vapi_error_e dispatch (const Common_req &limit)
+ {
+ return dispatch (&limit);
+ }
+
+ /**
+ * @brief wait for response to a specific request
+ *
+ * @param req request to wait for response for
+ *
+ * @return VAPI_OK on success, other error code on error
+ */
+ vapi_error_e wait_for_response (const Common_req &req)
+ {
+ if (RESPONSE_READY == req.get_response_state ())
+ {
+ return VAPI_OK;
+ }
+ return dispatch (req);
+ }
+
+private:
+ void msg_free (void *shm_data)
+ {
+#if VAPI_CPP_DEBUG_LEAKS
+ on_shm_data_free (shm_data);
+#endif
+ vapi_msg_free (vapi_ctx, shm_data);
+ }
+
+ template <template <typename XReq, typename XResp, typename... XArgs>
+ class X,
+ typename Req, typename Resp, typename... Args>
+ vapi_error_e send (X<Req, Resp, Args...> *req)
+ {
+ if (!req)
+ {
+ return VAPI_EINVAL;
+ }
+ u32 req_context =
+ req_context_counter.fetch_add (1, std::memory_order_relaxed);
+ req->request.shm_data->header.context = req_context;
+ vapi_swap_to_be<Req> (req->request.shm_data);
+ std::lock_guard<std::recursive_mutex> lock (requests_mutex);
+ vapi_error_e rv = vapi_send (vapi_ctx, req->request.shm_data);
+ if (VAPI_OK == rv)
+ {
+ VAPI_DBG ("Push %p", req);
+ requests.emplace_back (req);
+ req->set_context (req_context);
+#if VAPI_CPP_DEBUG_LEAKS
+ on_shm_data_free (req->request.shm_data);
+#endif
+ req->request.shm_data = nullptr; /* consumed by vapi_send */
+ }
+ else
+ {
+ vapi_swap_to_host<Req> (req->request.shm_data);
+ }
+ return rv;
+ }
+
+ template <template <typename XReq, typename XResp, typename... XArgs>
+ class X,
+ typename Req, typename Resp, typename... Args>
+ vapi_error_e send_with_control_ping (X<Req, Resp, Args...> *req)
+ {
+ if (!req)
+ {
+ return VAPI_EINVAL;
+ }
+ u32 req_context =
+ req_context_counter.fetch_add (1, std::memory_order_relaxed);
+ req->request.shm_data->header.context = req_context;
+ vapi_swap_to_be<Req> (req->request.shm_data);
+ std::lock_guard<std::recursive_mutex> lock (requests_mutex);
+ vapi_error_e rv = vapi_send_with_control_ping (
+ vapi_ctx, req->request.shm_data, req_context);
+ if (VAPI_OK == rv)
+ {
+ VAPI_DBG ("Push %p", req);
+ requests.emplace_back (req);
+ req->set_context (req_context);
+#if VAPI_CPP_DEBUG_LEAKS
+ on_shm_data_free (req->request.shm_data);
+#endif
+ req->request.shm_data = nullptr; /* consumed by vapi_send */
+ }
+ else
+ {
+ vapi_swap_to_host<Req> (req->request.shm_data);
+ }
+ return rv;
+ }
+
+ void unregister_request (Common_req *request)
+ {
+ std::lock_guard<std::recursive_mutex> lock (requests_mutex);
+ std::remove (requests.begin (), requests.end (), request);
+ }
+
+ template <typename M> void register_event (Event_registration<M> *event)
+ {
+ const vapi_msg_id_t id = M::get_msg_id ();
+ std::lock_guard<std::recursive_mutex> lock (events_mutex);
+ events[id] = event;
+ ++event_count;
+ }
+
+ template <typename M> void unregister_event (Event_registration<M> *event)
+ {
+ const vapi_msg_id_t id = M::get_msg_id ();
+ std::lock_guard<std::recursive_mutex> lock (events_mutex);
+ events[id] = nullptr;
+ --event_count;
+ }
+
+ vapi_ctx_t vapi_ctx;
+ std::atomic_ulong req_context_counter;
+ std::mutex dispatch_mutex;
+
+ std::recursive_mutex requests_mutex;
+ std::recursive_mutex events_mutex;
+ std::deque<Common_req *> requests;
+ std::vector<Common_req *> events;
+ int event_count;
+
+ template <typename Req, typename Resp, typename... Args>
+ friend class Request;
+
+ template <typename Req, typename Resp, typename... Args> friend class Dump;
+
+ template <typename M> friend class Result_set;
+
+ template <typename M> friend class Event_registration;
+
+ template <typename M, typename... Args>
+ friend M *vapi_alloc (Connection &con, Args...);
+
+ template <typename M> friend class Msg;
+
+#if VAPI_CPP_DEBUG_LEAKS
+ void on_shm_data_alloc (void *shm_data)
+ {
+ if (shm_data)
+ {
+ auto pos = shm_data_set.find (shm_data);
+ if (pos == shm_data_set.end ())
+ {
+ shm_data_set.insert (shm_data);
+ }
+ else
+ {
+ printf ("Double-add shm_data @%p!\n", shm_data);
+ }
+ }
+ }
+
+ void on_shm_data_free (void *shm_data)
+ {
+ auto pos = shm_data_set.find (shm_data);
+ if (pos == shm_data_set.end ())
+ {
+ printf ("Freeing untracked shm_data @%p!\n", shm_data);
+ }
+ else
+ {
+ shm_data_set.erase (pos);
+ }
+ }
+ std::unordered_set<void *> shm_data_set;
+#endif
+};
+
+template <typename Req, typename Resp, typename... Args> class Request;
+
+template <typename Req, typename Resp, typename... Args> class Dump;
+
+template <class, class = void> struct vapi_has_payload_trait : std::false_type
+{
+};
+
+template <class... T> using vapi_void_t = void;
+
+template <class T>
+struct vapi_has_payload_trait<T, vapi_void_t<decltype (&T::payload)>>
+ : std::true_type
+{
+};
+
+template <typename M> void vapi_msg_set_msg_id (vapi_msg_id_t id)
+{
+ Msg<M>::set_msg_id (id);
+}
+
+/**
+ * Class representing a message stored in shared memory
+ */
+template <typename M> class Msg
+{
+public:
+ Msg (const Msg &) = delete;
+
+ ~Msg ()
+ {
+ VAPI_DBG ("Destroy Msg<%s>@%p, shm_data@%p",
+ vapi_get_msg_name (get_msg_id ()), this, shm_data);
+ if (shm_data)
+ {
+ con.get ().msg_free (shm_data);
+ shm_data = nullptr;
+ }
+ }
+
+ static vapi_msg_id_t get_msg_id ()
+ {
+ return *msg_id_holder ();
+ }
+
+ template <typename X = M>
+ typename std::enable_if<vapi_has_payload_trait<X>::value,
+ decltype (X::payload) &>::type
+ get_payload () const
+ {
+ return shm_data->payload;
+ }
+
+private:
+ Msg (Msg<M> &&msg) : con{msg.con}
+ {
+ VAPI_DBG ("Move construct Msg<%s> from msg@%p to msg@%p, shm_data@%p",
+ vapi_get_msg_name (get_msg_id ()), &msg, this, msg.shm_data);
+ shm_data = msg.shm_data;
+ msg.shm_data = nullptr;
+ }
+
+ Msg<M> &operator= (Msg<M> &&msg)
+ {
+ VAPI_DBG ("Move assign Msg<%s> from msg@%p to msg@%p, shm_data@%p",
+ vapi_get_msg_name (get_msg_id ()), &msg, this, msg.shm_data);
+ con.get ().msg_free (shm_data);
+ con = msg.con;
+ shm_data = msg.shm_data;
+ msg.shm_data = nullptr;
+ return *this;
+ }
+
+ struct Msg_allocator : std::allocator<Msg<M>>
+ {
+ template <class U, class... Args> void construct (U *p, Args &&... args)
+ {
+ ::new ((void *)p) U (std::forward<Args> (args)...);
+ }
+
+ template <class U> struct rebind
+ {
+ typedef Msg_allocator other;
+ };
+ };
+
+ static void set_msg_id (vapi_msg_id_t id)
+ {
+ assert ((~0 == *msg_id_holder ()) || (id == *msg_id_holder ()));
+ *msg_id_holder () = id;
+ }
+
+ static vapi_msg_id_t *msg_id_holder ()
+ {
+ static vapi_msg_id_t my_id{~0};
+ return &my_id;
+ }
+
+ Msg (Connection &con, void *shm_data) throw (Msg_not_available_exception)
+ : con{con}
+ {
+ if (!con.is_msg_available (get_msg_id ()))
+ {
+ throw Msg_not_available_exception ();
+ }
+ this->shm_data = static_cast<shm_data_type *> (shm_data);
+ VAPI_DBG ("New Msg<%s>@%p shm_data@%p", vapi_get_msg_name (get_msg_id ()),
+ this, shm_data);
+ }
+
+ void assign_response (vapi_msg_id_t resp_id,
+ void *shm_data) throw (Unexpected_msg_id_exception)
+ {
+ assert (nullptr == this->shm_data);
+ if (resp_id != get_msg_id ())
+ {
+ throw Unexpected_msg_id_exception ();
+ }
+ this->shm_data = static_cast<M *> (shm_data);
+ vapi_swap_to_host<M> (this->shm_data);
+ VAPI_DBG ("Assign response to Msg<%s>@%p shm_data@%p",
+ vapi_get_msg_name (get_msg_id ()), this, shm_data);
+ }
+
+ std::reference_wrapper<Connection> con;
+ using shm_data_type = M;
+ shm_data_type *shm_data;
+
+ friend class Connection;
+
+ template <typename Req, typename Resp, typename... Args>
+ friend class Request;
+
+ template <typename Req, typename Resp, typename... Args> friend class Dump;
+
+ template <typename X> friend class Event_registration;
+
+ template <typename X> friend class Result_set;
+
+ friend struct Msg_allocator;
+
+ template <typename X> friend void vapi_msg_set_msg_id (vapi_msg_id_t id);
+};
+
+/**
+ * Class representing a simple request - with a single response message
+ */
+template <typename Req, typename Resp, typename... Args>
+class Request : public Common_req
+{
+public:
+ Request (Connection &con, Args... args,
+ std::function<vapi_error_e (Request<Req, Resp, Args...> &)>
+ callback = nullptr)
+ : Common_req{con}, callback{callback},
+ request{con, vapi_alloc<Req> (con, args...)}, response{con, nullptr}
+ {
+ }
+
+ Request (const Request &) = delete;
+
+ virtual ~Request ()
+ {
+ if (RESPONSE_NOT_READY == get_response_state ())
+ {
+ con.unregister_request (this);
+ }
+ }
+
+ vapi_error_e execute ()
+ {
+ return con.send (this);
+ }
+
+ const Msg<Req> &get_request (void) const
+ {
+ return request;
+ }
+
+ const Msg<Resp> &get_response (void)
+ {
+ return response;
+ }
+
+private:
+ virtual std::tuple<vapi_error_e, bool> assign_response (vapi_msg_id_t id,
+ void *shm_data)
+ {
+ assert (RESPONSE_NOT_READY == get_response_state ());
+ response.assign_response (id, shm_data);
+ set_response_state (RESPONSE_READY);
+ if (nullptr != callback)
+ {
+ return std::make_pair (callback (*this), true);
+ }
+ return std::make_pair (VAPI_OK, true);
+ }
+ std::function<vapi_error_e (Request<Req, Resp, Args...> &)> callback;
+ Msg<Req> request;
+ Msg<Resp> response;
+
+ friend class Connection;
+};
+
+/**
+ * Class representing iterable set of responses of the same type
+ */
+template <typename M> class Result_set
+{
+public:
+ ~Result_set ()
+ {
+ }
+
+ Result_set (const Result_set &) = delete;
+
+ bool is_complete () const
+ {
+ return complete;
+ }
+
+ size_t size () const
+ {
+ return set.size ();
+ }
+
+ using const_iterator =
+ typename std::vector<Msg<M>,
+ typename Msg<M>::Msg_allocator>::const_iterator;
+
+ const_iterator begin () const
+ {
+ return set.begin ();
+ }
+
+ const_iterator end () const
+ {
+ return set.end ();
+ }
+
+ void free_response (const_iterator pos)
+ {
+ set.erase (pos);
+ }
+
+ void free_all_responses ()
+ {
+ set.clear ();
+ }
+
+private:
+ void mark_complete ()
+ {
+ complete = true;
+ }
+
+ void assign_response (vapi_msg_id_t resp_id,
+ void *shm_data) throw (Unexpected_msg_id_exception)
+ {
+ if (resp_id != Msg<M>::get_msg_id ())
+ {
+ {
+ throw Unexpected_msg_id_exception ();
+ }
+ }
+ else if (shm_data)
+ {
+ vapi_swap_to_host<M> (static_cast<M *> (shm_data));
+ set.emplace_back (con, shm_data);
+ VAPI_DBG ("Result_set@%p emplace_back shm_data@%p", this, shm_data);
+ }
+ }
+
+ Result_set (Connection &con) : con{con}, complete{false}
+ {
+ }
+
+ Connection &con;
+ bool complete;
+ std::vector<Msg<M>, typename Msg<M>::Msg_allocator> set;
+
+ template <typename Req, typename Resp, typename... Args> friend class Dump;
+
+ template <typename X> friend class Event_registration;
+};
+
+/**
+ * Class representing a dump request - zero or more identical responses to a
+ * single request message
+ */
+template <typename Req, typename Resp, typename... Args>
+class Dump : public Common_req
+{
+public:
+ Dump (Connection &con, Args... args,
+ std::function<vapi_error_e (Dump<Req, Resp, Args...> &)> callback =
+ nullptr)
+ : Common_req{con}, request{con, vapi_alloc<Req> (con, args...)},
+ result_set{con}, callback{callback}
+ {
+ }
+
+ Dump (const Dump &) = delete;
+
+ virtual ~Dump ()
+ {
+ }
+
+ virtual std::tuple<vapi_error_e, bool> assign_response (vapi_msg_id_t id,
+ void *shm_data)
+ {
+ if (id == vapi_msg_id_control_ping_reply)
+ {
+ con.msg_free (shm_data);
+ result_set.mark_complete ();
+ set_response_state (RESPONSE_READY);
+ if (nullptr != callback)
+ {
+ return std::make_pair (callback (*this), true);
+ }
+ return std::make_pair (VAPI_OK, true);
+ }
+ else
+ {
+ result_set.assign_response (id, shm_data);
+ }
+ return std::make_pair (VAPI_OK, false);
+ }
+
+ vapi_error_e execute ()
+ {
+ return con.send_with_control_ping (this);
+ }
+
+ Msg<Req> &get_request (void)
+ {
+ return request;
+ }
+
+ using resp_type = typename Msg<Resp>::shm_data_type;
+
+ const Result_set<Resp> &get_result_set (void) const
+ {
+ return result_set;
+ }
+
+private:
+ Msg<Req> request;
+ Result_set<resp_type> result_set;
+ std::function<vapi_error_e (Dump<Req, Resp, Args...> &)> callback;
+
+ friend class Connection;
+};
+
+/**
+ * Class representing event registration - incoming events (messages) from
+ * vpp as a result of a subscription (typically a want_* simple request)
+ */
+template <typename M> class Event_registration : public Common_req
+{
+public:
+ Event_registration (
+ Connection &con,
+ std::function<vapi_error_e (Event_registration<M> &)> callback =
+ nullptr) throw (Msg_not_available_exception)
+ : Common_req{con}, result_set{con}, callback{callback}
+ {
+ if (!con.is_msg_available (M::get_msg_id ()))
+ {
+ throw Msg_not_available_exception ();
+ }
+ con.register_event (this);
+ }
+
+ Event_registration (const Event_registration &) = delete;
+
+ virtual ~Event_registration ()
+ {
+ con.unregister_event (this);
+ }
+
+ virtual std::tuple<vapi_error_e, bool> assign_response (vapi_msg_id_t id,
+ void *shm_data)
+ {
+ result_set.assign_response (id, shm_data);
+ if (nullptr != callback)
+ {
+ return std::make_pair (callback (*this), true);
+ }
+ return std::make_pair (VAPI_OK, true);
+ }
+
+ using resp_type = typename M::shm_data_type;
+
+ Result_set<resp_type> &get_result_set (void)
+ {
+ return result_set;
+ }
+
+private:
+ Result_set<resp_type> result_set;
+ std::function<vapi_error_e (Event_registration<M> &)> callback;
+};
+};
+
+#endif
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/vpp-api/vapi/vapi_c_gen.py b/src/vpp-api/vapi/vapi_c_gen.py
new file mode 100755
index 00000000..d7a7272a
--- /dev/null
+++ b/src/vpp-api/vapi/vapi_c_gen.py
@@ -0,0 +1,693 @@
+#!/usr/bin/env python2
+
+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(CField, self).__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 { unsigned 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 { unsigned 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 { unsigned i; for (i = 0; i < %d; ++i) { %s } }"\
+ " while(0);" % (
+ self.len,
+ self.type.get_swap_to_host_code(struct, "%s[i]" % var))
+ else:
+ # nelem_field already swapped to host here...
+ return (
+ "do { unsigned 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(CStruct, self).__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(CSimpleType, self).__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(CStructType, self).__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(CMessage, self).__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_vla_param_names(self):
+ return [self.get_alloc_func_vla_field_length_name(f)
+ for f in self.fields
+ if f.nelem_field is not None]
+
+ def get_alloc_func_decl(self):
+ return "%s* %s(struct vapi_ctx_s *ctx%s)" % (
+ self.get_c_name(),
+ self.get_alloc_func_name(),
+ "".join([", size_t %s" % n for n in
+ self.get_alloc_vla_param_names()]))
+
+ 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
+ ])),
+ " /* cast here required to play nicely with C++ world ... */",
+ " msg = (%s*)vapi_msg_alloc(ctx, size);" % self.get_c_name(),
+ " 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 message")
+ 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 ' ~0,',
+ ' sizeof(%s),' % self.get_c_name(),
+ ' (generic_swap_fn_t)%s,' % self.get_swap_to_be_func_name(),
+ ' (generic_swap_fn_t)%s,' % self.get_swap_to_host_func_name(),
+ ' ~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_unified_header(parser, logger, j, io, name):
+ logger.info("Generating header `%s'" % 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 <stdlib.h>")
+ print("#include <stddef.h>")
+ print("#include <arpa/inet.h>")
+ print("#include <vapi/vapi_internal.h>")
+ print("#include <vapi/vapi.h>")
+ print("#include <vapi/vapi_dbg.h>")
+ print("")
+ print("#ifdef __cplusplus")
+ print("extern \"C\" {")
+ print("#endif")
+ if name == "vpe.api.vapi.h":
+ print("")
+ print("static inline vapi_error_e vapi_send_with_control_ping "
+ "(vapi_ctx_t ctx, void * msg, u32 context);")
+ else:
+ print("#include <vapi/vpe.api.vapi.h>")
+ print("")
+ for m in parser.messages_by_json[j].values():
+ print("extern vapi_msg_id_t %s;" % m.get_msg_id_name())
+ 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 name == "vpe.api.vapi.h":
+ print("%s" % vapi_send_with_control_ping)
+ print("")
+
+ print("#ifdef __cplusplus")
+ print("}")
+ print("#endif")
+ print("")
+ print("#endif")
+ sys.stdout = orig_stdout
+
+
+def json_to_c_header_name(json_name):
+ if json_name.endswith(".json"):
+ return "%s.vapi.h" % os.path.splitext(json_name)[0]
+ raise Exception("Unexpected json name `%s'!" % json_name)
+
+
+def 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_c_header_name(j)), "w") as io:
+ gen_json_unified_header(
+ parser, logger, j, io, json_to_c_header_name(j))
+
+
+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 C API generator")
+ argparser.add_argument('files', metavar='api-file', action='append',
+ type=str, help='json api file'
+ '(may be specified multiple times)')
+ argparser.add_argument('--prefix', action='store', default=None,
+ help='path prefix')
+ 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_common.h b/src/vpp-api/vapi/vapi_common.h
new file mode 100644
index 00000000..ce64469d
--- /dev/null
+++ b/src/vpp-api/vapi/vapi_common.h
@@ -0,0 +1,61 @@
+/*
+ *------------------------------------------------------------------
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *------------------------------------------------------------------
+ */
+
+#ifndef vapi_common_h_included
+#define vapi_common_h_included
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef enum
+{
+ VAPI_OK = 0, /**< success */
+ VAPI_EINVAL, /**< invalid value encountered */
+ VAPI_EAGAIN, /**< operation would block */
+ VAPI_ENOTSUP, /**< operation not supported */
+ VAPI_ENOMEM, /**< out of memory */
+ VAPI_ENORESP, /**< no response to request */
+ VAPI_EMAP_FAIL, /**< failure while mapping api */
+ VAPI_ECON_FAIL, /**< failure while connecting to vpp */
+ VAPI_EINCOMPATIBLE, /**< fundamental incompatibility while connecting to vpp
+ (control ping/control ping reply mismatch) */
+ VAPI_MUTEX_FAILURE, /**< failure manipulating internal mutex(es) */
+ VAPI_EUSER, /**< user error used for breaking dispatch,
+ never used by VAPI */
+} vapi_error_e;
+
+typedef enum
+{
+ VAPI_MODE_BLOCKING = 1, /**< operations block until response received */
+ VAPI_MODE_NONBLOCKING = 2, /**< operations never block */
+} vapi_mode_e;
+
+typedef enum
+{
+ VAPI_WAIT_FOR_READ, /**< wait until some message is readable */
+ VAPI_WAIT_FOR_WRITE, /**< wait until a message can be written */
+ VAPI_WAIT_FOR_READ_WRITE, /**< wait until a read or write can be done */
+} vapi_wait_mode_e;
+
+typedef int vapi_msg_id_t;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/vpp-api/vapi/vapi_cpp_gen.py b/src/vpp-api/vapi/vapi_cpp_gen.py
new file mode 100755
index 00000000..3010f3e1
--- /dev/null
+++ b/src/vpp-api/vapi/vapi_cpp_gen.py
@@ -0,0 +1,263 @@
+#!/usr/bin/env python2
+
+import argparse
+import os
+import sys
+import logging
+from vapi_c_gen import CField, CStruct, CSimpleType, CStructType, CMessage, \
+ json_to_c_header_name
+from vapi_json_parser import JsonParser
+
+
+class CppField(CField):
+ def __init__(
+ self,
+ field_name,
+ field_type,
+ array_len=None,
+ nelem_field=None):
+ super(CppField, self).__init__(
+ field_name, field_type, array_len, nelem_field)
+
+
+class CppStruct(CStruct):
+ def __init__(self, name, fields):
+ super(CppStruct, self).__init__(name, fields)
+
+
+class CppSimpleType (CSimpleType):
+ def __init__(self, name):
+ super(CppSimpleType, self).__init__(name)
+
+
+class CppStructType (CStructType, CppStruct):
+ def __init__(self, definition, typedict, field_class):
+ super(CppStructType, self).__init__(definition, typedict, field_class)
+
+
+class CppMessage (CMessage):
+ def __init__(self, logger, definition, typedict,
+ struct_type_class, simple_type_class, field_class):
+ super(CppMessage, self).__init__(
+ logger, definition, typedict, struct_type_class,
+ simple_type_class, field_class)
+
+ def get_swap_to_be_template_instantiation(self):
+ return "\n".join([
+ "template <> inline void vapi_swap_to_be<%s>(%s *msg)" %
+ (self.get_c_name(), self.get_c_name()),
+ "{",
+ " %s(msg);" % self.get_swap_to_be_func_name(),
+ "}",
+ ])
+
+ def get_swap_to_host_template_instantiation(self):
+ return "\n".join([
+ "template <> inline void vapi_swap_to_host<%s>(%s *msg)" %
+ (self.get_c_name(), self.get_c_name()),
+ "{",
+ " %s(msg);" % self.get_swap_to_host_func_name(),
+ "}",
+ ])
+
+ def get_alloc_template_instantiation(self):
+ return "\n".join([
+ "template <> inline %s* vapi_alloc<%s%s>"
+ "(Connection &con%s)" %
+ (self.get_c_name(), self.get_c_name(),
+ ", size_t" * len(self.get_alloc_vla_param_names()),
+ "".join([", size_t %s" % n for n in
+ self.get_alloc_vla_param_names()])
+ ),
+ "{",
+ " %s* result = %s(con.vapi_ctx%s);" %
+ (self.get_c_name(), self.get_alloc_func_name(),
+ "".join([", %s" % n
+ for n in self.get_alloc_vla_param_names()])),
+ "#if VAPI_CPP_DEBUG_LEAKS",
+ " con.on_shm_data_alloc(result);",
+ "#endif",
+ " return result;",
+ "}",
+ ])
+
+ def get_cpp_name(self):
+ return "%s%s" % (self.name[0].upper(), self.name[1:])
+
+ def get_req_template_name(self):
+ if self.is_dump():
+ template = "Dump"
+ else:
+ template = "Request"
+
+ return "%s<%s, %s%s>" % (
+ template,
+ self.get_c_name(),
+ self.reply.get_c_name(),
+ "".join([", size_t"] * len(self.get_alloc_vla_param_names()))
+ )
+
+ def get_req_template_instantiation(self):
+ return "template class %s;" % self.get_req_template_name()
+
+ def get_type_alias(self):
+ return "using %s = %s;" % (
+ self.get_cpp_name(), self.get_req_template_name())
+
+ def get_reply_template_name(self):
+ return "Msg<%s>" % (self.get_c_name())
+
+ def get_reply_type_alias(self):
+ return "using %s = %s;" % (
+ self.get_cpp_name(), self.get_reply_template_name())
+
+ def get_msg_class_instantiation(self):
+ return "template class Msg<%s>;" % self.get_c_name()
+
+ def get_get_msg_id_t_instantiation(self):
+ return "\n".join([
+ ("template <> inline vapi_msg_id_t vapi_get_msg_id_t<%s>()"
+ % self.get_c_name()),
+ "{",
+ " return ::%s; " % self.get_msg_id_name(),
+ "}",
+ "",
+ ("template <> inline vapi_msg_id_t "
+ "vapi_get_msg_id_t<Msg<%s>>()" % self.get_c_name()),
+ "{",
+ " return ::%s; " % self.get_msg_id_name(),
+ "}",
+ ])
+
+ def get_cpp_constructor(self):
+ return '\n'.join([
+ ('static void __attribute__((constructor)) '
+ '__vapi_cpp_constructor_%s()'
+ % self.name),
+ '{',
+ (' vapi::vapi_msg_set_msg_id<%s>(%s);' % (
+ self.get_c_name(), self.get_msg_id_name())),
+ '}',
+ ])
+
+
+def gen_json_header(parser, logger, j, io, gen_h_prefix, add_debug_comments):
+ logger.info("Generating header `%s'" % io.name)
+ orig_stdout = sys.stdout
+ sys.stdout = io
+ include_guard = "__included_hpp_%s" % (
+ j.replace(".", "_").replace("/", "_").replace("-", "_"))
+ print("#ifndef %s" % include_guard)
+ print("#define %s" % include_guard)
+ print("")
+ print("#include <vapi/vapi.hpp>")
+ print("#include <%s%s>" % (gen_h_prefix, json_to_c_header_name(j)))
+ print("")
+ print("namespace vapi {")
+ print("")
+ for m in parser.messages_by_json[j].values():
+ # utility functions need to go first, otherwise internal instantiation
+ # causes headaches ...
+ if add_debug_comments:
+ print("/* m.get_swap_to_be_template_instantiation() */")
+ print("%s" % m.get_swap_to_be_template_instantiation())
+ print("")
+ if add_debug_comments:
+ print("/* m.get_swap_to_host_template_instantiation() */")
+ print("%s" % m.get_swap_to_host_template_instantiation())
+ print("")
+ if add_debug_comments:
+ print("/* m.get_get_msg_id_t_instantiation() */")
+ print("%s" % m.get_get_msg_id_t_instantiation())
+ print("")
+ if add_debug_comments:
+ print("/* m.get_cpp_constructor() */")
+ print("%s" % m.get_cpp_constructor())
+ print("")
+ if not m.is_reply():
+ if add_debug_comments:
+ print("/* m.get_alloc_template_instantiation() */")
+ print("%s" % m.get_alloc_template_instantiation())
+ print("")
+ if add_debug_comments:
+ print("/* m.get_msg_class_instantiation() */")
+ print("%s" % m.get_msg_class_instantiation())
+ print("")
+ if m.is_reply():
+ if add_debug_comments:
+ print("/* m.get_reply_type_alias() */")
+ print("%s" % m.get_reply_type_alias())
+ continue
+ if add_debug_comments:
+ print("/* m.get_req_template_instantiation() */")
+ print("%s" % m.get_req_template_instantiation())
+ print("")
+ if add_debug_comments:
+ print("/* m.get_type_alias() */")
+ print("%s" % m.get_type_alias())
+ print("")
+ print("}") # namespace vapi
+
+ print("#endif")
+ sys.stdout = orig_stdout
+
+
+def json_to_cpp_header_name(json_name):
+ if json_name.endswith(".json"):
+ return "%s.vapi.hpp" % os.path.splitext(json_name)[0]
+ raise Exception("Unexpected json name `%s'!" % json_name)
+
+
+def gen_cpp_headers(parser, logger, prefix, gen_h_prefix,
+ add_debug_comments=False):
+ if prefix == "" or prefix is None:
+ prefix = ""
+ else:
+ prefix = "%s/" % prefix
+ if gen_h_prefix is None:
+ gen_h_prefix = ""
+ else:
+ gen_h_prefix = "%s/" % gen_h_prefix
+ for j in parser.json_files:
+ with open('%s%s' % (prefix, json_to_cpp_header_name(j)), "w") as io:
+ gen_json_header(parser, logger, j, io,
+ gen_h_prefix, add_debug_comments)
+
+
+if __name__ == '__main__':
+ try:
+ verbose = int(os.getenv("V", 0))
+ except:
+ verbose = 0
+
+ if verbose >= 2:
+ log_level = 10
+ elif verbose == 1:
+ log_level = 20
+ else:
+ log_level = 40
+
+ logging.basicConfig(stream=sys.stdout, level=log_level)
+ logger = logging.getLogger("VAPI CPP GEN")
+ logger.setLevel(log_level)
+
+ argparser = argparse.ArgumentParser(description="VPP C++ API generator")
+ argparser.add_argument('files', metavar='api-file', action='append',
+ type=str, help='json api file'
+ '(may be specified multiple times)')
+ argparser.add_argument('--prefix', action='store', default=None,
+ help='path prefix')
+ argparser.add_argument('--gen-h-prefix', action='store', default=None,
+ help='generated C header prefix')
+ args = argparser.parse_args()
+
+ jsonparser = JsonParser(logger, args.files,
+ simple_type_class=CppSimpleType,
+ struct_type_class=CppStructType,
+ field_class=CppField,
+ message_class=CppMessage)
+
+ gen_cpp_headers(jsonparser, logger, args.prefix, args.gen_h_prefix)
+
+ for e in jsonparser.exceptions:
+ logger.error(e)
diff --git a/src/vpp-api/vapi/vapi_dbg.h b/src/vpp-api/vapi/vapi_dbg.h
new file mode 100644
index 00000000..ec3a3006
--- /dev/null
+++ b/src/vpp-api/vapi/vapi_dbg.h
@@ -0,0 +1,77 @@
+/*
+ *------------------------------------------------------------------
+ * 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)
+#define VAPI_CPP_DEBUG_LEAKS (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_doc.md b/src/vpp-api/vapi/vapi_doc.md
new file mode 100644
index 00000000..0e7e29dd
--- /dev/null
+++ b/src/vpp-api/vapi/vapi_doc.md
@@ -0,0 +1,155 @@
+# VPP API module {#vapi_doc}
+
+## Overview
+
+VPP API module allows communicating with VPP over shared memory interface.
+The API consists of 3 parts:
+
+* common code - low-level API
+* generated code - high-level API
+* code generator - to generate your own high-level API e.g. for custom plugins
+
+### Common code
+
+#### C common code
+
+C common code represents the basic, low-level API, providing functions to
+connect/disconnect, perform message discovery and send/receive messages.
+The C variant is in vapi.h.
+
+#### C++ common code
+
+C++ is provided by vapi.hpp and contains high-level API templates,
+which are specialized by generated code.
+
+### Generated code
+
+Each API file present in the source tree is automatically translated to JSON
+file, which the code generator parses and generates either C (`vapi_c_gen.py`)
+or C++ (`vapi_cpp_gen.py`) code.
+
+This can then be included in the client application and provides convenient way
+to interact with VPP. This includes:
+
+* automatic byte-swapping
+* automatic request-response matching based on context
+* automatic casts to appropriate types (type-safety) when calling callbacks
+* automatic sending of control-pings for dump messages
+
+The API supports two modes of operation:
+
+* blocking
+* non-blocking
+
+In blocking mode, whenever an operation is initiated, the code waits until it
+can finish. This means that when sending a message, the call blocks until
+the message can be written to shared memory. Similarly, receiving a message
+blocks until a message becomes available. On higher level, this also means that
+when doing a request (e.g. `show_version`), the call blocks until a response
+comes back (e.g. `show_version_reply`).
+
+In non-blocking mode, these are decoupled, the API returns VAPI_EAGAIN whenever
+an operation cannot be performed and after sending a request, it's up to
+the client to wait for and process a response.
+
+### Code generator
+
+Python code generator comes in two flavors - C and C++ and generates high-level
+API headers. All the code is stored in the headers.
+
+## Usage
+
+### Low-level API
+
+Refer to inline API documentation in doxygen format in `vapi.h` header
+for description of functions. It's recommened to use the safer, high-level
+API provided by specialized headers (e.g. `vpe.api.vapi.h`
+or `vpe.api.vapi.hpp`).
+
+#### C high-level API
+
+##### Callbacks
+
+The C high-level API is strictly callback-based for maximum efficiency.
+Whenever an operation is initiated a callback with a callback context is part
+of that operation. The callback is then invoked when the response (or multiple
+responses) arrive which are tied to the request. Also, callbacks are invoked
+whenever an event arrives, if such callback is registered. All the pointers
+to responses/events point to shared memory and are immediately freed after
+callback finishes so the client needs to extract/copy any data in which it
+is interested in.
+
+#### Blocking mode
+
+In simple blocking mode, the whole operation (being a simple request or a dump)
+is finished and it's callback is called (potentially multiple times for dumps)
+during function call.
+
+Example pseudo-code for a simple request in this mode:
+
+`
+vapi_show_version(message, callback, callback_context)
+
+1. generate unique internal context and assign it to message.header.context
+2. byteswap the message to network byte order
+3. send message to vpp (message is now consumed and vpp will free it)
+4. create internal "outstanding request context" which stores the callback,
+ callback context and the internal context value
+5. call dispatch, which in this mode receives and processes responses until
+ the internal "outstanding requests" queue is empty. In blocking mode, this
+ queue always contains at most one item.
+`
+
+**Note**: it's possible for different - unrelated callbacks to be called before
+the response callbacks is called in cases where e.g. events are stored
+in shared memory queue.
+
+#### Non-blocking mode
+
+In non-blocking mode, all the requests are only byte-swapped and the context
+information along with callbacks is stored locally (so in the above example,
+only steps 1-4 are executed and step 5 is skipped). Calling dispatch is up to
+the client application. This allows to alternate between sending/receiving
+messages or have a dedicated thread which calls dispatch.
+
+### C++ high level API
+
+#### Callbacks
+
+In C++ API, the response is automatically tied to the corresponding `Request`,
+`Dump` or `Event_registration` object. Optionally a callback might be specified,
+which then gets called when the response is received.
+
+**Note**: responses take up shared memory space and should be freed either
+manually (in case of result sets) or automatically (by destroying the object
+owning them) when no longer needed. Once a Request or Dump object was executed,
+it cannot be re-sent, since the request itself (stores in shared memory)
+is consumed by vpp and inaccessible (set to nullptr) anymore.
+
+#### Usage
+
+#### Requests & dumps
+
+0. Create on object of `Connection` type and call `connect()` to connect to vpp.
+1. Create an object of `Request` or `Dump` type using it's typedef (e.g.
+ `Show_version`)
+2. Use `get_request()` to obtain and manipulate the underlying request if
+ required.
+3. Issue `execute()` to send the request.
+4. Use either `wait_for_response()` or `dispatch()` to wait for the response.
+5. Use `get_response_state()` to get the state and `get_response()` to read
+ the response.
+
+#### Events
+
+0. Create a `Connection` and execute the appropriate `Request` to subscribe to
+ events (e.g. `Want_stats`)
+1. Create an `Event_registration` with a template argument being the type of
+ event you are insterested in.
+2. Call `dispatch()` or `wait_for_response()` to wait for the event. A callback
+ will be called when an event occurs (if passed to `Event_registration()`
+ constructor). Alternatively, read the result set.
+
+**Note**: events stored in the result set take up space in shared memory
+and should be freed regularly (e.g. in the callback, once the event is
+processed).
diff --git a/src/vpp-api/vapi/vapi_internal.h b/src/vpp-api/vapi/vapi_internal.h
new file mode 100644
index 00000000..2c51c673
--- /dev/null
+++ b/src/vpp-api/vapi/vapi_internal.h
@@ -0,0 +1,138 @@
+/*
+ *------------------------------------------------------------------
+ * 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 <endian.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..
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+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/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;
+ int context_offset;
+ int 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;
+
+vapi_msg_id_t vapi_register_msg (vapi_message_desc_t * msg);
+u16 vapi_lookup_vl_msg_id (vapi_ctx_t ctx, vapi_msg_id_t id);
+vapi_msg_id_t vapi_lookup_vapi_msg_id_t (vapi_ctx_t ctx, u16 vl_msg_id);
+int vapi_get_client_index (vapi_ctx_t ctx);
+bool vapi_is_nonblocking (vapi_ctx_t ctx);
+bool vapi_requests_empty (vapi_ctx_t ctx);
+bool vapi_requests_full (vapi_ctx_t ctx);
+size_t vapi_get_request_count (vapi_ctx_t ctx);
+size_t vapi_get_max_request_count (vapi_ctx_t ctx);
+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);
+bool vapi_msg_is_with_context (vapi_msg_id_t id);
+size_t vapi_get_message_count();
+const char *vapi_get_msg_name(vapi_msg_id_t id);
+
+vapi_error_e vapi_producer_lock (vapi_ctx_t ctx);
+vapi_error_e vapi_producer_unlock (vapi_ctx_t ctx);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/vpp-api/vapi/vapi_json_parser.py b/src/vpp-api/vapi/vapi_json_parser.py
new file mode 100644
index 00000000..4e62720d
--- /dev/null
+++ b/src/vpp-api/vapi/vapi_json_parser.py
@@ -0,0 +1,305 @@
+#!/usr/bin/env python2
+
+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(object):
+
+ 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(object):
+ def __init__(self, name):
+ self.name = name
+
+
+class SimpleType (Type):
+
+ def __init__(self, name):
+ super(SimpleType, self).__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(object):
+
+ def __init__(self, name, fields):
+ self.name = name
+ self.fields = fields
+ self.field_names = [n.name for n in self.fields]
+
+
+class Message(object):
+
+ def __init__(self, logger, definition, typedict,
+ struct_type_class, simple_type_class, field_class):
+ self.request = None
+ self.logger = logger
+ m = definition
+ logger.debug("Parsing message definition `%s'" % m)
+ 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(object):
+ 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)
+ m.reply.request = m
+ 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}