diff options
Diffstat (limited to 'src/vpp-api/vapi')
-rw-r--r-- | src/vpp-api/vapi/Makefile.am | 74 | ||||
-rw-r--r-- | src/vpp-api/vapi/libvapiclient.map | 44 | ||||
-rw-r--r-- | src/vpp-api/vapi/vapi.c | 933 | ||||
-rw-r--r-- | src/vpp-api/vapi/vapi.h | 263 | ||||
-rw-r--r-- | src/vpp-api/vapi/vapi.hpp | 905 | ||||
-rwxr-xr-x | src/vpp-api/vapi/vapi_c_gen.py | 693 | ||||
-rw-r--r-- | src/vpp-api/vapi/vapi_common.h | 61 | ||||
-rwxr-xr-x | src/vpp-api/vapi/vapi_cpp_gen.py | 263 | ||||
-rw-r--r-- | src/vpp-api/vapi/vapi_dbg.h | 77 | ||||
-rw-r--r-- | src/vpp-api/vapi/vapi_doc.md | 155 | ||||
-rw-r--r-- | src/vpp-api/vapi/vapi_internal.h | 138 | ||||
-rw-r--r-- | src/vpp-api/vapi/vapi_json_parser.py | 305 |
12 files changed, 3911 insertions, 0 deletions
diff --git a/src/vpp-api/vapi/Makefile.am b/src/vpp-api/vapi/Makefile.am new file mode 100644 index 00000000..74b2b47e --- /dev/null +++ b/src/vpp-api/vapi/Makefile.am @@ -0,0 +1,74 @@ +# Copyright (c) 2017 Cisco and/or its affiliates. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +AUTOMAKE_OPTIONS = foreign +ACLOCAL_AMFLAGS = -I m4 +AM_LIBTOOLFLAGS = --quiet + +AM_CFLAGS = -Wall -I${top_srcdir} -I${top_builddir} -I. -I$(top_srcdir)/vpp-api/ + +AM_LDFLAGS = -shared -avoid-version -rpath /none -no-undefined + +bin_PROGRAMS = +noinst_LTLIBRARIES = +CLEANDIRS = + +vapi/%.api.vapi.h: %.api.json vapi_c_gen.py vapi_json_parser.py + @echo " VAPI C GEN $< " $@ ; \ + mkdir -p `dirname $@` ; \ + $(top_srcdir)/vpp-api/vapi/vapi_c_gen.py --prefix=vapi $< + +vapi/%.api.vapi.hpp: %.api.json vapi_cpp_gen.py vapi_c_gen.py vapi_json_parser.py + @echo " VAPI CPP GEN $< " $@ ; \ + mkdir -p `dirname $@` ; \ + $(top_srcdir)/vpp-api/vapi/vapi_cpp_gen.py --prefix=vapi --gen-h-prefix=vapi $< + +%.api.json: + find $(top_builddir) -name '$@' | xargs ln -s + +BUILT_SOURCES = $(shell find $(top_builddir) -name '*.api.json' | xargs -n1 basename) \ + $(patsubst %.api.json,vapi/%.api.vapi.h,$(JSON_FILES)) \ + $(patsubst %.api.json,vapi/%.api.vapi.hpp,$(JSON_FILES)) + +vapi.c: $(BUILT_SOURCES) + +JSON_FILES = $(wildcard *.api.json) + +lib_LTLIBRARIES = libvapiclient.la + +libvapiclient_la_SOURCES = vapi.c + +libvapiclient_la_DEPENDENCIES = libvapiclient.map + +libvapiclient_la_LIBADD = -lpthread -lm -lrt \ + $(top_builddir)/libvppinfra.la \ + $(top_builddir)/libvlibmemoryclient.la \ + $(top_builddir)/libsvm.la + +libvapiclient_la_LDFLAGS = \ + -Wl,-L$(top_builddir)/.libs,--whole-archive,--no-whole-archive \ + -Wl,--version-script=$(srcdir)/libvapiclient.map,-lrt + +libvapiclient_la_CPPFLAGS = -I. -I$(top_builddir)/vpp-api/vapi + +vapiincludedir = $(includedir)/vapi + +vapiinclude_HEADERS = vapi.h \ + vapi.hpp \ + vapi_dbg.h \ + vapi_common.h \ + vapi_internal.h \ + $(patsubst %.api.json,vapi/%.api.vapi.h,$(JSON_FILES)) \ + $(patsubst %.api.json,vapi/%.api.vapi.hpp,$(JSON_FILES)) + +# vi:syntax=automake diff --git a/src/vpp-api/vapi/libvapiclient.map b/src/vpp-api/vapi/libvapiclient.map new file mode 100644 index 00000000..6b58d1e9 --- /dev/null +++ b/src/vpp-api/vapi/libvapiclient.map @@ -0,0 +1,44 @@ + +VAPICLIENT_17.07 { + global: + vapi_msg_alloc; + vapi_msg_free; + vapi_ctx_alloc; + vapi_ctx_free; + vapi_is_msg_available; + vapi_connect; + vapi_disconnect; + vapi_get_fd; + vapi_send; + vapi_send2; + vapi_recv; + vapi_wait; + vapi_dispatch_one; + vapi_dispatch; + vapi_set_event_cb; + vapi_clear_event_cb; + vapi_set_generic_event_cb; + vapi_clear_generic_event_cb; + vapi_get_client_index; + vapi_register_msg; + vapi_get_client_index; + vapi_is_nonblocking; + vapi_requests_empty; + vapi_requests_full; + vapi_gen_req_context; + vapi_producer_lock; + vapi_send_with_control_ping; + vapi_store_request; + vapi_is_nonblocking; + vapi_producer_unlock; + vapi_lookup_vl_msg_id; + vapi_lookup_vapi_msg_id_t; + vapi_msg_is_with_context; + vapi_get_context_offset; + vapi_msg_id_control_ping; + vapi_msg_id_control_ping_reply; + vapi_get_message_count; + vapi_get_msg_name; + + local: *; +}; diff --git a/src/vpp-api/vapi/vapi.c b/src/vpp-api/vapi/vapi.c new file mode 100644 index 00000000..3150d2b4 --- /dev/null +++ b/src/vpp-api/vapi/vapi.c @@ -0,0 +1,933 @@ +/* + *------------------------------------------------------------------ + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *------------------------------------------------------------------ + */ + +#include <stdlib.h> +#include <stdio.h> +#include <stdint.h> +#include <arpa/inet.h> +#include <stddef.h> +#include <assert.h> + +#include <vpp-api/vapi/vapi_dbg.h> +#include <vpp-api/vapi/vapi.h> +#include <vpp-api/vapi/vapi_internal.h> +#include <vppinfra/types.h> +#include <vlibapi/api_common.h> +#include <vlibmemory/api_common.h> + +/* we need to use control pings for some stuff and because we're forced to put + * the code in headers, we need a way to be able to grab the ids of these + * messages - so declare them here as extern */ +vapi_msg_id_t vapi_msg_id_control_ping = 0; +vapi_msg_id_t vapi_msg_id_control_ping_reply = 0; + +struct +{ + size_t count; + vapi_message_desc_t **msgs; + size_t max_len_name_with_crc; +} __vapi_metadata; + +typedef struct +{ + u32 context; + vapi_cb_t callback; + void *callback_ctx; + bool is_dump; +} vapi_req_t; + +static const u32 context_counter_mask = (1 << 31); + +typedef struct +{ + vapi_error_e (*cb) (vapi_ctx_t ctx, void *callback_ctx, vapi_msg_id_t id, + void *payload); + void *ctx; +} vapi_generic_cb_with_ctx; + +typedef struct +{ + vapi_error_e (*cb) (vapi_ctx_t ctx, void *callback_ctx, void *payload); + void *ctx; +} vapi_event_cb_with_ctx; + +struct vapi_ctx_s +{ + vapi_mode_e mode; + int requests_size; /* size of the requests array (circular queue) */ + int requests_start; /* index of first request */ + int requests_count; /* number of used slots */ + vapi_req_t *requests; + u32 context_counter; + vapi_generic_cb_with_ctx generic_cb; + vapi_event_cb_with_ctx *event_cbs; + u16 *vapi_msg_id_t_to_vl_msg_id; + u16 vl_msg_id_max; + vapi_msg_id_t *vl_msg_id_to_vapi_msg_t; + bool connected; + pthread_mutex_t requests_mutex; +}; + +u32 +vapi_gen_req_context (vapi_ctx_t ctx) +{ + ++ctx->context_counter; + ctx->context_counter %= context_counter_mask; + return ctx->context_counter | context_counter_mask; +} + +size_t +vapi_get_request_count (vapi_ctx_t ctx) +{ + return ctx->requests_count; +} + +bool +vapi_requests_full (vapi_ctx_t ctx) +{ + return (ctx->requests_count == ctx->requests_size); +} + +bool +vapi_requests_empty (vapi_ctx_t ctx) +{ + return (0 == ctx->requests_count); +} + +static int +vapi_requests_end (vapi_ctx_t ctx) +{ + return (ctx->requests_start + ctx->requests_count) % ctx->requests_size; +} + +void +vapi_store_request (vapi_ctx_t ctx, u32 context, bool is_dump, + vapi_cb_t callback, void *callback_ctx) +{ + assert (!vapi_requests_full (ctx)); + /* if the mutex is not held, bad things will happen */ + assert (0 != pthread_mutex_trylock (&ctx->requests_mutex)); + const int requests_end = vapi_requests_end (ctx); + vapi_req_t *slot = &ctx->requests[requests_end]; + slot->is_dump = is_dump; + slot->context = context; + slot->callback = callback; + slot->callback_ctx = callback_ctx; + VAPI_DBG ("stored@%d: context:%x (start is @%d)", requests_end, context, + ctx->requests_start); + ++ctx->requests_count; + assert (!vapi_requests_empty (ctx)); +} + +#if VAPI_DEBUG_ALLOC +struct to_be_freed_s; +struct to_be_freed_s +{ + void *v; + struct to_be_freed_s *next; +}; + +static struct to_be_freed_s *to_be_freed = NULL; + +void +vapi_add_to_be_freed (void *v) +{ + struct to_be_freed_s *prev = NULL; + struct to_be_freed_s *tmp; + tmp = to_be_freed; + while (tmp && tmp->v) + { + prev = tmp; + tmp = tmp->next; + } + if (!tmp) + { + if (!prev) + { + tmp = to_be_freed = calloc (1, sizeof (*to_be_freed)); + } + else + { + tmp = prev->next = calloc (1, sizeof (*to_be_freed)); + } + } + VAPI_DBG ("To be freed %p", v); + tmp->v = v; +} + +void +vapi_trace_free (void *v) +{ + struct to_be_freed_s *tmp = to_be_freed; + while (tmp && tmp->v != v) + { + tmp = tmp->next; + } + if (tmp && tmp->v == v) + { + VAPI_DBG ("Freed %p", v); + tmp->v = NULL; + } + else + { + VAPI_ERR ("Trying to free untracked pointer %p", v); + abort (); + } +} + +void +vapi_to_be_freed_validate () +{ + struct to_be_freed_s *tmp = to_be_freed; + while (tmp) + { + if (tmp->v) + { + VAPI_ERR ("Unfreed msg %p!", tmp->v); + } + tmp = tmp->next; + } +} + +#endif + +void * +vapi_msg_alloc (vapi_ctx_t ctx, size_t size) +{ + if (!ctx->connected) + { + return NULL; + } + void *rv = vl_msg_api_alloc_or_null (size); + return rv; +} + +void +vapi_msg_free (vapi_ctx_t ctx, void *msg) +{ + if (!ctx->connected) + { + return; + } +#if VAPI_DEBUG_ALLOC + vapi_trace_free (msg); +#endif + vl_msg_api_free (msg); +} + +vapi_msg_id_t +vapi_lookup_vapi_msg_id_t (vapi_ctx_t ctx, u16 vl_msg_id) +{ + if (vl_msg_id <= ctx->vl_msg_id_max) + { + return ctx->vl_msg_id_to_vapi_msg_t[vl_msg_id]; + } + return ~0; +} + +vapi_error_e +vapi_ctx_alloc (vapi_ctx_t * result) +{ + vapi_ctx_t ctx = calloc (1, sizeof (struct vapi_ctx_s)); + if (!ctx) + { + return VAPI_ENOMEM; + } + ctx->context_counter = 0; + ctx->vapi_msg_id_t_to_vl_msg_id = + malloc (__vapi_metadata.count * + sizeof (*ctx->vapi_msg_id_t_to_vl_msg_id)); + if (!ctx->vapi_msg_id_t_to_vl_msg_id) + { + goto fail; + } + ctx->event_cbs = calloc (__vapi_metadata.count, sizeof (*ctx->event_cbs)); + if (!ctx->event_cbs) + { + goto fail; + } + pthread_mutex_init (&ctx->requests_mutex, NULL); + *result = ctx; + return VAPI_OK; +fail: + vapi_ctx_free (ctx); + return VAPI_ENOMEM; +} + +void +vapi_ctx_free (vapi_ctx_t ctx) +{ + assert (!ctx->connected); + free (ctx->requests); + free (ctx->vapi_msg_id_t_to_vl_msg_id); + free (ctx->event_cbs); + free (ctx->vl_msg_id_to_vapi_msg_t); + pthread_mutex_destroy (&ctx->requests_mutex); + free (ctx); +} + +bool +vapi_is_msg_available (vapi_ctx_t ctx, vapi_msg_id_t id) +{ + return vapi_lookup_vl_msg_id (ctx, id) != UINT16_MAX; +} + +vapi_error_e +vapi_connect (vapi_ctx_t ctx, const char *name, + const char *chroot_prefix, + int max_outstanding_requests, + int response_queue_size, vapi_mode_e mode) +{ + if (response_queue_size <= 0 || max_outstanding_requests <= 0) + { + return VAPI_EINVAL; + } + ctx->requests_size = max_outstanding_requests; + const size_t size = ctx->requests_size * sizeof (*ctx->requests); + void *tmp = realloc (ctx->requests, size); + if (!tmp) + { + return VAPI_ENOMEM; + } + ctx->requests = tmp; + memset (ctx->requests, 0, size); + /* coverity[MISSING_LOCK] - 177211 requests_mutex is not needed here */ + ctx->requests_start = ctx->requests_count = 0; + if (chroot_prefix) + { + VAPI_DBG ("set memory root path `%s'", chroot_prefix); + vl_set_memory_root_path ((char *) chroot_prefix); + } + static char api_map[] = "/vpe-api"; + VAPI_DBG ("client api map `%s'", api_map); + if ((vl_client_api_map (api_map)) < 0) + { + return VAPI_EMAP_FAIL; + } + VAPI_DBG ("connect client `%s'", name); + if (vl_client_connect ((char *) name, 0, response_queue_size) < 0) + { + vl_client_api_unmap (); + return VAPI_ECON_FAIL; + } +#if VAPI_DEBUG_CONNECT + VAPI_DBG ("start probing messages"); +#endif + int rv; + int i; + for (i = 0; i < __vapi_metadata.count; ++i) + { + vapi_message_desc_t *m = __vapi_metadata.msgs[i]; + u8 scratch[m->name_with_crc_len + 1]; + memcpy (scratch, m->name_with_crc, m->name_with_crc_len + 1); + u32 id = vl_api_get_msg_index (scratch); + if (~0 != id) + { + if (id > UINT16_MAX) + { + VAPI_ERR ("Returned vl_msg_id `%u' > UINT16MAX `%u'!", id, + UINT16_MAX); + rv = VAPI_EINVAL; + goto fail; + } + if (id > ctx->vl_msg_id_max) + { + vapi_msg_id_t *tmp = realloc (ctx->vl_msg_id_to_vapi_msg_t, + sizeof + (*ctx->vl_msg_id_to_vapi_msg_t) * + (id + 1)); + if (!tmp) + { + rv = VAPI_ENOMEM; + goto fail; + } + ctx->vl_msg_id_to_vapi_msg_t = tmp; + ctx->vl_msg_id_max = id; + } + ctx->vl_msg_id_to_vapi_msg_t[id] = m->id; + ctx->vapi_msg_id_t_to_vl_msg_id[m->id] = id; +#if VAPI_DEBUG_CONNECT + VAPI_DBG ("Message `%s' has vl_msg_id `%u'", m->name_with_crc, + (unsigned) id); +#endif + } + else + { + ctx->vapi_msg_id_t_to_vl_msg_id[m->id] = UINT16_MAX; + VAPI_DBG ("Message `%s' not available", m->name_with_crc); + } + } +#if VAPI_DEBUG_CONNECT + VAPI_DBG ("finished probing messages"); +#endif + if (!vapi_is_msg_available (ctx, vapi_msg_id_control_ping) || + !vapi_is_msg_available (ctx, vapi_msg_id_control_ping_reply)) + { + VAPI_ERR + ("control ping or control ping reply not available, cannot connect"); + rv = VAPI_EINCOMPATIBLE; + goto fail; + } + ctx->mode = mode; + ctx->connected = true; + return VAPI_OK; +fail: + vl_client_disconnect (); + vl_client_api_unmap (); + return rv; +} + +vapi_error_e +vapi_disconnect (vapi_ctx_t ctx) +{ + if (!ctx->connected) + { + return VAPI_EINVAL; + } + vl_client_disconnect (); + vl_client_api_unmap (); +#if VAPI_DEBUG_ALLOC + vapi_to_be_freed_validate (); +#endif + ctx->connected = false; + return VAPI_OK; +} + +vapi_error_e +vapi_get_fd (vapi_ctx_t ctx, int *fd) +{ + return VAPI_ENOTSUP; +} + +vapi_error_e +vapi_send (vapi_ctx_t ctx, void *msg) +{ + vapi_error_e rv = VAPI_OK; + if (!ctx || !msg || !ctx->connected) + { + rv = VAPI_EINVAL; + goto out; + } + int tmp; + unix_shared_memory_queue_t *q = api_main.shmem_hdr->vl_input_queue; +#if VAPI_DEBUG + unsigned msgid = be16toh (*(u16 *) msg); + if (msgid <= ctx->vl_msg_id_max) + { + vapi_msg_id_t id = ctx->vl_msg_id_to_vapi_msg_t[msgid]; + if (id < __vapi_metadata.count) + { + VAPI_DBG ("send msg@%p:%u[%s]", msg, msgid, + __vapi_metadata.msgs[id]->name); + } + else + { + VAPI_DBG ("send msg@%p:%u[UNKNOWN]", msg, msgid); + } + } + else + { + VAPI_DBG ("send msg@%p:%u[UNKNOWN]", msg, msgid); + } +#endif + tmp = unix_shared_memory_queue_add (q, (u8 *) & msg, + VAPI_MODE_BLOCKING == + ctx->mode ? 0 : 1); + if (tmp < 0) + { + rv = VAPI_EAGAIN; + } +out: + VAPI_DBG ("vapi_send() rv = %d", rv); + return rv; +} + +vapi_error_e +vapi_send2 (vapi_ctx_t ctx, void *msg1, void *msg2) +{ + vapi_error_e rv = VAPI_OK; + if (!ctx || !msg1 || !msg2 || !ctx->connected) + { + rv = VAPI_EINVAL; + goto out; + } + unix_shared_memory_queue_t *q = api_main.shmem_hdr->vl_input_queue; +#if VAPI_DEBUG + unsigned msgid1 = be16toh (*(u16 *) msg1); + unsigned msgid2 = be16toh (*(u16 *) msg2); + const char *name1 = "UNKNOWN"; + const char *name2 = "UNKNOWN"; + if (msgid1 <= ctx->vl_msg_id_max) + { + vapi_msg_id_t id = ctx->vl_msg_id_to_vapi_msg_t[msgid1]; + if (id < __vapi_metadata.count) + { + name1 = __vapi_metadata.msgs[id]->name; + } + } + if (msgid2 <= ctx->vl_msg_id_max) + { + vapi_msg_id_t id = ctx->vl_msg_id_to_vapi_msg_t[msgid2]; + if (id < __vapi_metadata.count) + { + name2 = __vapi_metadata.msgs[id]->name; + } + } + VAPI_DBG ("send two: %u[%s], %u[%s]", msgid1, name1, msgid2, name2); +#endif + int tmp = unix_shared_memory_queue_add2 (q, (u8 *) & msg1, (u8 *) & msg2, + VAPI_MODE_BLOCKING == + ctx->mode ? 0 : 1); + if (tmp < 0) + { + rv = VAPI_EAGAIN; + } +out: + VAPI_DBG ("vapi_send() rv = %d", rv); + return rv; +} + +vapi_error_e +vapi_recv (vapi_ctx_t ctx, void **msg, size_t * msg_size) +{ + if (!ctx || !ctx->connected || !msg || !msg_size) + { + return VAPI_EINVAL; + } + vapi_error_e rv = VAPI_OK; + api_main_t *am = &api_main; + uword data; + + if (am->our_pid == 0) + { + return VAPI_EINVAL; + } + + unix_shared_memory_queue_t *q = am->vl_input_queue; + VAPI_DBG ("doing shm queue sub"); + int tmp = unix_shared_memory_queue_sub (q, (u8 *) & data, 0); + if (tmp == 0) + { +#if VAPI_DEBUG_ALLOC + vapi_add_to_be_freed ((void *) data); +#endif + msgbuf_t *msgbuf = + (msgbuf_t *) ((u8 *) data - offsetof (msgbuf_t, data)); + if (!msgbuf->data_len) + { + vapi_msg_free (ctx, (u8 *) data); + return VAPI_EAGAIN; + } + *msg = (u8 *) data; + *msg_size = ntohl (msgbuf->data_len); +#if VAPI_DEBUG + unsigned msgid = be16toh (*(u16 *) * msg); + if (msgid <= ctx->vl_msg_id_max) + { + vapi_msg_id_t id = ctx->vl_msg_id_to_vapi_msg_t[msgid]; + if (id < __vapi_metadata.count) + { + VAPI_DBG ("recv msg@%p:%u[%s]", *msg, msgid, + __vapi_metadata.msgs[id]->name); + } + else + { + VAPI_DBG ("recv msg@%p:%u[UNKNOWN]", *msg, msgid); + } + } + else + { + VAPI_DBG ("recv msg@%p:%u[UNKNOWN]", *msg, msgid); + } +#endif + } + else + { + rv = VAPI_EAGAIN; + } + return rv; +} + +vapi_error_e +vapi_wait (vapi_ctx_t ctx, vapi_wait_mode_e mode) +{ + return VAPI_ENOTSUP; +} + +static vapi_error_e +vapi_dispatch_response (vapi_ctx_t ctx, vapi_msg_id_t id, + u32 context, void *msg) +{ + int mrv; + if (0 != (mrv = pthread_mutex_lock (&ctx->requests_mutex))) + { + VAPI_DBG ("pthread_mutex_lock() failed, rv=%d:%s", mrv, strerror (mrv)); + return VAPI_MUTEX_FAILURE; + } + int tmp = ctx->requests_start; + const int requests_end = vapi_requests_end (ctx); + while (ctx->requests[tmp].context != context && tmp != requests_end) + { + ++tmp; + if (tmp == ctx->requests_size) + { + tmp = 0; + } + } + VAPI_DBG ("dispatch, search from %d, %s at %d", ctx->requests_start, + ctx->requests[tmp].context == context ? "matched" : "stopped", + tmp); + vapi_error_e rv = VAPI_OK; + if (ctx->requests[tmp].context == context) + { + while (ctx->requests_start != tmp) + { + VAPI_ERR ("No response to req with context=%u", + (unsigned) ctx->requests[tmp].context); + ctx->requests[ctx->requests_start].callback (ctx, + ctx->requests + [ctx-> + requests_start].callback_ctx, + VAPI_ENORESP, true, + NULL); + memset (&ctx->requests[ctx->requests_start], 0, + sizeof (ctx->requests[ctx->requests_start])); + ++ctx->requests_start; + --ctx->requests_count; + if (ctx->requests_start == ctx->requests_size) + { + ctx->requests_start = 0; + } + } + // now ctx->requests_start == tmp + int payload_offset = vapi_get_payload_offset (id); + void *payload = ((u8 *) msg) + payload_offset; + bool is_last = true; + if (ctx->requests[tmp].is_dump) + { + if (vapi_msg_id_control_ping_reply == id) + { + payload = NULL; + } + else + { + is_last = false; + } + } + if (payload_offset != -1) + { + rv = + ctx->requests[tmp].callback (ctx, ctx->requests[tmp].callback_ctx, + VAPI_OK, is_last, payload); + } + else + { + /* this is a message without payload, so bend the callback a little + */ + rv = + ((vapi_error_e (*)(vapi_ctx_t, void *, vapi_error_e, bool)) + ctx->requests[tmp].callback) (ctx, + ctx->requests[tmp].callback_ctx, + VAPI_OK, is_last); + } + if (is_last) + { + memset (&ctx->requests[ctx->requests_start], 0, + sizeof (ctx->requests[ctx->requests_start])); + ++ctx->requests_start; + --ctx->requests_count; + if (ctx->requests_start == ctx->requests_size) + { + ctx->requests_start = 0; + } + } + VAPI_DBG ("after dispatch, req start = %d, end = %d, count = %d", + ctx->requests_start, requests_end, ctx->requests_count); + } + if (0 != (mrv = pthread_mutex_unlock (&ctx->requests_mutex))) + { + VAPI_DBG ("pthread_mutex_unlock() failed, rv=%d:%s", mrv, + strerror (mrv)); + abort (); /* this really shouldn't happen */ + } + return rv; +} + +static vapi_error_e +vapi_dispatch_event (vapi_ctx_t ctx, vapi_msg_id_t id, void *msg) +{ + if (ctx->event_cbs[id].cb) + { + return ctx->event_cbs[id].cb (ctx, ctx->event_cbs[id].ctx, msg); + } + else if (ctx->generic_cb.cb) + { + return ctx->generic_cb.cb (ctx, ctx->generic_cb.ctx, id, msg); + } + else + { + VAPI_DBG + ("No handler/generic handler for msg id %u[%s], message ignored", + (unsigned) id, __vapi_metadata.msgs[id]->name); + } + return VAPI_OK; +} + +bool +vapi_msg_is_with_context (vapi_msg_id_t id) +{ + assert (id <= __vapi_metadata.count); + return __vapi_metadata.msgs[id]->has_context; +} + +vapi_error_e +vapi_dispatch_one (vapi_ctx_t ctx) +{ + VAPI_DBG ("vapi_dispatch_one()"); + void *msg; + size_t size; + vapi_error_e rv = vapi_recv (ctx, &msg, &size); + if (VAPI_OK != rv) + { + VAPI_DBG ("vapi_recv failed with rv=%d", rv); + return rv; + } + u16 vpp_id = be16toh (*(u16 *) msg); + if (vpp_id > ctx->vl_msg_id_max) + { + VAPI_ERR ("Unknown msg ID received, id `%u', out of range <0,%u>", + (unsigned) vpp_id, (unsigned) ctx->vl_msg_id_max); + vapi_msg_free (ctx, msg); + return VAPI_EINVAL; + } + if (~0 == (unsigned) ctx->vl_msg_id_to_vapi_msg_t[vpp_id]) + { + VAPI_ERR ("Unknown msg ID received, id `%u' marked as not supported", + (unsigned) vpp_id); + vapi_msg_free (ctx, msg); + return VAPI_EINVAL; + } + const vapi_msg_id_t id = ctx->vl_msg_id_to_vapi_msg_t[vpp_id]; + const size_t expect_size = vapi_get_message_size (id); + if (size < expect_size) + { + VAPI_ERR + ("Invalid msg received, unexpected size `%zu' < expected min `%zu'", + size, expect_size); + vapi_msg_free (ctx, msg); + return VAPI_EINVAL; + } + u32 context; + vapi_get_swap_to_host_func (id) (msg); + if (vapi_msg_is_with_context (id)) + { + context = *(u32 *) (((u8 *) msg) + vapi_get_context_offset (id)); + /* is this a message originating from VAPI? */ + VAPI_DBG ("dispatch, context is %x", context); + if (context & context_counter_mask) + { + rv = vapi_dispatch_response (ctx, id, context, msg); + goto done; + } + } + rv = vapi_dispatch_event (ctx, id, msg); + +done: + vapi_msg_free (ctx, msg); + return rv; +} + +vapi_error_e +vapi_dispatch (vapi_ctx_t ctx) +{ + vapi_error_e rv = VAPI_OK; + while (!vapi_requests_empty (ctx)) + { + rv = vapi_dispatch_one (ctx); + if (VAPI_OK != rv) + { + return rv; + } + } + return rv; +} + +void +vapi_set_event_cb (vapi_ctx_t ctx, vapi_msg_id_t id, + vapi_event_cb callback, void *callback_ctx) +{ + vapi_event_cb_with_ctx *c = &ctx->event_cbs[id]; + c->cb = callback; + c->ctx = callback_ctx; +} + +void +vapi_clear_event_cb (vapi_ctx_t ctx, vapi_msg_id_t id) +{ + vapi_set_event_cb (ctx, id, NULL, NULL); +} + +void +vapi_set_generic_event_cb (vapi_ctx_t ctx, vapi_generic_event_cb callback, + void *callback_ctx) +{ + ctx->generic_cb.cb = callback; + ctx->generic_cb.ctx = callback_ctx; +} + +void +vapi_clear_generic_event_cb (vapi_ctx_t ctx) +{ + ctx->generic_cb.cb = NULL; + ctx->generic_cb.ctx = NULL; +} + +u16 +vapi_lookup_vl_msg_id (vapi_ctx_t ctx, vapi_msg_id_t id) +{ + assert (id < __vapi_metadata.count); + return ctx->vapi_msg_id_t_to_vl_msg_id[id]; +} + +int +vapi_get_client_index (vapi_ctx_t ctx) +{ + return api_main.my_client_index; +} + +bool +vapi_is_nonblocking (vapi_ctx_t ctx) +{ + return (VAPI_MODE_NONBLOCKING == ctx->mode); +} + +size_t +vapi_get_max_request_count (vapi_ctx_t ctx) +{ + return ctx->requests_size - 1; +} + +int +vapi_get_payload_offset (vapi_msg_id_t id) +{ + assert (id < __vapi_metadata.count); + return __vapi_metadata.msgs[id]->payload_offset; +} + +void (*vapi_get_swap_to_host_func (vapi_msg_id_t id)) (void *msg) +{ + assert (id < __vapi_metadata.count); + return __vapi_metadata.msgs[id]->swap_to_host; +} + +void (*vapi_get_swap_to_be_func (vapi_msg_id_t id)) (void *msg) +{ + assert (id < __vapi_metadata.count); + return __vapi_metadata.msgs[id]->swap_to_be; +} + +size_t +vapi_get_message_size (vapi_msg_id_t id) +{ + assert (id < __vapi_metadata.count); + return __vapi_metadata.msgs[id]->size; +} + +size_t +vapi_get_context_offset (vapi_msg_id_t id) +{ + assert (id < __vapi_metadata.count); + return __vapi_metadata.msgs[id]->context_offset; +} + +vapi_msg_id_t +vapi_register_msg (vapi_message_desc_t * msg) +{ + int i = 0; + for (i = 0; i < __vapi_metadata.count; ++i) + { + if (!strcmp + (msg->name_with_crc, __vapi_metadata.msgs[i]->name_with_crc)) + { + /* this happens if somebody is linking together several objects while + * using the static inline headers, just fill in the already + * assigned id here so that all the objects are in sync */ + msg->id = __vapi_metadata.msgs[i]->id; + return msg->id; + } + } + vapi_msg_id_t id = __vapi_metadata.count; + ++__vapi_metadata.count; + __vapi_metadata.msgs = + realloc (__vapi_metadata.msgs, + sizeof (*__vapi_metadata.msgs) * __vapi_metadata.count); + __vapi_metadata.msgs[id] = msg; + size_t s = strlen (msg->name_with_crc); + if (s > __vapi_metadata.max_len_name_with_crc) + { + __vapi_metadata.max_len_name_with_crc = s; + } + msg->id = id; + return id; +} + +vapi_error_e +vapi_producer_lock (vapi_ctx_t ctx) +{ + int mrv; + if (0 != (mrv = pthread_mutex_lock (&ctx->requests_mutex))) + { + VAPI_DBG ("pthread_mutex_lock() failed, rv=%d:%s", mrv, strerror (mrv)); + (void) mrv; /* avoid warning if the above debug is not enabled */ + return VAPI_MUTEX_FAILURE; + } + return VAPI_OK; +} + +vapi_error_e +vapi_producer_unlock (vapi_ctx_t ctx) +{ + int mrv; + if (0 != (mrv = pthread_mutex_unlock (&ctx->requests_mutex))) + { + VAPI_DBG ("pthread_mutex_unlock() failed, rv=%d:%s", mrv, + strerror (mrv)); + (void) mrv; /* avoid warning if the above debug is not enabled */ + return VAPI_MUTEX_FAILURE; + } + return VAPI_OK; +} + +size_t +vapi_get_message_count () +{ + return __vapi_metadata.count; +} + +const char * +vapi_get_msg_name (vapi_msg_id_t id) +{ + return __vapi_metadata.msgs[id]->name; +} + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/vpp-api/vapi/vapi.h b/src/vpp-api/vapi/vapi.h new file mode 100644 index 00000000..245bf654 --- /dev/null +++ b/src/vpp-api/vapi/vapi.h @@ -0,0 +1,263 @@ +/* + *------------------------------------------------------------------ + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *------------------------------------------------------------------ + */ + +#ifndef vpp_api_h_included +#define vpp_api_h_included + +#include <string.h> +#include <stdbool.h> +#include <vppinfra/types.h> +#include <vapi/vapi_common.h> + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * @file vapi.h + * + * common vpp api C declarations + * + * This file declares the common C API functions. These include connect, + * disconnect and utility functions as well as the low-level vapi_send and + * vapi_recv API. This is only the transport layer. + * + * Message formats and higher-level APIs are generated by running the + * vapi_c_gen.py script (which is run for in-tree APIs as part of the build + * process). It's not recommended to mix the higher and lower level APIs. Due + * to version issues, the higher-level APIs are not part of the shared library. + */ + typedef struct vapi_ctx_s *vapi_ctx_t; + +/** + * @brief allocate vapi message of given size + * + * @note message must be freed by vapi_msg_free if not consumed by vapi_send + * call + * + * @param ctx opaque vapi context + * + * @return pointer to message or NULL if out of memory + */ + void *vapi_msg_alloc (vapi_ctx_t ctx, size_t size); + +/** + * @brief free a vapi message + * + * @note messages received by vapi_recv must be freed when no longer needed + * + * @param ctx opaque vapi context + * @param msg message to be freed + */ + void vapi_msg_free (vapi_ctx_t ctx, void *msg); + +/** + * @brief allocate vapi context + * + * @param[out] pointer to result variable + * + * @return VAPI_OK on success, other error code on error + */ + vapi_error_e vapi_ctx_alloc (vapi_ctx_t * result); + +/** + * @brief free vapi context + */ + void vapi_ctx_free (vapi_ctx_t ctx); + +/** + * @brief check if message identified by it's message id is known by the vpp to + * which the connection is open + */ + bool vapi_is_msg_available (vapi_ctx_t ctx, vapi_msg_id_t type); + +/** + * @brief connect to vpp + * + * @param ctx opaque vapi context, must be allocated using vapi_ctx_alloc first + * @param name application name + * @param chroot_prefix shared memory prefix + * @param max_outstanding_requests max number of outstanding requests queued + * @param response_queue_size size of the response queue + * @param mode mode of operation - blocking or nonblocking + * + * @return VAPI_OK on success, other error code on error + */ + vapi_error_e vapi_connect (vapi_ctx_t ctx, const char *name, + const char *chroot_prefix, + int max_outstanding_requests, + int response_queue_size, vapi_mode_e mode); + +/** + * @brief disconnect from vpp + * + * @param ctx opaque vapi context + * + * @return VAPI_OK on success, other error code on error + */ + vapi_error_e vapi_disconnect (vapi_ctx_t ctx); + +/** + * @brief get event file descriptor + * + * @note this file descriptor becomes readable when messages (from vpp) + * are waiting in queue + * + * @param ctx opaque vapi context + * @param[out] fd pointer to result variable + * + * @return VAPI_OK on success, other error code on error + */ + vapi_error_e vapi_get_fd (vapi_ctx_t ctx, int *fd); + +/** + * @brief low-level api for sending messages to vpp + * + * @note it is not recommended to use this api directly, use generated api + * instead + * + * @param ctx opaque vapi context + * @param msg message to send + * + * @return VAPI_OK on success, other error code on error + */ + vapi_error_e vapi_send (vapi_ctx_t ctx, void *msg); + +/** + * @brief low-level api for atomically sending two messages to vpp - either + * both messages are sent or neither one is + * + * @note it is not recommended to use this api directly, use generated api + * instead + * + * @param ctx opaque vapi context + * @param msg1 first message to send + * @param msg2 second message to send + * + * @return VAPI_OK on success, other error code on error + */ + vapi_error_e vapi_send2 (vapi_ctx_t ctx, void *msg1, void *msg2); + +/** + * @brief low-level api for reading messages from vpp + * + * @note it is not recommended to use this api directly, use generated api + * instead + * + * @param ctx opaque vapi context + * @param[out] msg pointer to result variable containing message + * @param[out] msg_size pointer to result variable containing message size + * + * @return VAPI_OK on success, other error code on error + */ + vapi_error_e vapi_recv (vapi_ctx_t ctx, void **msg, size_t * msg_size); + +/** + * @brief wait for connection to become readable or writable + * + * @param ctx opaque vapi context + * @param mode type of property to wait for - readability, writability or both + * + * @return VAPI_OK on success, other error code on error + */ + vapi_error_e vapi_wait (vapi_ctx_t ctx, vapi_wait_mode_e mode); + +/** + * @brief pick next message sent by vpp and call the appropriate callback + * + * @return VAPI_OK on success, other error code on error + */ + vapi_error_e vapi_dispatch_one (vapi_ctx_t ctx); + +/** + * @brief loop vapi_dispatch_one until responses to all currently outstanding + * requests have been received and their callbacks called + * + * @note the dispatch loop is interrupted if any error is encountered or + * returned from the callback, in which case this error is returned as the + * result of vapi_dispatch. In this case it might be necessary to call dispatch + * again to process the remaining messages. Returning VAPI_EUSER from + * a callback allows the user to break the dispatch loop (and distinguish + * this case in the calling code from other failures). VAPI never returns + * VAPI_EUSER on its own. + * + * @return VAPI_OK on success, other error code on error + */ + vapi_error_e vapi_dispatch (vapi_ctx_t ctx); + +/** generic vapi event callback */ + typedef vapi_error_e (*vapi_event_cb) (vapi_ctx_t ctx, void *callback_ctx, + void *payload); + +/** + * @brief set event callback to call when message with given id is dispatched + * + * @param ctx opaque vapi context + * @param id message id + * @param callback callback + * @param callback_ctx context pointer stored and passed to callback + */ + void vapi_set_event_cb (vapi_ctx_t ctx, vapi_msg_id_t id, + vapi_event_cb callback, void *callback_ctx); + +/** + * @brief clear event callback for given message id + * + * @param ctx opaque vapi context + * @param id message id + */ + void vapi_clear_event_cb (vapi_ctx_t ctx, vapi_msg_id_t id); + +/** generic vapi event callback */ + typedef vapi_error_e (*vapi_generic_event_cb) (vapi_ctx_t ctx, + void *callback_ctx, + vapi_msg_id_t id, void *msg); +/** + * @brief set generic event callback + * + * @note this callback is called by dispatch if no message-type specific + * callback is set (so it's a fallback callback) + * + * @param ctx opaque vapi context + * @param callback callback + * @param callback_ctx context pointer stored and passed to callback + */ + void vapi_set_generic_event_cb (vapi_ctx_t ctx, + vapi_generic_event_cb callback, + void *callback_ctx); + +/** + * @brief clear generic event callback + * + * @param ctx opaque vapi context + */ + void vapi_clear_generic_event_cb (vapi_ctx_t ctx); + +#ifdef __cplusplus +} +#endif + +#endif + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/vpp-api/vapi/vapi.hpp b/src/vpp-api/vapi/vapi.hpp new file mode 100644 index 00000000..3be78b41 --- /dev/null +++ b/src/vpp-api/vapi/vapi.hpp @@ -0,0 +1,905 @@ +/* + *------------------------------------------------------------------ + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *------------------------------------------------------------------ + */ + +#ifndef vapi_hpp_included +#define vapi_hpp_included + +#include <cstddef> +#include <vector> +#include <mutex> +#include <queue> +#include <cassert> +#include <functional> +#include <algorithm> +#include <atomic> +#include <vppinfra/types.h> +#include <vapi/vapi.h> +#include <vapi/vapi_internal.h> +#include <vapi/vapi_dbg.h> +#include <vapi/vpe.api.vapi.h> + +#if VAPI_CPP_DEBUG_LEAKS +#include <unordered_set> +#endif + +/** + * @file + * @brief C++ VPP API + */ + +namespace vapi +{ + +class Connection; + +template <typename Req, typename Resp, typename... Args> class Request; +template <typename M> class Msg; +template <typename M> void vapi_swap_to_be (M *msg); +template <typename M> void vapi_swap_to_host (M *msg); +template <typename M, typename... Args> +M *vapi_alloc (Connection &con, Args...); +template <typename M> vapi_msg_id_t vapi_get_msg_id_t (); +template <typename M> class Event_registration; + +class Unexpected_msg_id_exception : public std::exception +{ +public: + virtual const char *what () const throw () + { + return "unexpected message id"; + } +}; + +class Msg_not_available_exception : public std::exception +{ +public: + virtual const char *what () const throw () + { + return "message unavailable"; + } +}; + +typedef enum { + /** response not ready yet */ + RESPONSE_NOT_READY, + + /** response to request is ready */ + RESPONSE_READY, + + /** no response to request (will never come) */ + RESPONSE_NO_RESPONSE, +} vapi_response_state_e; + +/** + * Class representing common functionality of a request - response state + * and context + */ +class Common_req +{ +public: + virtual ~Common_req (){}; + + Connection &get_connection () + { + return con; + }; + + vapi_response_state_e get_response_state (void) const + { + return response_state; + } + +private: + Connection &con; + Common_req (Connection &con) : con{con}, response_state{RESPONSE_NOT_READY} + { + } + + void set_response_state (vapi_response_state_e state) + { + response_state = state; + } + + virtual std::tuple<vapi_error_e, bool> assign_response (vapi_msg_id_t id, + void *shm_data) = 0; + + void set_context (u32 context) + { + this->context = context; + } + + u32 get_context () + { + return context; + } + + u32 context; + vapi_response_state_e response_state; + + friend class Connection; + + template <typename M> friend class Msg; + + template <typename Req, typename Resp, typename... Args> + friend class Request; + + template <typename Req, typename Resp, typename... Args> friend class Dump; + + template <typename M> friend class Event_registration; +}; + +/** + * Class representing a connection to VPP + * + * After creating a Connection object, call connect() to actually connect + * to VPP. Use is_msg_available to discover whether a specific message is known + * and supported by the VPP connected to. + */ +class Connection +{ +public: + Connection (void) : vapi_ctx{0}, event_count{0} + { + + vapi_error_e rv = VAPI_OK; + if (!vapi_ctx) + { + if (VAPI_OK != (rv = vapi_ctx_alloc (&vapi_ctx))) + { + throw std::bad_alloc (); + } + } + events.reserve (vapi_get_message_count () + 1); + } + + Connection (const Connection &) = delete; + + ~Connection (void) + { + vapi_ctx_free (vapi_ctx); +#if VAPI_CPP_DEBUG_LEAKS + for (auto x : shm_data_set) + { + printf ("Leaked shm_data@%p!\n", x); + } +#endif + } + + /** + * @brief check if message identified by it's message id is known by the + * vpp to which the connection is open + */ + bool is_msg_available (vapi_msg_id_t type) + { + return vapi_is_msg_available (vapi_ctx, type); + } + + /** + * @brief connect to vpp + * + * @param name application name + * @param chroot_prefix shared memory prefix + * @param max_queued_request max number of outstanding requests queued + * + * @return VAPI_OK on success, other error code on error + */ + vapi_error_e connect (const char *name, const char *chroot_prefix, + int max_outstanding_requests, int response_queue_size) + { + return vapi_connect (vapi_ctx, name, chroot_prefix, + max_outstanding_requests, response_queue_size, + VAPI_MODE_BLOCKING); + } + + /** + * @brief disconnect from vpp + * + * @return VAPI_OK on success, other error code on error + */ + vapi_error_e disconnect () + { + auto x = requests.size (); + while (x > 0) + { + VAPI_DBG ("popping request @%p", requests.front ()); + requests.pop_front (); + --x; + } + return vapi_disconnect (vapi_ctx); + }; + + /** + * @brief get event file descriptor + * + * @note this file descriptor becomes readable when messages (from vpp) + * are waiting in queue + * + * @param[out] fd pointer to result variable + * + * @return VAPI_OK on success, other error code on error + */ + vapi_error_e get_fd (int *fd) + { + return vapi_get_fd (vapi_ctx, fd); + } + + /** + * @brief wait for responses from vpp and assign them to appropriate objects + * + * @param limit stop dispatch after the limit object received it's response + * + * @return VAPI_OK on success, other error code on error + */ + vapi_error_e dispatch (const Common_req *limit = nullptr) + { + std::lock_guard<std::mutex> lock (dispatch_mutex); + vapi_error_e rv = VAPI_OK; + bool loop_again = true; + while (loop_again) + { + void *shm_data; + size_t shm_data_size; + rv = vapi_recv (vapi_ctx, &shm_data, &shm_data_size); + if (VAPI_OK != rv) + { + return rv; + } +#if VAPI_CPP_DEBUG_LEAKS + on_shm_data_alloc (shm_data); +#endif + std::lock_guard<std::recursive_mutex> requests_lock (requests_mutex); + std::lock_guard<std::recursive_mutex> events_lock (events_mutex); + vapi_msg_id_t id = vapi_lookup_vapi_msg_id_t ( + vapi_ctx, be16toh (*static_cast<u16 *> (shm_data))); + bool has_context = vapi_msg_is_with_context (id); + bool break_dispatch = false; + Common_req *matching_req = nullptr; + if (has_context) + { + u32 context = *reinterpret_cast<u32 *> ( + (static_cast<u8 *> (shm_data) + vapi_get_context_offset (id))); + const auto x = requests.front (); + matching_req = x; + if (context == x->context) + { + std::tie (rv, break_dispatch) = + x->assign_response (id, shm_data); + } + else + { + std::tie (rv, break_dispatch) = + x->assign_response (id, nullptr); + } + if (break_dispatch) + { + requests.pop_front (); + } + } + else + { + if (events[id]) + { + std::tie (rv, break_dispatch) = + events[id]->assign_response (id, shm_data); + matching_req = events[id]; + } + else + { + msg_free (shm_data); + } + } + if ((matching_req && matching_req == limit && break_dispatch) || + VAPI_OK != rv) + { + return rv; + } + loop_again = !requests.empty () || (event_count > 0); + } + return rv; + } + + /** + * @brief convenience wrapper function + */ + vapi_error_e dispatch (const Common_req &limit) + { + return dispatch (&limit); + } + + /** + * @brief wait for response to a specific request + * + * @param req request to wait for response for + * + * @return VAPI_OK on success, other error code on error + */ + vapi_error_e wait_for_response (const Common_req &req) + { + if (RESPONSE_READY == req.get_response_state ()) + { + return VAPI_OK; + } + return dispatch (req); + } + +private: + void msg_free (void *shm_data) + { +#if VAPI_CPP_DEBUG_LEAKS + on_shm_data_free (shm_data); +#endif + vapi_msg_free (vapi_ctx, shm_data); + } + + template <template <typename XReq, typename XResp, typename... XArgs> + class X, + typename Req, typename Resp, typename... Args> + vapi_error_e send (X<Req, Resp, Args...> *req) + { + if (!req) + { + return VAPI_EINVAL; + } + u32 req_context = + req_context_counter.fetch_add (1, std::memory_order_relaxed); + req->request.shm_data->header.context = req_context; + vapi_swap_to_be<Req> (req->request.shm_data); + std::lock_guard<std::recursive_mutex> lock (requests_mutex); + vapi_error_e rv = vapi_send (vapi_ctx, req->request.shm_data); + if (VAPI_OK == rv) + { + VAPI_DBG ("Push %p", req); + requests.emplace_back (req); + req->set_context (req_context); +#if VAPI_CPP_DEBUG_LEAKS + on_shm_data_free (req->request.shm_data); +#endif + req->request.shm_data = nullptr; /* consumed by vapi_send */ + } + else + { + vapi_swap_to_host<Req> (req->request.shm_data); + } + return rv; + } + + template <template <typename XReq, typename XResp, typename... XArgs> + class X, + typename Req, typename Resp, typename... Args> + vapi_error_e send_with_control_ping (X<Req, Resp, Args...> *req) + { + if (!req) + { + return VAPI_EINVAL; + } + u32 req_context = + req_context_counter.fetch_add (1, std::memory_order_relaxed); + req->request.shm_data->header.context = req_context; + vapi_swap_to_be<Req> (req->request.shm_data); + std::lock_guard<std::recursive_mutex> lock (requests_mutex); + vapi_error_e rv = vapi_send_with_control_ping ( + vapi_ctx, req->request.shm_data, req_context); + if (VAPI_OK == rv) + { + VAPI_DBG ("Push %p", req); + requests.emplace_back (req); + req->set_context (req_context); +#if VAPI_CPP_DEBUG_LEAKS + on_shm_data_free (req->request.shm_data); +#endif + req->request.shm_data = nullptr; /* consumed by vapi_send */ + } + else + { + vapi_swap_to_host<Req> (req->request.shm_data); + } + return rv; + } + + void unregister_request (Common_req *request) + { + std::lock_guard<std::recursive_mutex> lock (requests_mutex); + std::remove (requests.begin (), requests.end (), request); + } + + template <typename M> void register_event (Event_registration<M> *event) + { + const vapi_msg_id_t id = M::get_msg_id (); + std::lock_guard<std::recursive_mutex> lock (events_mutex); + events[id] = event; + ++event_count; + } + + template <typename M> void unregister_event (Event_registration<M> *event) + { + const vapi_msg_id_t id = M::get_msg_id (); + std::lock_guard<std::recursive_mutex> lock (events_mutex); + events[id] = nullptr; + --event_count; + } + + vapi_ctx_t vapi_ctx; + std::atomic_ulong req_context_counter; + std::mutex dispatch_mutex; + + std::recursive_mutex requests_mutex; + std::recursive_mutex events_mutex; + std::deque<Common_req *> requests; + std::vector<Common_req *> events; + int event_count; + + template <typename Req, typename Resp, typename... Args> + friend class Request; + + template <typename Req, typename Resp, typename... Args> friend class Dump; + + template <typename M> friend class Result_set; + + template <typename M> friend class Event_registration; + + template <typename M, typename... Args> + friend M *vapi_alloc (Connection &con, Args...); + + template <typename M> friend class Msg; + +#if VAPI_CPP_DEBUG_LEAKS + void on_shm_data_alloc (void *shm_data) + { + if (shm_data) + { + auto pos = shm_data_set.find (shm_data); + if (pos == shm_data_set.end ()) + { + shm_data_set.insert (shm_data); + } + else + { + printf ("Double-add shm_data @%p!\n", shm_data); + } + } + } + + void on_shm_data_free (void *shm_data) + { + auto pos = shm_data_set.find (shm_data); + if (pos == shm_data_set.end ()) + { + printf ("Freeing untracked shm_data @%p!\n", shm_data); + } + else + { + shm_data_set.erase (pos); + } + } + std::unordered_set<void *> shm_data_set; +#endif +}; + +template <typename Req, typename Resp, typename... Args> class Request; + +template <typename Req, typename Resp, typename... Args> class Dump; + +template <class, class = void> struct vapi_has_payload_trait : std::false_type +{ +}; + +template <class... T> using vapi_void_t = void; + +template <class T> +struct vapi_has_payload_trait<T, vapi_void_t<decltype (&T::payload)>> + : std::true_type +{ +}; + +template <typename M> void vapi_msg_set_msg_id (vapi_msg_id_t id) +{ + Msg<M>::set_msg_id (id); +} + +/** + * Class representing a message stored in shared memory + */ +template <typename M> class Msg +{ +public: + Msg (const Msg &) = delete; + + ~Msg () + { + VAPI_DBG ("Destroy Msg<%s>@%p, shm_data@%p", + vapi_get_msg_name (get_msg_id ()), this, shm_data); + if (shm_data) + { + con.get ().msg_free (shm_data); + shm_data = nullptr; + } + } + + static vapi_msg_id_t get_msg_id () + { + return *msg_id_holder (); + } + + template <typename X = M> + typename std::enable_if<vapi_has_payload_trait<X>::value, + decltype (X::payload) &>::type + get_payload () const + { + return shm_data->payload; + } + +private: + Msg (Msg<M> &&msg) : con{msg.con} + { + VAPI_DBG ("Move construct Msg<%s> from msg@%p to msg@%p, shm_data@%p", + vapi_get_msg_name (get_msg_id ()), &msg, this, msg.shm_data); + shm_data = msg.shm_data; + msg.shm_data = nullptr; + } + + Msg<M> &operator= (Msg<M> &&msg) + { + VAPI_DBG ("Move assign Msg<%s> from msg@%p to msg@%p, shm_data@%p", + vapi_get_msg_name (get_msg_id ()), &msg, this, msg.shm_data); + con.get ().msg_free (shm_data); + con = msg.con; + shm_data = msg.shm_data; + msg.shm_data = nullptr; + return *this; + } + + struct Msg_allocator : std::allocator<Msg<M>> + { + template <class U, class... Args> void construct (U *p, Args &&... args) + { + ::new ((void *)p) U (std::forward<Args> (args)...); + } + + template <class U> struct rebind + { + typedef Msg_allocator other; + }; + }; + + static void set_msg_id (vapi_msg_id_t id) + { + assert ((~0 == *msg_id_holder ()) || (id == *msg_id_holder ())); + *msg_id_holder () = id; + } + + static vapi_msg_id_t *msg_id_holder () + { + static vapi_msg_id_t my_id{~0}; + return &my_id; + } + + Msg (Connection &con, void *shm_data) throw (Msg_not_available_exception) + : con{con} + { + if (!con.is_msg_available (get_msg_id ())) + { + throw Msg_not_available_exception (); + } + this->shm_data = static_cast<shm_data_type *> (shm_data); + VAPI_DBG ("New Msg<%s>@%p shm_data@%p", vapi_get_msg_name (get_msg_id ()), + this, shm_data); + } + + void assign_response (vapi_msg_id_t resp_id, + void *shm_data) throw (Unexpected_msg_id_exception) + { + assert (nullptr == this->shm_data); + if (resp_id != get_msg_id ()) + { + throw Unexpected_msg_id_exception (); + } + this->shm_data = static_cast<M *> (shm_data); + vapi_swap_to_host<M> (this->shm_data); + VAPI_DBG ("Assign response to Msg<%s>@%p shm_data@%p", + vapi_get_msg_name (get_msg_id ()), this, shm_data); + } + + std::reference_wrapper<Connection> con; + using shm_data_type = M; + shm_data_type *shm_data; + + friend class Connection; + + template <typename Req, typename Resp, typename... Args> + friend class Request; + + template <typename Req, typename Resp, typename... Args> friend class Dump; + + template <typename X> friend class Event_registration; + + template <typename X> friend class Result_set; + + friend struct Msg_allocator; + + template <typename X> friend void vapi_msg_set_msg_id (vapi_msg_id_t id); +}; + +/** + * Class representing a simple request - with a single response message + */ +template <typename Req, typename Resp, typename... Args> +class Request : public Common_req +{ +public: + Request (Connection &con, Args... args, + std::function<vapi_error_e (Request<Req, Resp, Args...> &)> + callback = nullptr) + : Common_req{con}, callback{callback}, + request{con, vapi_alloc<Req> (con, args...)}, response{con, nullptr} + { + } + + Request (const Request &) = delete; + + virtual ~Request () + { + if (RESPONSE_NOT_READY == get_response_state ()) + { + con.unregister_request (this); + } + } + + vapi_error_e execute () + { + return con.send (this); + } + + const Msg<Req> &get_request (void) const + { + return request; + } + + const Msg<Resp> &get_response (void) + { + return response; + } + +private: + virtual std::tuple<vapi_error_e, bool> assign_response (vapi_msg_id_t id, + void *shm_data) + { + assert (RESPONSE_NOT_READY == get_response_state ()); + response.assign_response (id, shm_data); + set_response_state (RESPONSE_READY); + if (nullptr != callback) + { + return std::make_pair (callback (*this), true); + } + return std::make_pair (VAPI_OK, true); + } + std::function<vapi_error_e (Request<Req, Resp, Args...> &)> callback; + Msg<Req> request; + Msg<Resp> response; + + friend class Connection; +}; + +/** + * Class representing iterable set of responses of the same type + */ +template <typename M> class Result_set +{ +public: + ~Result_set () + { + } + + Result_set (const Result_set &) = delete; + + bool is_complete () const + { + return complete; + } + + size_t size () const + { + return set.size (); + } + + using const_iterator = + typename std::vector<Msg<M>, + typename Msg<M>::Msg_allocator>::const_iterator; + + const_iterator begin () const + { + return set.begin (); + } + + const_iterator end () const + { + return set.end (); + } + + void free_response (const_iterator pos) + { + set.erase (pos); + } + + void free_all_responses () + { + set.clear (); + } + +private: + void mark_complete () + { + complete = true; + } + + void assign_response (vapi_msg_id_t resp_id, + void *shm_data) throw (Unexpected_msg_id_exception) + { + if (resp_id != Msg<M>::get_msg_id ()) + { + { + throw Unexpected_msg_id_exception (); + } + } + else if (shm_data) + { + vapi_swap_to_host<M> (static_cast<M *> (shm_data)); + set.emplace_back (con, shm_data); + VAPI_DBG ("Result_set@%p emplace_back shm_data@%p", this, shm_data); + } + } + + Result_set (Connection &con) : con{con}, complete{false} + { + } + + Connection &con; + bool complete; + std::vector<Msg<M>, typename Msg<M>::Msg_allocator> set; + + template <typename Req, typename Resp, typename... Args> friend class Dump; + + template <typename X> friend class Event_registration; +}; + +/** + * Class representing a dump request - zero or more identical responses to a + * single request message + */ +template <typename Req, typename Resp, typename... Args> +class Dump : public Common_req +{ +public: + Dump (Connection &con, Args... args, + std::function<vapi_error_e (Dump<Req, Resp, Args...> &)> callback = + nullptr) + : Common_req{con}, request{con, vapi_alloc<Req> (con, args...)}, + result_set{con}, callback{callback} + { + } + + Dump (const Dump &) = delete; + + virtual ~Dump () + { + } + + virtual std::tuple<vapi_error_e, bool> assign_response (vapi_msg_id_t id, + void *shm_data) + { + if (id == vapi_msg_id_control_ping_reply) + { + con.msg_free (shm_data); + result_set.mark_complete (); + set_response_state (RESPONSE_READY); + if (nullptr != callback) + { + return std::make_pair (callback (*this), true); + } + return std::make_pair (VAPI_OK, true); + } + else + { + result_set.assign_response (id, shm_data); + } + return std::make_pair (VAPI_OK, false); + } + + vapi_error_e execute () + { + return con.send_with_control_ping (this); + } + + Msg<Req> &get_request (void) + { + return request; + } + + using resp_type = typename Msg<Resp>::shm_data_type; + + const Result_set<Resp> &get_result_set (void) const + { + return result_set; + } + +private: + Msg<Req> request; + Result_set<resp_type> result_set; + std::function<vapi_error_e (Dump<Req, Resp, Args...> &)> callback; + + friend class Connection; +}; + +/** + * Class representing event registration - incoming events (messages) from + * vpp as a result of a subscription (typically a want_* simple request) + */ +template <typename M> class Event_registration : public Common_req +{ +public: + Event_registration ( + Connection &con, + std::function<vapi_error_e (Event_registration<M> &)> callback = + nullptr) throw (Msg_not_available_exception) + : Common_req{con}, result_set{con}, callback{callback} + { + if (!con.is_msg_available (M::get_msg_id ())) + { + throw Msg_not_available_exception (); + } + con.register_event (this); + } + + Event_registration (const Event_registration &) = delete; + + virtual ~Event_registration () + { + con.unregister_event (this); + } + + virtual std::tuple<vapi_error_e, bool> assign_response (vapi_msg_id_t id, + void *shm_data) + { + result_set.assign_response (id, shm_data); + if (nullptr != callback) + { + return std::make_pair (callback (*this), true); + } + return std::make_pair (VAPI_OK, true); + } + + using resp_type = typename M::shm_data_type; + + Result_set<resp_type> &get_result_set (void) + { + return result_set; + } + +private: + Result_set<resp_type> result_set; + std::function<vapi_error_e (Event_registration<M> &)> callback; +}; +}; + +#endif + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/vpp-api/vapi/vapi_c_gen.py b/src/vpp-api/vapi/vapi_c_gen.py new file mode 100755 index 00000000..d7a7272a --- /dev/null +++ b/src/vpp-api/vapi/vapi_c_gen.py @@ -0,0 +1,693 @@ +#!/usr/bin/env python2 + +import argparse +import os +import sys +import logging +from vapi_json_parser import Field, Struct, Message, JsonParser,\ + SimpleType, StructType + + +class CField(Field): + def __init__( + self, + field_name, + field_type, + array_len=None, + nelem_field=None): + super(CField, self).__init__( + field_name, field_type, array_len, nelem_field) + + def get_c_def(self): + if self.len is not None: + return "%s %s[%d]" % (self.type.get_c_name(), self.name, self.len) + else: + return "%s %s" % (self.type.get_c_name(), self.name) + + def get_swap_to_be_code(self, struct, var): + if self.len is not None: + if self.len > 0: + return "do { unsigned i; for (i = 0; i < %d; ++i) { %s } }"\ + " while(0);" % ( + self.len, + self.type.get_swap_to_be_code(struct, "%s[i]" % var)) + else: + if self.nelem_field.needs_byte_swap(): + nelem_field = "%s(%s%s)" % ( + self.nelem_field.type.get_swap_to_host_func_name(), + struct, self.nelem_field.name) + else: + nelem_field = "%s%s" % (struct, self.nelem_field.name) + return ( + "do { unsigned i; for (i = 0; i < %s; ++i) { %s } }" + " while(0);" % + (nelem_field, self.type.get_swap_to_be_code( + struct, "%s[i]" % var))) + return self.type.get_swap_to_be_code(struct, "%s" % var) + + def get_swap_to_host_code(self, struct, var): + if self.len is not None: + if self.len > 0: + return "do { unsigned i; for (i = 0; i < %d; ++i) { %s } }"\ + " while(0);" % ( + self.len, + self.type.get_swap_to_host_code(struct, "%s[i]" % var)) + else: + # nelem_field already swapped to host here... + return ( + "do { unsigned i; for (i = 0; i < %s%s; ++i) { %s } }" + " while(0);" % + (struct, self.nelem_field.name, + self.type.get_swap_to_host_code( + struct, "%s[i]" % var))) + return self.type.get_swap_to_host_code(struct, "%s" % var) + + def needs_byte_swap(self): + return self.type.needs_byte_swap() + + +class CStruct(Struct): + def __init__(self, name, fields): + super(CStruct, self).__init__(name, fields) + + def get_c_def(self): + return "\n".join([ + "typedef struct __attribute__((__packed__)) {", + "%s;" % ";\n".join([" %s" % x.get_c_def() + for x in self.fields]), + "} %s;" % self.get_c_name()]) + + +class CSimpleType (SimpleType): + + swap_to_be_dict = { + 'i16': 'htobe16', 'u16': 'htobe16', + 'i32': 'htobe32', 'u32': 'htobe32', + 'i64': 'htobe64', 'u64': 'htobe64', + } + + swap_to_host_dict = { + 'i16': 'be16toh', 'u16': 'be16toh', + 'i32': 'be32toh', 'u32': 'be32toh', + 'i64': 'be64toh', 'u64': 'be64toh', + } + + def __init__(self, name): + super(CSimpleType, self).__init__(name) + + def get_c_name(self): + return self.name + + def get_swap_to_be_func_name(self): + return self.swap_to_be_dict[self.name] + + def get_swap_to_host_func_name(self): + return self.swap_to_host_dict[self.name] + + def get_swap_to_be_code(self, struct, var): + x = "%s%s" % (struct, var) + return "%s = %s(%s);" % (x, self.get_swap_to_be_func_name(), x) + + def get_swap_to_host_code(self, struct, var): + x = "%s%s" % (struct, var) + return "%s = %s(%s);" % (x, self.get_swap_to_host_func_name(), x) + + def needs_byte_swap(self): + try: + self.get_swap_to_host_func_name() + return True + except: + pass + return False + + +class CStructType (StructType, CStruct): + def __init__(self, definition, typedict, field_class): + super(CStructType, self).__init__(definition, typedict, field_class) + + def get_c_name(self): + return "vapi_type_%s" % self.name + + def get_swap_to_be_func_name(self): + return "%s_hton" % self.get_c_name() + + def get_swap_to_host_func_name(self): + return "%s_ntoh" % self.get_c_name() + + def get_swap_to_be_func_decl(self): + return "void %s(%s *msg)" % ( + self.get_swap_to_be_func_name(), self.get_c_name()) + + def get_swap_to_be_func_def(self): + return "%s\n{\n%s\n}" % ( + self.get_swap_to_be_func_decl(), + "\n".join([ + " %s" % p.get_swap_to_be_code("msg->", "%s" % p.name) + for p in self.fields if p.needs_byte_swap()]), + ) + + def get_swap_to_host_func_decl(self): + return "void %s(%s *msg)" % ( + self.get_swap_to_host_func_name(), self.get_c_name()) + + def get_swap_to_host_func_def(self): + return "%s\n{\n%s\n}" % ( + self.get_swap_to_host_func_decl(), + "\n".join([ + " %s" % p.get_swap_to_host_code("msg->", "%s" % p.name) + for p in self.fields if p.needs_byte_swap()]), + ) + + def get_swap_to_be_code(self, struct, var): + return "%s(&%s%s);" % (self.get_swap_to_be_func_name(), struct, var) + + def get_swap_to_host_code(self, struct, var): + return "%s(&%s%s);" % (self.get_swap_to_host_func_name(), struct, var) + + def needs_byte_swap(self): + for f in self.fields: + if f.needs_byte_swap(): + return True + return False + + +class CMessage (Message): + def __init__(self, logger, definition, typedict, + struct_type_class, simple_type_class, field_class): + super(CMessage, self).__init__(logger, definition, typedict, + struct_type_class, simple_type_class, + field_class) + self.payload_members = [ + " %s" % p.get_c_def() + for p in self.fields + if p.type != self.header + ] + + def has_payload(self): + return len(self.payload_members) > 0 + + def get_msg_id_name(self): + return "vapi_msg_id_%s" % self.name + + def get_c_name(self): + return "vapi_msg_%s" % self.name + + def get_payload_struct_name(self): + return "vapi_payload_%s" % self.name + + def get_alloc_func_vla_field_length_name(self, field): + return "%s_array_size" % field.name + + def get_alloc_func_name(self): + return "vapi_alloc_%s" % self.name + + def get_alloc_vla_param_names(self): + return [self.get_alloc_func_vla_field_length_name(f) + for f in self.fields + if f.nelem_field is not None] + + def get_alloc_func_decl(self): + return "%s* %s(struct vapi_ctx_s *ctx%s)" % ( + self.get_c_name(), + self.get_alloc_func_name(), + "".join([", size_t %s" % n for n in + self.get_alloc_vla_param_names()])) + + def get_alloc_func_def(self): + extra = [] + if self.header.has_field('client_index'): + extra.append( + " msg->header.client_index = vapi_get_client_index(ctx);") + if self.header.has_field('context'): + extra.append(" msg->header.context = 0;") + return "\n".join([ + "%s" % self.get_alloc_func_decl(), + "{", + " %s *msg = NULL;" % self.get_c_name(), + " const size_t size = sizeof(%s)%s;" % ( + self.get_c_name(), + "".join([ + " + sizeof(msg->payload.%s[0]) * %s" % ( + f.name, + self.get_alloc_func_vla_field_length_name(f)) + for f in self.fields + if f.nelem_field is not None + ])), + " /* cast here required to play nicely with C++ world ... */", + " msg = (%s*)vapi_msg_alloc(ctx, size);" % self.get_c_name(), + " if (!msg) {", + " return NULL;", + " }", + ] + extra + [ + " msg->header._vl_msg_id = vapi_lookup_vl_msg_id(ctx, %s);" % + self.get_msg_id_name(), + "\n".join([" msg->payload.%s = %s;" % ( + f.nelem_field.name, + self.get_alloc_func_vla_field_length_name(f)) + for f in self.fields + if f.nelem_field is not None]), + " return msg;", + "}"]) + + def get_calc_msg_size_func_name(self): + return "vapi_calc_%s_msg_size" % self.name + + def get_calc_msg_size_func_decl(self): + return "uword %s(%s *msg)" % ( + self.get_calc_msg_size_func_name(), + self.get_c_name()) + + def get_calc_msg_size_func_def(self): + return "\n".join([ + "%s" % self.get_calc_msg_size_func_decl(), + "{", + " return sizeof(*msg)%s;" % + "".join(["+ msg->payload.%s * sizeof(msg->payload.%s[0])" % ( + f.nelem_field.name, + f.name) + for f in self.fields + if f.nelem_field is not None + ]), + "}", + ]) + + def get_c_def(self): + if self.has_payload(): + return "\n".join([ + "typedef struct __attribute__ ((__packed__)) {", + "%s; " % + ";\n".join(self.payload_members), + "} %s;" % self.get_payload_struct_name(), + "", + "typedef struct __attribute__ ((__packed__)) {", + (" %s %s;" % (self.header.get_c_name(), + self.fields[0].name) + if self.header is not None else ""), + " %s payload;" % self.get_payload_struct_name(), + "} %s;" % self.get_c_name(), ]) + else: + return "\n".join([ + "typedef struct __attribute__ ((__packed__)) {", + (" %s %s;" % (self.header.get_c_name(), + self.fields[0].name) + if self.header is not None else ""), + "} %s;" % self.get_c_name(), ]) + + def get_swap_payload_to_host_func_name(self): + return "%s_payload_ntoh" % self.get_c_name() + + def get_swap_payload_to_be_func_name(self): + return "%s_payload_hton" % self.get_c_name() + + def get_swap_payload_to_host_func_decl(self): + return "void %s(%s *payload)" % ( + self.get_swap_payload_to_host_func_name(), + self.get_payload_struct_name()) + + def get_swap_payload_to_be_func_decl(self): + return "void %s(%s *payload)" % ( + self.get_swap_payload_to_be_func_name(), + self.get_payload_struct_name()) + + def get_swap_payload_to_be_func_def(self): + return "%s\n{\n%s\n}" % ( + self.get_swap_payload_to_be_func_decl(), + "\n".join([ + " %s" % p.get_swap_to_be_code("payload->", "%s" % p.name) + for p in self.fields + if p.needs_byte_swap() and p.type != self.header]), + ) + + def get_swap_payload_to_host_func_def(self): + return "%s\n{\n%s\n}" % ( + self.get_swap_payload_to_host_func_decl(), + "\n".join([ + " %s" % p.get_swap_to_host_code("payload->", "%s" % p.name) + for p in self.fields + if p.needs_byte_swap() and p.type != self.header]), + ) + + def get_swap_to_host_func_name(self): + return "%s_ntoh" % self.get_c_name() + + def get_swap_to_be_func_name(self): + return "%s_hton" % self.get_c_name() + + def get_swap_to_host_func_decl(self): + return "void %s(%s *msg)" % ( + self.get_swap_to_host_func_name(), self.get_c_name()) + + def get_swap_to_be_func_decl(self): + return "void %s(%s *msg)" % ( + self.get_swap_to_be_func_name(), self.get_c_name()) + + def get_swap_to_be_func_def(self): + return "\n".join([ + "%s" % self.get_swap_to_be_func_decl(), + "{", + (" VAPI_DBG(\"Swapping `%s'@%%p to big endian\", msg);" % + self.get_c_name()), + " %s(&msg->header);" % self.header.get_swap_to_be_func_name() + if self.header is not None else "", + " %s(&msg->payload);" % self.get_swap_payload_to_be_func_name() + if self.has_payload() else "", + "}", + ]) + + def get_swap_to_host_func_def(self): + return "\n".join([ + "%s" % self.get_swap_to_host_func_decl(), + "{", + (" VAPI_DBG(\"Swapping `%s'@%%p to host byte order\", msg);" % + self.get_c_name()), + " %s(&msg->header);" % self.header.get_swap_to_host_func_name() + if self.header is not None else "", + " %s(&msg->payload);" % self.get_swap_payload_to_host_func_name() + if self.has_payload() else "", + "}", + ]) + + def get_op_func_name(self): + return "vapi_%s" % self.name + + def get_op_func_decl(self): + if self.reply.has_payload(): + return "vapi_error_e %s(%s)" % ( + self.get_op_func_name(), + ",\n ".join([ + 'struct vapi_ctx_s *ctx', + '%s *msg' % self.get_c_name(), + 'vapi_error_e (*callback)(struct vapi_ctx_s *ctx', + ' void *callback_ctx', + ' vapi_error_e rv', + ' bool is_last', + ' %s *reply)' % + self.reply.get_payload_struct_name(), + 'void *callback_ctx', + ]) + ) + else: + return "vapi_error_e %s(%s)" % ( + self.get_op_func_name(), + ",\n ".join([ + 'struct vapi_ctx_s *ctx', + '%s *msg' % self.get_c_name(), + 'vapi_error_e (*callback)(struct vapi_ctx_s *ctx', + ' void *callback_ctx', + ' vapi_error_e rv', + ' bool is_last)', + 'void *callback_ctx', + ]) + ) + + def get_op_func_def(self): + return "\n".join([ + "%s" % self.get_op_func_decl(), + "{", + " if (!msg || !callback) {", + " return VAPI_EINVAL;", + " }", + " if (vapi_is_nonblocking(ctx) && vapi_requests_full(ctx)) {", + " return VAPI_EAGAIN;", + " }", + " vapi_error_e rv;", + " if (VAPI_OK != (rv = vapi_producer_lock (ctx))) {", + " return rv;", + " }", + " u32 req_context = vapi_gen_req_context(ctx);", + " msg->header.context = req_context;", + " %s(msg);" % self.get_swap_to_be_func_name(), + (" if (VAPI_OK == (rv = vapi_send_with_control_ping " + "(ctx, msg, req_context))) {" + if self.is_dump() else + " if (VAPI_OK == (rv = vapi_send (ctx, msg))) {" + ), + (" vapi_store_request(ctx, req_context, %s, " + "(vapi_cb_t)callback, callback_ctx);" % + ("true" if self.is_dump() else "false")), + " if (VAPI_OK != vapi_producer_unlock (ctx)) {", + " abort (); /* this really shouldn't happen */", + " }", + " if (vapi_is_nonblocking(ctx)) {", + " rv = VAPI_OK;", + " } else {", + " rv = vapi_dispatch(ctx);", + " }", + " } else {", + " %s(msg);" % self.get_swap_to_host_func_name(), + " if (VAPI_OK != vapi_producer_unlock (ctx)) {", + " abort (); /* this really shouldn't happen */", + " }", + " }", + " return rv;", + "}", + "", + ]) + + def get_event_cb_func_decl(self): + if not self.is_reply(): + raise Exception( + "Cannot register event callback for non-reply message") + if self.has_payload(): + return "\n".join([ + "void vapi_set_%s_event_cb (" % + self.get_c_name(), + " struct vapi_ctx_s *ctx, ", + (" vapi_error_e (*callback)(struct vapi_ctx_s *ctx, " + "void *callback_ctx, %s *payload)," % + self.get_payload_struct_name()), + " void *callback_ctx)", + ]) + else: + return "\n".join([ + "void vapi_set_%s_event_cb (" % + self.get_c_name(), + " struct vapi_ctx_s *ctx, ", + " vapi_error_e (*callback)(struct vapi_ctx_s *ctx, " + "void *callback_ctx),", + " void *callback_ctx)", + ]) + + def get_event_cb_func_def(self): + if not self.is_reply(): + raise Exception( + "Cannot register event callback for non-reply function") + return "\n".join([ + "%s" % self.get_event_cb_func_decl(), + "{", + (" vapi_set_event_cb(ctx, %s, (vapi_event_cb)callback, " + "callback_ctx);" % + self.get_msg_id_name()), + "}"]) + + def get_c_metadata_struct_name(self): + return "__vapi_metadata_%s" % self.name + + def get_c_constructor(self): + has_context = False + if self.header is not None: + has_context = self.header.has_field('context') + return '\n'.join([ + 'static void __attribute__((constructor)) __vapi_constructor_%s()' + % self.name, + '{', + ' static const char name[] = "%s";' % self.name, + ' static const char name_with_crc[] = "%s_%s";' + % (self.name, self.crc[2:]), + ' static vapi_message_desc_t %s = {' % + self.get_c_metadata_struct_name(), + ' name,', + ' sizeof(name) - 1,', + ' name_with_crc,', + ' sizeof(name_with_crc) - 1,', + ' true,' if has_context else ' false,', + ' offsetof(%s, context),' % self.header.get_c_name() + if has_context else ' 0,', + (' offsetof(%s, payload),' % self.get_c_name()) + if self.has_payload() else ' ~0,', + ' sizeof(%s),' % self.get_c_name(), + ' (generic_swap_fn_t)%s,' % self.get_swap_to_be_func_name(), + ' (generic_swap_fn_t)%s,' % self.get_swap_to_host_func_name(), + ' ~0,', + ' };', + '', + ' %s = vapi_register_msg(&%s);' % + (self.get_msg_id_name(), self.get_c_metadata_struct_name()), + ' VAPI_DBG("Assigned msg id %%d to %s", %s);' % + (self.name, self.get_msg_id_name()), + '}', + ]) + + +vapi_send_with_control_ping = """ +static inline vapi_error_e +vapi_send_with_control_ping (vapi_ctx_t ctx, void *msg, u32 context) +{ + vapi_msg_control_ping *ping = vapi_alloc_control_ping (ctx); + if (!ping) + { + return VAPI_ENOMEM; + } + ping->header.context = context; + vapi_msg_control_ping_hton (ping); + return vapi_send2 (ctx, msg, ping); +} +""" + + +def gen_json_unified_header(parser, logger, j, io, name): + logger.info("Generating header `%s'" % name) + orig_stdout = sys.stdout + sys.stdout = io + include_guard = "__included_%s" % ( + j.replace(".", "_").replace("/", "_").replace("-", "_")) + print("#ifndef %s" % include_guard) + print("#define %s" % include_guard) + print("") + print("#include <stdlib.h>") + print("#include <stddef.h>") + print("#include <arpa/inet.h>") + print("#include <vapi/vapi_internal.h>") + print("#include <vapi/vapi.h>") + print("#include <vapi/vapi_dbg.h>") + print("") + print("#ifdef __cplusplus") + print("extern \"C\" {") + print("#endif") + if name == "vpe.api.vapi.h": + print("") + print("static inline vapi_error_e vapi_send_with_control_ping " + "(vapi_ctx_t ctx, void * msg, u32 context);") + else: + print("#include <vapi/vpe.api.vapi.h>") + print("") + for m in parser.messages_by_json[j].values(): + print("extern vapi_msg_id_t %s;" % m.get_msg_id_name()) + print("") + print("#define DEFINE_VAPI_MSG_IDS_%s\\" % + j.replace(".", "_").replace("/", "_").replace("-", "_").upper()) + print("\\\n".join([ + " vapi_msg_id_t %s;" % m.get_msg_id_name() + for m in parser.messages_by_json[j].values() + ])) + print("") + print("") + for t in parser.types_by_json[j].values(): + try: + print("%s" % t.get_c_def()) + print("") + except: + pass + for m in parser.messages_by_json[j].values(): + print("%s" % m.get_c_def()) + print("") + + print("") + function_attrs = "static inline " + for t in parser.types_by_json[j].values(): + print("%s%s" % (function_attrs, t.get_swap_to_be_func_def())) + print("") + print("%s%s" % (function_attrs, t.get_swap_to_host_func_def())) + print("") + for m in parser.messages_by_json[j].values(): + if m.has_payload(): + print("%s%s" % (function_attrs, + m.get_swap_payload_to_be_func_def())) + print("") + print("%s%s" % (function_attrs, + m.get_swap_payload_to_host_func_def())) + print("") + print("%s%s" % (function_attrs, m.get_calc_msg_size_func_def())) + print("") + print("%s%s" % (function_attrs, m.get_swap_to_be_func_def())) + print("") + print("%s%s" % (function_attrs, m.get_swap_to_host_func_def())) + print("") + for m in parser.messages_by_json[j].values(): + if m.is_reply(): + continue + print("%s%s" % (function_attrs, m.get_alloc_func_def())) + print("") + print("%s%s" % (function_attrs, m.get_op_func_def())) + print("") + print("") + for m in parser.messages_by_json[j].values(): + print("%s" % m.get_c_constructor()) + print("") + print("") + for m in parser.messages_by_json[j].values(): + if not m.is_reply(): + continue + print("%s%s;" % (function_attrs, m.get_event_cb_func_def())) + print("") + print("") + + if name == "vpe.api.vapi.h": + print("%s" % vapi_send_with_control_ping) + print("") + + print("#ifdef __cplusplus") + print("}") + print("#endif") + print("") + print("#endif") + sys.stdout = orig_stdout + + +def json_to_c_header_name(json_name): + if json_name.endswith(".json"): + return "%s.vapi.h" % os.path.splitext(json_name)[0] + raise Exception("Unexpected json name `%s'!" % json_name) + + +def gen_c_unified_headers(parser, logger, prefix): + if prefix == "" or prefix is None: + prefix = "" + else: + prefix = "%s/" % prefix + for j in parser.json_files: + with open('%s%s' % (prefix, json_to_c_header_name(j)), "w") as io: + gen_json_unified_header( + parser, logger, j, io, json_to_c_header_name(j)) + + +if __name__ == '__main__': + try: + verbose = int(os.getenv("V", 0)) + except: + verbose = 0 + + if verbose >= 2: + log_level = 10 + elif verbose == 1: + log_level = 20 + else: + log_level = 40 + + logging.basicConfig(stream=sys.stdout, level=log_level) + logger = logging.getLogger("VAPI C GEN") + logger.setLevel(log_level) + + argparser = argparse.ArgumentParser(description="VPP C API generator") + argparser.add_argument('files', metavar='api-file', action='append', + type=str, help='json api file' + '(may be specified multiple times)') + argparser.add_argument('--prefix', action='store', default=None, + help='path prefix') + args = argparser.parse_args() + + jsonparser = JsonParser(logger, args.files, + simple_type_class=CSimpleType, + struct_type_class=CStructType, + field_class=CField, + message_class=CMessage) + + # not using the model of having separate generated header and code files + # with generated symbols present in shared library (per discussion with + # Damjan), to avoid symbol version issues in .so + # gen_c_headers_and_code(jsonparser, logger, args.prefix) + + gen_c_unified_headers(jsonparser, logger, args.prefix) + + for e in jsonparser.exceptions: + logger.error(e) diff --git a/src/vpp-api/vapi/vapi_common.h b/src/vpp-api/vapi/vapi_common.h new file mode 100644 index 00000000..ce64469d --- /dev/null +++ b/src/vpp-api/vapi/vapi_common.h @@ -0,0 +1,61 @@ +/* + *------------------------------------------------------------------ + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *------------------------------------------------------------------ + */ + +#ifndef vapi_common_h_included +#define vapi_common_h_included + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum +{ + VAPI_OK = 0, /**< success */ + VAPI_EINVAL, /**< invalid value encountered */ + VAPI_EAGAIN, /**< operation would block */ + VAPI_ENOTSUP, /**< operation not supported */ + VAPI_ENOMEM, /**< out of memory */ + VAPI_ENORESP, /**< no response to request */ + VAPI_EMAP_FAIL, /**< failure while mapping api */ + VAPI_ECON_FAIL, /**< failure while connecting to vpp */ + VAPI_EINCOMPATIBLE, /**< fundamental incompatibility while connecting to vpp + (control ping/control ping reply mismatch) */ + VAPI_MUTEX_FAILURE, /**< failure manipulating internal mutex(es) */ + VAPI_EUSER, /**< user error used for breaking dispatch, + never used by VAPI */ +} vapi_error_e; + +typedef enum +{ + VAPI_MODE_BLOCKING = 1, /**< operations block until response received */ + VAPI_MODE_NONBLOCKING = 2, /**< operations never block */ +} vapi_mode_e; + +typedef enum +{ + VAPI_WAIT_FOR_READ, /**< wait until some message is readable */ + VAPI_WAIT_FOR_WRITE, /**< wait until a message can be written */ + VAPI_WAIT_FOR_READ_WRITE, /**< wait until a read or write can be done */ +} vapi_wait_mode_e; + +typedef int vapi_msg_id_t; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/vpp-api/vapi/vapi_cpp_gen.py b/src/vpp-api/vapi/vapi_cpp_gen.py new file mode 100755 index 00000000..3010f3e1 --- /dev/null +++ b/src/vpp-api/vapi/vapi_cpp_gen.py @@ -0,0 +1,263 @@ +#!/usr/bin/env python2 + +import argparse +import os +import sys +import logging +from vapi_c_gen import CField, CStruct, CSimpleType, CStructType, CMessage, \ + json_to_c_header_name +from vapi_json_parser import JsonParser + + +class CppField(CField): + def __init__( + self, + field_name, + field_type, + array_len=None, + nelem_field=None): + super(CppField, self).__init__( + field_name, field_type, array_len, nelem_field) + + +class CppStruct(CStruct): + def __init__(self, name, fields): + super(CppStruct, self).__init__(name, fields) + + +class CppSimpleType (CSimpleType): + def __init__(self, name): + super(CppSimpleType, self).__init__(name) + + +class CppStructType (CStructType, CppStruct): + def __init__(self, definition, typedict, field_class): + super(CppStructType, self).__init__(definition, typedict, field_class) + + +class CppMessage (CMessage): + def __init__(self, logger, definition, typedict, + struct_type_class, simple_type_class, field_class): + super(CppMessage, self).__init__( + logger, definition, typedict, struct_type_class, + simple_type_class, field_class) + + def get_swap_to_be_template_instantiation(self): + return "\n".join([ + "template <> inline void vapi_swap_to_be<%s>(%s *msg)" % + (self.get_c_name(), self.get_c_name()), + "{", + " %s(msg);" % self.get_swap_to_be_func_name(), + "}", + ]) + + def get_swap_to_host_template_instantiation(self): + return "\n".join([ + "template <> inline void vapi_swap_to_host<%s>(%s *msg)" % + (self.get_c_name(), self.get_c_name()), + "{", + " %s(msg);" % self.get_swap_to_host_func_name(), + "}", + ]) + + def get_alloc_template_instantiation(self): + return "\n".join([ + "template <> inline %s* vapi_alloc<%s%s>" + "(Connection &con%s)" % + (self.get_c_name(), self.get_c_name(), + ", size_t" * len(self.get_alloc_vla_param_names()), + "".join([", size_t %s" % n for n in + self.get_alloc_vla_param_names()]) + ), + "{", + " %s* result = %s(con.vapi_ctx%s);" % + (self.get_c_name(), self.get_alloc_func_name(), + "".join([", %s" % n + for n in self.get_alloc_vla_param_names()])), + "#if VAPI_CPP_DEBUG_LEAKS", + " con.on_shm_data_alloc(result);", + "#endif", + " return result;", + "}", + ]) + + def get_cpp_name(self): + return "%s%s" % (self.name[0].upper(), self.name[1:]) + + def get_req_template_name(self): + if self.is_dump(): + template = "Dump" + else: + template = "Request" + + return "%s<%s, %s%s>" % ( + template, + self.get_c_name(), + self.reply.get_c_name(), + "".join([", size_t"] * len(self.get_alloc_vla_param_names())) + ) + + def get_req_template_instantiation(self): + return "template class %s;" % self.get_req_template_name() + + def get_type_alias(self): + return "using %s = %s;" % ( + self.get_cpp_name(), self.get_req_template_name()) + + def get_reply_template_name(self): + return "Msg<%s>" % (self.get_c_name()) + + def get_reply_type_alias(self): + return "using %s = %s;" % ( + self.get_cpp_name(), self.get_reply_template_name()) + + def get_msg_class_instantiation(self): + return "template class Msg<%s>;" % self.get_c_name() + + def get_get_msg_id_t_instantiation(self): + return "\n".join([ + ("template <> inline vapi_msg_id_t vapi_get_msg_id_t<%s>()" + % self.get_c_name()), + "{", + " return ::%s; " % self.get_msg_id_name(), + "}", + "", + ("template <> inline vapi_msg_id_t " + "vapi_get_msg_id_t<Msg<%s>>()" % self.get_c_name()), + "{", + " return ::%s; " % self.get_msg_id_name(), + "}", + ]) + + def get_cpp_constructor(self): + return '\n'.join([ + ('static void __attribute__((constructor)) ' + '__vapi_cpp_constructor_%s()' + % self.name), + '{', + (' vapi::vapi_msg_set_msg_id<%s>(%s);' % ( + self.get_c_name(), self.get_msg_id_name())), + '}', + ]) + + +def gen_json_header(parser, logger, j, io, gen_h_prefix, add_debug_comments): + logger.info("Generating header `%s'" % io.name) + orig_stdout = sys.stdout + sys.stdout = io + include_guard = "__included_hpp_%s" % ( + j.replace(".", "_").replace("/", "_").replace("-", "_")) + print("#ifndef %s" % include_guard) + print("#define %s" % include_guard) + print("") + print("#include <vapi/vapi.hpp>") + print("#include <%s%s>" % (gen_h_prefix, json_to_c_header_name(j))) + print("") + print("namespace vapi {") + print("") + for m in parser.messages_by_json[j].values(): + # utility functions need to go first, otherwise internal instantiation + # causes headaches ... + if add_debug_comments: + print("/* m.get_swap_to_be_template_instantiation() */") + print("%s" % m.get_swap_to_be_template_instantiation()) + print("") + if add_debug_comments: + print("/* m.get_swap_to_host_template_instantiation() */") + print("%s" % m.get_swap_to_host_template_instantiation()) + print("") + if add_debug_comments: + print("/* m.get_get_msg_id_t_instantiation() */") + print("%s" % m.get_get_msg_id_t_instantiation()) + print("") + if add_debug_comments: + print("/* m.get_cpp_constructor() */") + print("%s" % m.get_cpp_constructor()) + print("") + if not m.is_reply(): + if add_debug_comments: + print("/* m.get_alloc_template_instantiation() */") + print("%s" % m.get_alloc_template_instantiation()) + print("") + if add_debug_comments: + print("/* m.get_msg_class_instantiation() */") + print("%s" % m.get_msg_class_instantiation()) + print("") + if m.is_reply(): + if add_debug_comments: + print("/* m.get_reply_type_alias() */") + print("%s" % m.get_reply_type_alias()) + continue + if add_debug_comments: + print("/* m.get_req_template_instantiation() */") + print("%s" % m.get_req_template_instantiation()) + print("") + if add_debug_comments: + print("/* m.get_type_alias() */") + print("%s" % m.get_type_alias()) + print("") + print("}") # namespace vapi + + print("#endif") + sys.stdout = orig_stdout + + +def json_to_cpp_header_name(json_name): + if json_name.endswith(".json"): + return "%s.vapi.hpp" % os.path.splitext(json_name)[0] + raise Exception("Unexpected json name `%s'!" % json_name) + + +def gen_cpp_headers(parser, logger, prefix, gen_h_prefix, + add_debug_comments=False): + if prefix == "" or prefix is None: + prefix = "" + else: + prefix = "%s/" % prefix + if gen_h_prefix is None: + gen_h_prefix = "" + else: + gen_h_prefix = "%s/" % gen_h_prefix + for j in parser.json_files: + with open('%s%s' % (prefix, json_to_cpp_header_name(j)), "w") as io: + gen_json_header(parser, logger, j, io, + gen_h_prefix, add_debug_comments) + + +if __name__ == '__main__': + try: + verbose = int(os.getenv("V", 0)) + except: + verbose = 0 + + if verbose >= 2: + log_level = 10 + elif verbose == 1: + log_level = 20 + else: + log_level = 40 + + logging.basicConfig(stream=sys.stdout, level=log_level) + logger = logging.getLogger("VAPI CPP GEN") + logger.setLevel(log_level) + + argparser = argparse.ArgumentParser(description="VPP C++ API generator") + argparser.add_argument('files', metavar='api-file', action='append', + type=str, help='json api file' + '(may be specified multiple times)') + argparser.add_argument('--prefix', action='store', default=None, + help='path prefix') + argparser.add_argument('--gen-h-prefix', action='store', default=None, + help='generated C header prefix') + args = argparser.parse_args() + + jsonparser = JsonParser(logger, args.files, + simple_type_class=CppSimpleType, + struct_type_class=CppStructType, + field_class=CppField, + message_class=CppMessage) + + gen_cpp_headers(jsonparser, logger, args.prefix, args.gen_h_prefix) + + for e in jsonparser.exceptions: + logger.error(e) diff --git a/src/vpp-api/vapi/vapi_dbg.h b/src/vpp-api/vapi/vapi_dbg.h new file mode 100644 index 00000000..ec3a3006 --- /dev/null +++ b/src/vpp-api/vapi/vapi_dbg.h @@ -0,0 +1,77 @@ +/* + *------------------------------------------------------------------ + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *------------------------------------------------------------------ + */ + +#ifndef __included_vapi_debug_h__ +#define __included_vapi_debug_h__ + +/* controls debug prints */ +#define VAPI_DEBUG (0) +#define VAPI_DEBUG_CONNECT (0) +#define VAPI_DEBUG_ALLOC (0) +#define VAPI_CPP_DEBUG_LEAKS (0) + +#if VAPI_DEBUG +#include <stdio.h> +#define VAPI_DEBUG_FILE_DEF \ + static const char *__file = NULL; \ + { \ + __file = strrchr (__FILE__, '/'); \ + if (__file) \ + { \ + ++__file; \ + } \ + else \ + { \ + __file = __FILE__; \ + } \ + } + +#define VAPI_DBG(fmt, ...) \ + do \ + { \ + VAPI_DEBUG_FILE_DEF \ + printf ("DBG:%s:%d:%s():" fmt, __file, __LINE__, __func__, \ + ##__VA_ARGS__); \ + printf ("\n"); \ + fflush (stdout); \ + } \ + while (0); + +#define VAPI_ERR(fmt, ...) \ + do \ + { \ + VAPI_DEBUG_FILE_DEF \ + printf ("ERR:%s:%d:%s():" fmt, __file, __LINE__, __func__, \ + ##__VA_ARGS__); \ + printf ("\n"); \ + fflush (stdout); \ + } \ + while (0); +#else +#define VAPI_DBG(...) +#define VAPI_ERR(...) +#endif + +#endif /* __included_vapi_debug_h__ */ + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/vpp-api/vapi/vapi_doc.md b/src/vpp-api/vapi/vapi_doc.md new file mode 100644 index 00000000..0e7e29dd --- /dev/null +++ b/src/vpp-api/vapi/vapi_doc.md @@ -0,0 +1,155 @@ +# VPP API module {#vapi_doc} + +## Overview + +VPP API module allows communicating with VPP over shared memory interface. +The API consists of 3 parts: + +* common code - low-level API +* generated code - high-level API +* code generator - to generate your own high-level API e.g. for custom plugins + +### Common code + +#### C common code + +C common code represents the basic, low-level API, providing functions to +connect/disconnect, perform message discovery and send/receive messages. +The C variant is in vapi.h. + +#### C++ common code + +C++ is provided by vapi.hpp and contains high-level API templates, +which are specialized by generated code. + +### Generated code + +Each API file present in the source tree is automatically translated to JSON +file, which the code generator parses and generates either C (`vapi_c_gen.py`) +or C++ (`vapi_cpp_gen.py`) code. + +This can then be included in the client application and provides convenient way +to interact with VPP. This includes: + +* automatic byte-swapping +* automatic request-response matching based on context +* automatic casts to appropriate types (type-safety) when calling callbacks +* automatic sending of control-pings for dump messages + +The API supports two modes of operation: + +* blocking +* non-blocking + +In blocking mode, whenever an operation is initiated, the code waits until it +can finish. This means that when sending a message, the call blocks until +the message can be written to shared memory. Similarly, receiving a message +blocks until a message becomes available. On higher level, this also means that +when doing a request (e.g. `show_version`), the call blocks until a response +comes back (e.g. `show_version_reply`). + +In non-blocking mode, these are decoupled, the API returns VAPI_EAGAIN whenever +an operation cannot be performed and after sending a request, it's up to +the client to wait for and process a response. + +### Code generator + +Python code generator comes in two flavors - C and C++ and generates high-level +API headers. All the code is stored in the headers. + +## Usage + +### Low-level API + +Refer to inline API documentation in doxygen format in `vapi.h` header +for description of functions. It's recommened to use the safer, high-level +API provided by specialized headers (e.g. `vpe.api.vapi.h` +or `vpe.api.vapi.hpp`). + +#### C high-level API + +##### Callbacks + +The C high-level API is strictly callback-based for maximum efficiency. +Whenever an operation is initiated a callback with a callback context is part +of that operation. The callback is then invoked when the response (or multiple +responses) arrive which are tied to the request. Also, callbacks are invoked +whenever an event arrives, if such callback is registered. All the pointers +to responses/events point to shared memory and are immediately freed after +callback finishes so the client needs to extract/copy any data in which it +is interested in. + +#### Blocking mode + +In simple blocking mode, the whole operation (being a simple request or a dump) +is finished and it's callback is called (potentially multiple times for dumps) +during function call. + +Example pseudo-code for a simple request in this mode: + +` +vapi_show_version(message, callback, callback_context) + +1. generate unique internal context and assign it to message.header.context +2. byteswap the message to network byte order +3. send message to vpp (message is now consumed and vpp will free it) +4. create internal "outstanding request context" which stores the callback, + callback context and the internal context value +5. call dispatch, which in this mode receives and processes responses until + the internal "outstanding requests" queue is empty. In blocking mode, this + queue always contains at most one item. +` + +**Note**: it's possible for different - unrelated callbacks to be called before +the response callbacks is called in cases where e.g. events are stored +in shared memory queue. + +#### Non-blocking mode + +In non-blocking mode, all the requests are only byte-swapped and the context +information along with callbacks is stored locally (so in the above example, +only steps 1-4 are executed and step 5 is skipped). Calling dispatch is up to +the client application. This allows to alternate between sending/receiving +messages or have a dedicated thread which calls dispatch. + +### C++ high level API + +#### Callbacks + +In C++ API, the response is automatically tied to the corresponding `Request`, +`Dump` or `Event_registration` object. Optionally a callback might be specified, +which then gets called when the response is received. + +**Note**: responses take up shared memory space and should be freed either +manually (in case of result sets) or automatically (by destroying the object +owning them) when no longer needed. Once a Request or Dump object was executed, +it cannot be re-sent, since the request itself (stores in shared memory) +is consumed by vpp and inaccessible (set to nullptr) anymore. + +#### Usage + +#### Requests & dumps + +0. Create on object of `Connection` type and call `connect()` to connect to vpp. +1. Create an object of `Request` or `Dump` type using it's typedef (e.g. + `Show_version`) +2. Use `get_request()` to obtain and manipulate the underlying request if + required. +3. Issue `execute()` to send the request. +4. Use either `wait_for_response()` or `dispatch()` to wait for the response. +5. Use `get_response_state()` to get the state and `get_response()` to read + the response. + +#### Events + +0. Create a `Connection` and execute the appropriate `Request` to subscribe to + events (e.g. `Want_stats`) +1. Create an `Event_registration` with a template argument being the type of + event you are insterested in. +2. Call `dispatch()` or `wait_for_response()` to wait for the event. A callback + will be called when an event occurs (if passed to `Event_registration()` + constructor). Alternatively, read the result set. + +**Note**: events stored in the result set take up space in shared memory +and should be freed regularly (e.g. in the callback, once the event is +processed). diff --git a/src/vpp-api/vapi/vapi_internal.h b/src/vpp-api/vapi/vapi_internal.h new file mode 100644 index 00000000..2c51c673 --- /dev/null +++ b/src/vpp-api/vapi/vapi_internal.h @@ -0,0 +1,138 @@ +/* + *------------------------------------------------------------------ + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *------------------------------------------------------------------ + */ + +#ifndef VAPI_INTERNAL_H +#define VAPI_INTERNAL_H + +#include <endian.h> +#include <string.h> +#include <vppinfra/types.h> + +/** + * @file vapi_internal.h + * + * internal vpp api C declarations + * + * This file contains internal vpp api C declarations. It's not intended to be + * used by the client programmer and the API defined here might change at any + * time.. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +struct vapi_ctx_s; + +typedef struct __attribute__ ((__packed__)) +{ + u16 _vl_msg_id; + u32 context; +} vapi_type_msg_header1_t; + +typedef struct __attribute__ ((__packed__)) +{ + u16 _vl_msg_id; + u32 client_index; + u32 context; +} vapi_type_msg_header2_t; + +static inline void +vapi_type_msg_header1_t_hton (vapi_type_msg_header1_t * h) +{ + h->_vl_msg_id = htobe16 (h->_vl_msg_id); +} + +static inline void +vapi_type_msg_header1_t_ntoh (vapi_type_msg_header1_t * h) +{ + h->_vl_msg_id = be16toh (h->_vl_msg_id); +} + +static inline void +vapi_type_msg_header2_t_hton (vapi_type_msg_header2_t * h) +{ + h->_vl_msg_id = htobe16 (h->_vl_msg_id); +} + +static inline void +vapi_type_msg_header2_t_ntoh (vapi_type_msg_header2_t * h) +{ + h->_vl_msg_id = be16toh (h->_vl_msg_id); +} + + +#include <vapi/vapi.h> + +typedef vapi_error_e (*vapi_cb_t) (struct vapi_ctx_s *, void *, vapi_error_e, + bool, void *); + +typedef void (*generic_swap_fn_t) (void *payload); + +typedef struct +{ + const char *name; + size_t name_len; + const char *name_with_crc; + size_t name_with_crc_len; + bool has_context; + int context_offset; + int payload_offset; + size_t size; + generic_swap_fn_t swap_to_be; + generic_swap_fn_t swap_to_host; + vapi_msg_id_t id; /* assigned at run-time */ +} vapi_message_desc_t; + +typedef struct +{ + const char *name; + int payload_offset; + size_t size; + void (*swap_to_be) (void *payload); + void (*swap_to_host) (void *payload); +} vapi_event_desc_t; + +vapi_msg_id_t vapi_register_msg (vapi_message_desc_t * msg); +u16 vapi_lookup_vl_msg_id (vapi_ctx_t ctx, vapi_msg_id_t id); +vapi_msg_id_t vapi_lookup_vapi_msg_id_t (vapi_ctx_t ctx, u16 vl_msg_id); +int vapi_get_client_index (vapi_ctx_t ctx); +bool vapi_is_nonblocking (vapi_ctx_t ctx); +bool vapi_requests_empty (vapi_ctx_t ctx); +bool vapi_requests_full (vapi_ctx_t ctx); +size_t vapi_get_request_count (vapi_ctx_t ctx); +size_t vapi_get_max_request_count (vapi_ctx_t ctx); +u32 vapi_gen_req_context (vapi_ctx_t ctx); +void vapi_store_request (vapi_ctx_t ctx, u32 context, bool is_dump, + vapi_cb_t callback, void *callback_ctx); +int vapi_get_payload_offset (vapi_msg_id_t id); +void (*vapi_get_swap_to_host_func (vapi_msg_id_t id)) (void *payload); +void (*vapi_get_swap_to_be_func (vapi_msg_id_t id)) (void *payload); +size_t vapi_get_message_size (vapi_msg_id_t id); +size_t vapi_get_context_offset (vapi_msg_id_t id); +bool vapi_msg_is_with_context (vapi_msg_id_t id); +size_t vapi_get_message_count(); +const char *vapi_get_msg_name(vapi_msg_id_t id); + +vapi_error_e vapi_producer_lock (vapi_ctx_t ctx); +vapi_error_e vapi_producer_unlock (vapi_ctx_t ctx); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/vpp-api/vapi/vapi_json_parser.py b/src/vpp-api/vapi/vapi_json_parser.py new file mode 100644 index 00000000..4e62720d --- /dev/null +++ b/src/vpp-api/vapi/vapi_json_parser.py @@ -0,0 +1,305 @@ +#!/usr/bin/env python2 + +import json + + +def msg_is_reply(name): + return name.endswith('_reply') or name.endswith('_details') \ + or name.endswith('_event') or name.endswith('_counters') + + +class ParseError (Exception): + pass + + +magic_prefix = "vl_api_" +magic_suffix = "_t" + + +def remove_magic(what): + if what.startswith(magic_prefix) and what.endswith(magic_suffix): + return what[len(magic_prefix): - len(magic_suffix)] + return what + + +class Field(object): + + def __init__( + self, + field_name, + field_type, + array_len=None, + nelem_field=None): + self.name = field_name + self.type = field_type + self.len = array_len + self.nelem_field = nelem_field + + def __str__(self): + if self.len is None: + return "name: %s, type: %s" % (self.name, self.type) + elif self.len > 0: + return "name: %s, type: %s, length: %s" % (self.name, self.type, + self.len) + else: + return ("name: %s, type: %s, variable length stored in: %s" % + (self.name, self.type, self.nelem_field)) + + +class Type(object): + def __init__(self, name): + self.name = name + + +class SimpleType (Type): + + def __init__(self, name): + super(SimpleType, self).__init__(name) + + def __str__(self): + return self.name + + +def get_msg_header_defs(struct_type_class, field_class, typedict): + return [ + struct_type_class(['msg_header1_t', + ['u16', '_vl_msg_id'], + ['u32', 'context'], + ], + typedict, field_class + ), + struct_type_class(['msg_header2_t', + ['u16', '_vl_msg_id'], + ['u32', 'client_index'], + ['u32', 'context'], + ], + typedict, field_class + ), + ] + + +class Struct(object): + + def __init__(self, name, fields): + self.name = name + self.fields = fields + self.field_names = [n.name for n in self.fields] + + +class Message(object): + + def __init__(self, logger, definition, typedict, + struct_type_class, simple_type_class, field_class): + self.request = None + self.logger = logger + m = definition + logger.debug("Parsing message definition `%s'" % m) + name = m[0] + self.name = name + logger.debug("Message name is `%s'" % name) + ignore = True + self.header = None + fields = [] + for header in get_msg_header_defs(struct_type_class, field_class, + typedict): + logger.debug("Probing header `%s'" % header.name) + if header.is_part_of_def(m[1:]): + self.header = header + logger.debug("Found header `%s'" % header.name) + fields.append(field_class(field_name='header', + field_type=self.header)) + ignore = False + break + if ignore and not msg_is_reply(name): + raise ParseError("While parsing message `%s': could not find all " + "common header fields" % name) + for field in m[1:]: + if len(field) == 1 and 'crc' in field: + self.crc = field['crc'] + logger.debug("Found CRC `%s'" % self.crc) + continue + else: + field_type = field[0] + if field_type in typedict: + field_type = typedict[field_type] + else: + field_type = typedict[remove_magic(field_type)] + if len(field) == 2: + if self.header is not None and\ + self.header.has_field(field[1]): + continue + p = field_class(field_name=field[1], + field_type=field_type) + elif len(field) == 3: + if field[2] == 0: + raise ParseError( + "While parsing message `%s': variable length " + "array `%s' doesn't have reference to member " + "containing the actual length" % ( + name, field[1])) + p = field_class( + field_name=field[1], + field_type=field_type, + array_len=field[2]) + elif len(field) == 4: + nelem_field = None + for f in fields: + if f.name == field[3]: + nelem_field = f + if nelem_field is None: + raise ParseError( + "While parsing message `%s': couldn't find " + "variable length array `%s' member containing " + "the actual length `%s'" % ( + name, field[1], field[3])) + p = field_class( + field_name=field[1], + field_type=field_type, + array_len=field[2], + nelem_field=nelem_field) + else: + raise Exception("Don't know how to parse message " + "definition for message `%s': `%s'" % + (m, m[1:])) + logger.debug("Parsed field `%s'" % p) + fields.append(p) + self.fields = fields + + def is_dump(self): + return self.name.endswith('_dump') + + def is_reply(self): + return msg_is_reply(self.name) + + +class StructType (Type, Struct): + + def __init__(self, definition, typedict, field_class): + t = definition + name = t[0] + fields = [] + for field in t[1:]: + if len(field) == 1 and 'crc' in field: + self.crc = field['crc'] + continue + elif len(field) == 2: + p = field_class(field_name=field[1], + field_type=typedict[field[0]]) + elif len(field) == 3: + if field[2] == 0: + raise ParseError("While parsing type `%s': array `%s' has " + "variable length" % (name, field[1])) + p = field_class(field_name=field[1], + field_type=typedict[field[0]], + array_len=field[2]) + else: + raise ParseError( + "Don't know how to parse type definition for " + "type `%s': `%s'" % (t, t[1:])) + fields.append(p) + Type.__init__(self, name) + Struct.__init__(self, name, fields) + + def has_field(self, name): + return name in self.field_names + + def is_part_of_def(self, definition): + for idx in range(len(self.fields)): + field = definition[idx] + p = self.fields[idx] + if field[1] != p.name: + return False + if field[0] != p.type.name: + raise ParseError( + "Unexpected field type `%s' (should be `%s'), " + "while parsing msg/def/field `%s/%s/%s'" % + (field[0], p.type, p.name, definition, field)) + return True + + +class JsonParser(object): + def __init__(self, logger, files, simple_type_class=SimpleType, + struct_type_class=StructType, field_class=Field, + message_class=Message): + self.messages = {} + self.types = { + x: simple_type_class(x) for x in [ + 'i8', 'i16', 'i32', 'i64', + 'u8', 'u16', 'u32', 'u64', + 'f64' + ] + } + + self.simple_type_class = simple_type_class + self.struct_type_class = struct_type_class + self.field_class = field_class + self.message_class = message_class + + self.exceptions = [] + self.json_files = [] + self.types_by_json = {} + self.messages_by_json = {} + self.logger = logger + for f in files: + self.parse_json_file(f) + self.finalize_parsing() + + def parse_json_file(self, path): + self.logger.info("Parsing json api file: `%s'" % path) + self.json_files.append(path) + self.types_by_json[path] = {} + self.messages_by_json[path] = {} + with open(path) as f: + j = json.load(f) + for t in j['types']: + try: + type_ = self.struct_type_class(t, self.types, + self.field_class) + if type_.name in self.types: + raise ParseError("Duplicate type `%s'" % type_.name) + except ParseError as e: + self.exceptions.append(e) + continue + self.types[type_.name] = type_ + self.types_by_json[path][type_.name] = type_ + for m in j['messages']: + try: + msg = self.message_class(self.logger, m, self.types, + self.struct_type_class, + self.simple_type_class, + self.field_class) + if msg.name in self.messages: + raise ParseError("Duplicate message `%s'" % msg.name) + except ParseError as e: + self.exceptions.append(e) + continue + self.messages[msg.name] = msg + self.messages_by_json[path][msg.name] = msg + + def get_reply(self, message): + if self.messages[message].is_dump(): + return self.messages["%s_details" % message[:-len("_dump")]] + return self.messages["%s_reply" % message] + + def finalize_parsing(self): + if len(self.messages) == 0: + for e in self.exceptions: + self.logger.error(e) + raise Exception("No messages parsed.") + for jn, j in self.messages_by_json.items(): + remove = [] + for n, m in j.items(): + try: + if not m.is_reply(): + try: + m.reply = self.get_reply(n) + m.reply.request = m + except: + raise ParseError( + "Cannot find reply to message `%s'" % n) + except ParseError as e: + self.exceptions.append(e) + remove.append(n) + + self.messages_by_json[jn] = { + k: v for k, v in j.items() if k not in remove} |