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