From 8f2a4eafeaa439432107563033728e09665c16d9 Mon Sep 17 00:00:00 2001 From: Klement Sekera Date: Thu, 4 May 2017 06:15:18 +0200 Subject: Add new C API Change-Id: I717ce3cd7c867c155de149ec56623269d26d0ff7 Signed-off-by: Klement Sekera --- src/vpp-api/vapi/vapi.c | 895 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 895 insertions(+) create mode 100644 src/vpp-api/vapi/vapi.c (limited to 'src/vpp-api/vapi/vapi.c') diff --git a/src/vpp-api/vapi/vapi.c b/src/vpp-api/vapi/vapi.c new file mode 100644 index 00000000..b9c81a13 --- /dev/null +++ b/src/vpp-api/vapi/vapi.c @@ -0,0 +1,895 @@ +/* + *------------------------------------------------------------------ + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *------------------------------------------------------------------ + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +/* we need to use control pings for some stuff and because we're forced to put + * the code in headers, we need a way to be able to grab the ids of these + * messages - so declare them here as extern */ +vapi_msg_id_t vapi_msg_id_control_ping = 0; +vapi_msg_id_t vapi_msg_id_control_ping_reply = 0; + +struct +{ + size_t count; + vapi_message_desc_t **msgs; + size_t max_len_name_with_crc; +} __vapi_metadata; + +typedef struct +{ + u32 context; + vapi_cb_t callback; + void *callback_ctx; + bool is_dump; +} vapi_req_t; + +static const u32 context_counter_mask = (1 << 31); + +typedef struct +{ + vapi_error_e (*cb) (vapi_ctx_t ctx, void *callback_ctx, vapi_msg_id_t id, + void *payload); + void *ctx; +} vapi_generic_cb_with_ctx; + +typedef struct +{ + vapi_error_e (*cb) (vapi_ctx_t ctx, void *callback_ctx, void *payload); + void *ctx; +} vapi_event_cb_with_ctx; + +struct vapi_ctx_s +{ + vapi_mode_e mode; + int requests_size; /* size of the requests array (circular queue) */ + int requests_start; /* index of first request */ + int requests_count; /* number of used slots */ + vapi_req_t *requests; + u32 context_counter; + vapi_generic_cb_with_ctx generic_cb; + vapi_event_cb_with_ctx *event_cbs; + u16 *vapi_msg_id_t_to_vl_msg_id; + u16 vl_msg_id_max; + vapi_msg_id_t *vl_msg_id_to_vapi_msg_t; + bool connected; + pthread_mutex_t requests_mutex; +}; + +u32 +vapi_gen_req_context (vapi_ctx_t ctx) +{ + ++ctx->context_counter; + ctx->context_counter %= context_counter_mask; + return ctx->context_counter | context_counter_mask; +} + +size_t +vapi_get_request_count (vapi_ctx_t ctx) +{ + return ctx->requests_count; +} + +bool +vapi_requests_full (vapi_ctx_t ctx) +{ + return (ctx->requests_count == ctx->requests_size); +} + +static bool +vapi_requests_empty (vapi_ctx_t ctx) +{ + return (0 == ctx->requests_count); +} + +static int +vapi_requests_end (vapi_ctx_t ctx) +{ + return (ctx->requests_start + ctx->requests_count) % ctx->requests_size; +} + +void +vapi_store_request (vapi_ctx_t ctx, u32 context, bool is_dump, + vapi_cb_t callback, void *callback_ctx) +{ + assert (!vapi_requests_full (ctx)); + /* if the mutex is not held, bad things will happen */ + assert (0 != pthread_mutex_trylock (&ctx->requests_mutex)); + const int requests_end = vapi_requests_end (ctx); + vapi_req_t *slot = &ctx->requests[requests_end]; + slot->is_dump = is_dump; + slot->context = context; + slot->callback = callback; + slot->callback_ctx = callback_ctx; + VAPI_DBG ("stored@%d: context:%x (start is @%d)", requests_end, context, + ctx->requests_start); + ++ctx->requests_count; + assert (!vapi_requests_empty (ctx)); +} + +#if VAPI_DEBUG_ALLOC +struct to_be_freed_s; +struct to_be_freed_s +{ + void *v; + struct to_be_freed_s *next; +}; + +static struct to_be_freed_s *to_be_freed = NULL; + +void +vapi_add_to_be_freed (void *v) +{ + struct to_be_freed_s *prev = NULL; + struct to_be_freed_s *tmp; + tmp = to_be_freed; + while (tmp && tmp->v) + { + prev = tmp; + tmp = tmp->next; + } + if (!tmp) + { + if (!prev) + { + tmp = to_be_freed = calloc (1, sizeof (*to_be_freed)); + } + else + { + tmp = prev->next = calloc (1, sizeof (*to_be_freed)); + } + } + VAPI_DBG ("To be freed %p", v); + tmp->v = v; +} + +void +vapi_trace_free (void *v) +{ + struct to_be_freed_s *tmp = to_be_freed; + while (tmp && tmp->v != v) + { + tmp = tmp->next; + } + if (tmp && tmp->v == v) + { + VAPI_DBG ("Freed %p", v); + tmp->v = NULL; + } + else + { + VAPI_ERR ("Trying to free untracked pointer %p", v); + abort (); + } +} + +void +vapi_to_be_freed_validate () +{ + struct to_be_freed_s *tmp = to_be_freed; + while (tmp) + { + if (tmp->v) + { + VAPI_ERR ("Unfreed msg %p!", tmp->v); + } + tmp = tmp->next; + } +} + +#endif + +void * +vapi_msg_alloc (vapi_ctx_t ctx, size_t size) +{ + if (!ctx->connected) + { + return NULL; + } + void *rv = vl_msg_api_alloc_or_null (size); + return rv; +} + +void +vapi_msg_free (vapi_ctx_t ctx, void *msg) +{ + if (!ctx->connected) + { + return; + } +#if VAPI_DEBUG_ALLOC + vapi_trace_free (msg); +#endif + vl_msg_api_free (msg); +} + +vapi_error_e +vapi_ctx_alloc (vapi_ctx_t * result) +{ + vapi_ctx_t ctx = calloc (1, sizeof (struct vapi_ctx_s)); + if (!ctx) + { + return VAPI_ENOMEM; + } + ctx->context_counter = 0; + ctx->vapi_msg_id_t_to_vl_msg_id = + malloc (__vapi_metadata.count * + sizeof (*ctx->vapi_msg_id_t_to_vl_msg_id)); + if (!ctx->vapi_msg_id_t_to_vl_msg_id) + { + goto fail; + } + ctx->event_cbs = calloc (__vapi_metadata.count, sizeof (*ctx->event_cbs)); + if (!ctx->event_cbs) + { + goto fail; + } + pthread_mutex_init (&ctx->requests_mutex, NULL); + *result = ctx; + return VAPI_OK; +fail: + vapi_ctx_free (ctx); + return VAPI_ENOMEM; +} + +void +vapi_ctx_free (vapi_ctx_t ctx) +{ + assert (!ctx->connected); + free (ctx->requests); + free (ctx->vapi_msg_id_t_to_vl_msg_id); + free (ctx->event_cbs); + free (ctx->vl_msg_id_to_vapi_msg_t); + pthread_mutex_destroy (&ctx->requests_mutex); + free (ctx); +} + +bool +vapi_is_msg_available (vapi_ctx_t ctx, vapi_msg_id_t id) +{ + return vapi_lookup_vl_msg_id (ctx, id) != UINT16_MAX; +} + +vapi_error_e +vapi_connect (vapi_ctx_t ctx, const char *name, + const char *chroot_prefix, + int max_outstanding_requests, + int response_queue_size, vapi_mode_e mode) +{ + if (response_queue_size <= 0 || max_outstanding_requests <= 0) + { + return VAPI_EINVAL; + } + ctx->requests_size = max_outstanding_requests; + const size_t size = ctx->requests_size * sizeof (*ctx->requests); + void *tmp = realloc (ctx->requests, size); + if (!tmp) + { + return VAPI_ENOMEM; + } + ctx->requests = tmp; + memset (ctx->requests, 0, size); + ctx->requests_start = ctx->requests_count = 0; + if (chroot_prefix) + { + VAPI_DBG ("set memory root path `%s'", chroot_prefix); + vl_set_memory_root_path ((char *) chroot_prefix); + } + static char api_map[] = "/vpe-api"; + VAPI_DBG ("client api map `%s'", api_map); + if ((vl_client_api_map (api_map)) < 0) + { + return VAPI_EMAP_FAIL; + } + VAPI_DBG ("connect client `%s'", name); + if (vl_client_connect ((char *) name, 0, response_queue_size) < 0) + { + vl_client_api_unmap (); + return VAPI_ECON_FAIL; + } +#if VAPI_DEBUG_CONNECT + VAPI_DBG ("start probing messages"); +#endif + int rv; + int i; + for (i = 0; i < __vapi_metadata.count; ++i) + { + vapi_message_desc_t *m = __vapi_metadata.msgs[i]; + u8 scratch[m->name_with_crc_len + 1]; + memcpy (scratch, m->name_with_crc, m->name_with_crc_len + 1); + u32 id = vl_api_get_msg_index (scratch); + if (~0 != id) + { + if (id > UINT16_MAX) + { + VAPI_ERR ("Returned vl_msg_id `%u' > UINT16MAX `%u'!", id, + UINT16_MAX); + rv = VAPI_EINVAL; + goto fail; + } + if (id > ctx->vl_msg_id_max) + { + vapi_msg_id_t *tmp = realloc (ctx->vl_msg_id_to_vapi_msg_t, + sizeof + (*ctx->vl_msg_id_to_vapi_msg_t) * + (id + 1)); + if (!tmp) + { + rv = VAPI_ENOMEM; + goto fail; + } + ctx->vl_msg_id_to_vapi_msg_t = tmp; + ctx->vl_msg_id_max = id; + } + ctx->vl_msg_id_to_vapi_msg_t[id] = m->id; + ctx->vapi_msg_id_t_to_vl_msg_id[m->id] = id; +#if VAPI_DEBUG_CONNECT + VAPI_DBG ("Message `%s' has vl_msg_id `%u'", m->name_with_crc, + (unsigned) id); +#endif + } + else + { + ctx->vapi_msg_id_t_to_vl_msg_id[m->id] = UINT16_MAX; + VAPI_DBG ("Message `%s' not available", m->name_with_crc); + } + } +#if VAPI_DEBUG_CONNECT + VAPI_DBG ("finished probing messages"); +#endif + if (!vapi_is_msg_available (ctx, vapi_msg_id_control_ping) || + !vapi_is_msg_available (ctx, vapi_msg_id_control_ping_reply)) + { + VAPI_ERR + ("control ping or control ping reply not available, cannot connect"); + rv = VAPI_EINCOMPATIBLE; + goto fail; + } + ctx->mode = mode; + ctx->connected = true; + return VAPI_OK; +fail: + vl_client_disconnect (); + vl_client_api_unmap (); + return rv; +} + +vapi_error_e +vapi_disconnect (vapi_ctx_t ctx) +{ + if (!ctx->connected) + { + return VAPI_EINVAL; + } + vl_client_disconnect (); + vl_client_api_unmap (); +#if VAPI_DEBUG_ALLOC + vapi_to_be_freed_validate (); +#endif + ctx->connected = false; + return VAPI_OK; +} + +vapi_error_e +vapi_get_fd (vapi_ctx_t ctx, int *fd) +{ + return VAPI_ENOTSUP; +} + +vapi_error_e +vapi_send (vapi_ctx_t ctx, void *msg) +{ + vapi_error_e rv = VAPI_OK; + if (!ctx || !msg || !ctx->connected) + { + rv = VAPI_EINVAL; + goto out; + } + int tmp; + unix_shared_memory_queue_t *q = api_main.shmem_hdr->vl_input_queue; +#if VAPI_DEBUG + unsigned msgid = be16toh (*(u16 *) msg); + if (msgid <= ctx->vl_msg_id_max) + { + vapi_msg_id_t id = ctx->vl_msg_id_to_vapi_msg_t[msgid]; + if (id < __vapi_metadata.count) + { + VAPI_DBG ("send msg %u[%s]", msgid, __vapi_metadata.msgs[id]->name); + } + else + { + VAPI_DBG ("send msg %u[UNKNOWN]", msgid); + } + } + else + { + VAPI_DBG ("send msg %u[UNKNOWN]", msgid); + } +#endif + tmp = unix_shared_memory_queue_add (q, (u8 *) & msg, + VAPI_MODE_BLOCKING == + ctx->mode ? 0 : 1); + if (tmp < 0) + { + rv = VAPI_EAGAIN; + } +out: + VAPI_DBG ("vapi_send() rv = %d", rv); + return rv; +} + +vapi_error_e +vapi_send2 (vapi_ctx_t ctx, void *msg1, void *msg2) +{ + vapi_error_e rv = VAPI_OK; + if (!ctx || !msg1 || !msg2 || !ctx->connected) + { + rv = VAPI_EINVAL; + goto out; + } + unix_shared_memory_queue_t *q = api_main.shmem_hdr->vl_input_queue; +#if VAPI_DEBUG + unsigned msgid1 = be16toh (*(u16 *) msg1); + unsigned msgid2 = be16toh (*(u16 *) msg2); + const char *name1 = "UNKNOWN"; + const char *name2 = "UNKNOWN"; + if (msgid1 <= ctx->vl_msg_id_max) + { + vapi_msg_id_t id = ctx->vl_msg_id_to_vapi_msg_t[msgid1]; + if (id < __vapi_metadata.count) + { + name1 = __vapi_metadata.msgs[id]->name; + } + } + if (msgid2 <= ctx->vl_msg_id_max) + { + vapi_msg_id_t id = ctx->vl_msg_id_to_vapi_msg_t[msgid2]; + if (id < __vapi_metadata.count) + { + name2 = __vapi_metadata.msgs[id]->name; + } + } + VAPI_DBG ("send two: %u[%s], %u[%s]", msgid1, name1, msgid2, name2); +#endif + int tmp = unix_shared_memory_queue_add2 (q, (u8 *) & msg1, (u8 *) & msg2, + VAPI_MODE_BLOCKING == + ctx->mode ? 0 : 1); + if (tmp < 0) + { + rv = VAPI_EAGAIN; + } +out: + VAPI_DBG ("vapi_send() rv = %d", rv); + return rv; +} + +vapi_error_e +vapi_recv (vapi_ctx_t ctx, void **msg, size_t * msg_size) +{ + if (!ctx || !ctx->connected || !msg || !msg_size) + { + return VAPI_EINVAL; + } + vapi_error_e rv = VAPI_OK; + api_main_t *am = &api_main; + uword data; + + if (am->our_pid == 0) + { + return VAPI_EINVAL; + } + + unix_shared_memory_queue_t *q = am->vl_input_queue; + VAPI_DBG ("doing shm queue sub"); + int tmp = unix_shared_memory_queue_sub (q, (u8 *) & data, 0); + if (tmp == 0) + { +#if VAPI_DEBUG_ALLOC + vapi_add_to_be_freed ((void *) data); +#endif + msgbuf_t *msgbuf = + (msgbuf_t *) ((u8 *) data - offsetof (msgbuf_t, data)); + if (!msgbuf->data_len) + { + vapi_msg_free (ctx, (u8 *) data); + return VAPI_EAGAIN; + } + *msg = (u8 *) data; + *msg_size = ntohl (msgbuf->data_len); + VAPI_DBG ("recv msg %p", *msg); + } + else + { + rv = VAPI_EAGAIN; + } + return rv; +} + +vapi_error_e +vapi_wait (vapi_ctx_t ctx, vapi_wait_mode_e mode) +{ + /* FIXME */ + return VAPI_ENOTSUP; +} + +static vapi_error_e +vapi_dispatch_response (vapi_ctx_t ctx, vapi_msg_id_t id, + u32 context, void *msg) +{ + int mrv; + if (0 != (mrv = pthread_mutex_lock (&ctx->requests_mutex))) + { + VAPI_DBG ("pthread_mutex_lock() failed, rv=%d:%s", mrv, strerror (mrv)); + return VAPI_MUTEX_FAILURE; + } + int tmp = ctx->requests_start; + const int requests_end = vapi_requests_end (ctx); + while (ctx->requests[tmp].context != context && tmp != requests_end) + { + ++tmp; + if (tmp == ctx->requests_size) + { + tmp = 0; + } + } + VAPI_DBG ("dispatch, search from %d, %s at %d", ctx->requests_start, + ctx->requests[tmp].context == context ? "matched" : "stopped", + tmp); + vapi_error_e rv = VAPI_OK; + if (ctx->requests[tmp].context == context) + { + while (ctx->requests_start != tmp) + { + VAPI_ERR ("No response to req with context=%u", + (unsigned) ctx->requests[tmp].context); + ctx->requests[ctx->requests_start].callback (ctx, + ctx->requests + [ctx-> + requests_start].callback_ctx, + VAPI_ENORESP, true, + NULL); + memset (&ctx->requests[ctx->requests_start], 0, + sizeof (ctx->requests[ctx->requests_start])); + ++ctx->requests_start; + --ctx->requests_count; + if (ctx->requests_start == ctx->requests_size) + { + ctx->requests_start = 0; + } + } + // now ctx->requests_start == tmp + int payload_offset = vapi_get_payload_offset (id); + void *payload = ((u8 *) msg) + payload_offset; + bool is_last = true; + if (ctx->requests[tmp].is_dump) + { + if (vapi_msg_id_control_ping_reply == id) + { + payload = NULL; + } + else + { + is_last = false; + } + } + if (payload_offset != -1) + { + rv = + ctx->requests[tmp].callback (ctx, ctx->requests[tmp].callback_ctx, + VAPI_OK, is_last, payload); + } + else + { + /* this is a message without payload, so bend the callback a little + */ + rv = + ((vapi_error_e (*)(vapi_ctx_t, void *, vapi_error_e, bool)) + ctx->requests[tmp].callback) (ctx, + ctx->requests[tmp].callback_ctx, + VAPI_OK, is_last); + } + if (is_last) + { + memset (&ctx->requests[ctx->requests_start], 0, + sizeof (ctx->requests[ctx->requests_start])); + ++ctx->requests_start; + --ctx->requests_count; + if (ctx->requests_start == ctx->requests_size) + { + ctx->requests_start = 0; + } + } + VAPI_DBG ("after dispatch, req start = %d, end = %d, count = %d", + ctx->requests_start, requests_end, ctx->requests_count); + } + if (0 != (mrv = pthread_mutex_unlock (&ctx->requests_mutex))) + { + VAPI_DBG ("pthread_mutex_unlock() failed, rv=%d:%s", mrv, + strerror (mrv)); + abort (); /* this really shouldn't happen */ + } + return rv; +} + +static vapi_error_e +vapi_dispatch_event (vapi_ctx_t ctx, vapi_msg_id_t id, void *msg) +{ + if (ctx->event_cbs[id].cb) + { + return ctx->event_cbs[id].cb (ctx, ctx->event_cbs[id].ctx, msg); + } + else if (ctx->generic_cb.cb) + { + return ctx->generic_cb.cb (ctx, ctx->generic_cb.ctx, id, msg); + } + else + { + VAPI_DBG + ("No handler/generic handler for msg id %u[%s], message ignored", + (unsigned) id, __vapi_metadata.msgs[id]->name); + } + return VAPI_OK; +} + +static bool +vapi_msg_is_with_context (vapi_msg_id_t id) +{ + assert (id <= __vapi_metadata.count); + return __vapi_metadata.msgs[id]->has_context; +} + +vapi_error_e +vapi_dispatch_one (vapi_ctx_t ctx) +{ + VAPI_DBG ("vapi_dispatch_one()"); + void *msg; + size_t size; + vapi_error_e rv = vapi_recv (ctx, &msg, &size); + if (VAPI_OK != rv) + { + VAPI_DBG ("vapi_recv failed with rv=%d", rv); + return rv; + } + u16 vpp_id = be16toh (*(u16 *) msg); + if (vpp_id > ctx->vl_msg_id_max) + { + VAPI_ERR ("Unknown msg ID received, id `%u', out of range <0,%u>", + (unsigned) vpp_id, (unsigned) ctx->vl_msg_id_max); + vapi_msg_free (ctx, msg); + return VAPI_EINVAL; + } + if (~0 == (unsigned) ctx->vl_msg_id_to_vapi_msg_t[vpp_id]) + { + VAPI_ERR ("Unknown msg ID received, id `%u' marked as not supported", + (unsigned) vpp_id); + vapi_msg_free (ctx, msg); + return VAPI_EINVAL; + } + const vapi_msg_id_t id = ctx->vl_msg_id_to_vapi_msg_t[vpp_id]; + const size_t expect_size = vapi_get_message_size (id); + if (size < expect_size) + { + VAPI_ERR + ("Invalid msg received, unexpected size `%zu' < expected min `%zu'", + size, expect_size); + vapi_msg_free (ctx, msg); + return VAPI_EINVAL; + } + u32 context; + vapi_get_swap_to_host_func (id) (msg); + if (vapi_msg_is_with_context (id)) + { + context = *(u32 *) (((u8 *) msg) + vapi_get_context_offset (id)); + /* is this a message originating from VAPI? */ + VAPI_DBG ("dispatch, context is %x", context); + if (context & context_counter_mask) + { + rv = vapi_dispatch_response (ctx, id, context, msg); + goto done; + } + } + rv = vapi_dispatch_event (ctx, id, msg); + +done: + vapi_msg_free (ctx, msg); + return rv; +} + +vapi_error_e +vapi_dispatch (vapi_ctx_t ctx) +{ + vapi_error_e rv = VAPI_OK; + while (!vapi_requests_empty (ctx)) + { + rv = vapi_dispatch_one (ctx); + if (VAPI_OK != rv) + { + return rv; + } + } + return rv; +} + +void +vapi_set_event_cb (vapi_ctx_t ctx, vapi_msg_id_t id, + vapi_event_cb callback, void *callback_ctx) +{ + vapi_event_cb_with_ctx *c = &ctx->event_cbs[id]; + c->cb = callback; + c->ctx = callback_ctx; +} + +void +vapi_clear_event_cb (vapi_ctx_t ctx, vapi_msg_id_t id) +{ + vapi_set_event_cb (ctx, id, NULL, NULL); +} + +void +vapi_set_generic_event_cb (vapi_ctx_t ctx, vapi_generic_event_cb callback, + void *callback_ctx) +{ + ctx->generic_cb.cb = callback; + ctx->generic_cb.ctx = callback_ctx; +} + +void +vapi_clear_generic_event_cb (vapi_ctx_t ctx) +{ + ctx->generic_cb.cb = NULL; + ctx->generic_cb.ctx = NULL; +} + +u16 +vapi_lookup_vl_msg_id (vapi_ctx_t ctx, vapi_msg_id_t id) +{ + assert (id < __vapi_metadata.count); + return ctx->vapi_msg_id_t_to_vl_msg_id[id]; +} + +int +vapi_get_client_index (vapi_ctx_t ctx) +{ + return api_main.my_client_index; +} + +bool +vapi_is_nonblocking (vapi_ctx_t ctx) +{ + return (VAPI_MODE_NONBLOCKING == ctx->mode); +} + +bool vapi_requests_full (vapi_ctx_t ctx); + +size_t vapi_get_request_count (vapi_ctx_t ctx); + +size_t +vapi_get_max_request_count (vapi_ctx_t ctx) +{ + return ctx->requests_size - 1; +} + +int +vapi_get_payload_offset (vapi_msg_id_t id) +{ + assert (id < __vapi_metadata.count); + return __vapi_metadata.msgs[id]->payload_offset; +} + +void (*vapi_get_swap_to_host_func (vapi_msg_id_t id)) (void *msg) +{ + assert (id < __vapi_metadata.count); + return __vapi_metadata.msgs[id]->swap_to_host; +} + +void (*vapi_get_swap_to_be_func (vapi_msg_id_t id)) (void *msg) +{ + assert (id < __vapi_metadata.count); + return __vapi_metadata.msgs[id]->swap_to_be; +} + +size_t +vapi_get_message_size (vapi_msg_id_t id) +{ + assert (id < __vapi_metadata.count); + return __vapi_metadata.msgs[id]->size; +} + +size_t +vapi_get_context_offset (vapi_msg_id_t id) +{ + assert (id < __vapi_metadata.count); + return __vapi_metadata.msgs[id]->context_offset; +} + +vapi_msg_id_t +vapi_register_msg (vapi_message_desc_t * msg) +{ + int i = 0; + for (i = 0; i < __vapi_metadata.count; ++i) + { + if (!strcmp + (msg->name_with_crc, __vapi_metadata.msgs[i]->name_with_crc)) + { + /* this happens if somebody is linking together several objects while + * using the static inline headers, just fill in the already + * assigned id here so that all the objects are in sync */ + msg->id = __vapi_metadata.msgs[i]->id; + return msg->id; + } + } + vapi_msg_id_t id = __vapi_metadata.count; + ++__vapi_metadata.count; + __vapi_metadata.msgs = + realloc (__vapi_metadata.msgs, + sizeof (*__vapi_metadata.msgs) * __vapi_metadata.count); + __vapi_metadata.msgs[id] = msg; + size_t s = strlen (msg->name_with_crc); + if (s > __vapi_metadata.max_len_name_with_crc) + { + __vapi_metadata.max_len_name_with_crc = s; + } + msg->id = id; + return id; +} + +vapi_error_e +vapi_producer_lock (vapi_ctx_t ctx) +{ + int mrv; + if (0 != (mrv = pthread_mutex_lock (&ctx->requests_mutex))) + { + VAPI_DBG ("pthread_mutex_lock() failed, rv=%d:%s", mrv, strerror (mrv)); + (void) mrv; /* avoid warning if the above debug is not enabled */ + return VAPI_MUTEX_FAILURE; + } + return VAPI_OK; +} + +vapi_error_e +vapi_producer_unlock (vapi_ctx_t ctx) +{ + int mrv; + if (0 != (mrv = pthread_mutex_unlock (&ctx->requests_mutex))) + { + VAPI_DBG ("pthread_mutex_unlock() failed, rv=%d:%s", mrv, + strerror (mrv)); + (void) mrv; /* avoid warning if the above debug is not enabled */ + return VAPI_MUTEX_FAILURE; + } + return VAPI_OK; +} + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ -- cgit 1.2.3-korg From dc15be2ca7c51772b00e4c5548934a35aa7e4add Mon Sep 17 00:00:00 2001 From: Klement Sekera Date: Mon, 12 Jun 2017 06:49:33 +0200 Subject: Add C++ API Change-Id: Iff634f22d43470e2dc028387b3816257fd7b4156 Signed-off-by: Klement Sekera --- .clang-format | 38 ++ build-root/scripts/checkstyle.sh | 62 +- src/configure.ac | 1 + src/vpp-api/vapi/Makefile.am | 27 +- src/vpp-api/vapi/libvapiclient.map | 3 + src/vpp-api/vapi/vapi.c | 59 +- src/vpp-api/vapi/vapi.h | 102 ++- src/vpp-api/vapi/vapi.hpp | 905 ++++++++++++++++++++++++++ src/vpp-api/vapi/vapi_c_gen.py | 188 +----- src/vpp-api/vapi/vapi_common.h | 61 ++ src/vpp-api/vapi/vapi_cpp_gen.py | 262 ++++++++ src/vpp-api/vapi/vapi_dbg.h | 1 + src/vpp-api/vapi/vapi_doc.md | 155 +++++ src/vpp-api/vapi/vapi_internal.h | 22 +- src/vpp-api/vapi/vapi_json_parser.py | 2 + test/ext/Makefile | 26 +- test/ext/fake.api.json | 35 + test/ext/vapi_c_test.c | 1168 ++++++++++++++++++++++++++++++++++ test/ext/vapi_cpp_test.cpp | 591 +++++++++++++++++ test/ext/vapi_test.c | 1152 --------------------------------- test/test_vapi.py | 36 +- 21 files changed, 3483 insertions(+), 1413 deletions(-) create mode 100644 .clang-format create mode 100644 src/vpp-api/vapi/vapi.hpp create mode 100644 src/vpp-api/vapi/vapi_common.h create mode 100755 src/vpp-api/vapi/vapi_cpp_gen.py create mode 100644 src/vpp-api/vapi/vapi_doc.md create mode 100644 test/ext/fake.api.json create mode 100644 test/ext/vapi_c_test.c create mode 100644 test/ext/vapi_cpp_test.cpp delete mode 100644 test/ext/vapi_test.c (limited to 'src/vpp-api/vapi/vapi.c') diff --git a/.clang-format b/.clang-format new file mode 100644 index 00000000..977ed2db --- /dev/null +++ b/.clang-format @@ -0,0 +1,38 @@ +--- +AlignEscapedNewlinesLeft: true +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AllowShortFunctionsOnASingleLine: false +AlwaysBreakBeforeMultilineStrings: false +BreakBeforeBinaryOperators: false +BreakBeforeTernaryOperators: true +BinPackParameters: true +BreakBeforeBraces: GNU +ColumnLimit: 79 +IndentCaseLabels: false +MaxEmptyLinesToKeep: 1 +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 60 +PenaltyBreakString: 1000 +PenaltyBreakFirstLessLess: 120 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 60 +PointerBindsToType: false +SpaceBeforeAssignmentOperators: true +SpaceBeforeParens: Always +SpacesBeforeTrailingComments: 1 +SpacesInParentheses: false +SpaceInEmptyParentheses: false +SpacesInCStyleCastParentheses: false +SpaceAfterControlStatementKeyword: true +Cpp11BracedListStyle: true +Standard: Cpp11 +SortIncludes: false +IndentWidth: 2 +TabWidth: 4 +UseTab: Never +IndentFunctionDeclarationAfterType: false +ContinuationIndentWidth: 4 +... diff --git a/build-root/scripts/checkstyle.sh b/build-root/scripts/checkstyle.sh index 55fe4ab5..bd2ba813 100755 --- a/build-root/scripts/checkstyle.sh +++ b/build-root/scripts/checkstyle.sh @@ -32,37 +32,75 @@ fi # don't *fail*. command -v indent > /dev/null if [ $? != 0 ]; then - echo "Cound not find required commend \"indent\". Checkstyle aborted" + echo "Cound not find required command \"indent\". Checkstyle aborted" exit ${EXIT_CODE} fi indent --version +# Check to make sure we have clang-format. Exit if we don't with an error message, but +# don't *fail*. +command -v clang-format > /dev/null +if [ $? != 0 ]; then + echo "Could not find command \"clang-format\". Checking C++ files will cause abort" + HAVE_CLANG_FORMAT=0 +else + HAVE_CLANG_FORMAT=1 + clang-format --version +fi + cd ${VPP_DIR} git status for i in ${FILELIST}; do if [ -f ${i} ] && [ ${i} != "build-root/scripts/checkstyle.sh" ] && [ ${i} != "extras/emacs/fix-coding-style.el" ]; then grep -q "fd.io coding-style-patch-verification: ON" ${i} if [ $? == 0 ]; then + EXTENSION=`basename ${i} | sed 's/^\w\+.//'` + case ${EXTENSION} in + hpp|cpp|cc|hh) + CMD="clang-format" + if [ ${HAVE_CLANG_FORMAT} == 0 ]; then + echo "C++ file detected. Abort. (missing clang-format)" + exit ${EXIT_CODE} + fi + ;; + *) + CMD="indent" + ;; + esac CHECKSTYLED_FILES="${CHECKSTYLED_FILES} ${i}" if [ ${FIX} == 0 ]; then - indent ${i} -o ${i}.out1 > /dev/null 2>&1 - indent ${i}.out1 -o ${i}.out2 > /dev/null 2>&1 - # Remove trailing whitespace - sed -i -e 's/[[:space:]]*$//' ${i}.out2 + if [ "${CMD}" == "clang-format" ] + then + clang-format ${i} > ${i}.out2 + else + indent ${i} -o ${i}.out1 > /dev/null 2>&1 + indent ${i}.out1 -o ${i}.out2 > /dev/null 2>&1 + fi + # Remove trailing whitespace + sed -i -e 's/[[:space:]]*$//' ${i}.out2 diff -q ${i} ${i}.out2 else - indent ${i} - indent ${i} - # Remove trailing whitespace - sed -i -e 's/[[:space:]]*$//' ${i} + if [ "${CMD}" == "clang-format" ]; then + clang-format -i ${i} > /dev/null 2>&1 + else + indent ${i} + indent ${i} + fi + # Remove trailing whitespace + sed -i -e 's/[[:space:]]*$//' ${i} fi if [ $? != 0 ]; then EXIT_CODE=1 echo echo "Checkstyle failed for ${i}." - echo "Run indent (twice!) as shown to fix the problem:" - echo "indent ${VPP_DIR}${i}" - echo "indent ${VPP_DIR}${i}" + if [ "${CMD}" == "clang-format" ]; then + echo "Run clang-format as shown to fix the problem:" + echo "clang-format -i ${VPP_DIR}${i}" + else + echo "Run indent (twice!) as shown to fix the problem:" + echo "indent ${VPP_DIR}${i}" + echo "indent ${VPP_DIR}${i}" + fi fi if [ -f ${i}.out1 ]; then rm ${i}.out1 diff --git a/src/configure.ac b/src/configure.ac index 2efb23ad..f5ce3be2 100644 --- a/src/configure.ac +++ b/src/configure.ac @@ -7,6 +7,7 @@ AC_CONFIG_FILES([Makefile plugins/Makefile vpp-api/python/Makefile vpp-api/java/ AC_CONFIG_MACRO_DIR([m4]) AC_PROG_CC +AC_PROG_CXX AM_PROG_AS AM_PROG_LIBTOOL AC_PROG_YACC diff --git a/src/vpp-api/vapi/Makefile.am b/src/vpp-api/vapi/Makefile.am index ce681c38..74b2b47e 100644 --- a/src/vpp-api/vapi/Makefile.am +++ b/src/vpp-api/vapi/Makefile.am @@ -15,7 +15,7 @@ AUTOMAKE_OPTIONS = foreign ACLOCAL_AMFLAGS = -I m4 AM_LIBTOOLFLAGS = --quiet -AM_CFLAGS = -Wall -I${top_srcdir} -I${top_builddir} -I. -I$(top_srcdir)/vpp-api/vapi +AM_CFLAGS = -Wall -I${top_srcdir} -I${top_builddir} -I. -I$(top_srcdir)/vpp-api/ AM_LDFLAGS = -shared -avoid-version -rpath /none -no-undefined @@ -23,26 +23,33 @@ bin_PROGRAMS = noinst_LTLIBRARIES = CLEANDIRS = -%.api.vapi.h: %.api.json vapi_c_gen.py +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 $< + $(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,%.api.vapi.h,$(JSON_FILES)) + $(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 \ @@ -54,10 +61,14 @@ libvapiclient_la_LDFLAGS = \ libvapiclient_la_CPPFLAGS = -I. -I$(top_builddir)/vpp-api/vapi -nobase_include_HEADERS = ${top_srcdir}/vpp-api/client/vppapiclient.h \ - vapi.h \ +vapiincludedir = $(includedir)/vapi + +vapiinclude_HEADERS = vapi.h \ + vapi.hpp \ vapi_dbg.h \ + vapi_common.h \ vapi_internal.h \ - $(patsubst %.api.json,%.api.vapi.h,$(JSON_FILES)) + $(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 index 53733002..6b58d1e9 100644 --- a/src/vpp-api/vapi/libvapiclient.map +++ b/src/vpp-api/vapi/libvapiclient.map @@ -23,6 +23,7 @@ VAPICLIENT_17.07 { vapi_register_msg; vapi_get_client_index; vapi_is_nonblocking; + vapi_requests_empty; vapi_requests_full; vapi_gen_req_context; vapi_producer_lock; @@ -36,6 +37,8 @@ VAPICLIENT_17.07 { 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 index b9c81a13..59415e03 100644 --- a/src/vpp-api/vapi/vapi.c +++ b/src/vpp-api/vapi/vapi.c @@ -102,7 +102,7 @@ vapi_requests_full (vapi_ctx_t ctx) return (ctx->requests_count == ctx->requests_size); } -static bool +bool vapi_requests_empty (vapi_ctx_t ctx) { return (0 == ctx->requests_count); @@ -229,6 +229,16 @@ vapi_msg_free (vapi_ctx_t ctx, void *msg) 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) { @@ -420,16 +430,17 @@ vapi_send (vapi_ctx_t ctx, void *msg) vapi_msg_id_t id = ctx->vl_msg_id_to_vapi_msg_t[msgid]; if (id < __vapi_metadata.count) { - VAPI_DBG ("send msg %u[%s]", msgid, __vapi_metadata.msgs[id]->name); + VAPI_DBG ("send msg@%p:%u[%s]", msg, msgid, + __vapi_metadata.msgs[id]->name); } else { - VAPI_DBG ("send msg %u[UNKNOWN]", msgid); + VAPI_DBG ("send msg@%p:%u[UNKNOWN]", msg, msgid); } } else { - VAPI_DBG ("send msg %u[UNKNOWN]", msgid); + VAPI_DBG ("send msg@%p:%u[UNKNOWN]", msg, msgid); } #endif tmp = unix_shared_memory_queue_add (q, (u8 *) & msg, @@ -522,7 +533,26 @@ vapi_recv (vapi_ctx_t ctx, void **msg, size_t * msg_size) } *msg = (u8 *) data; *msg_size = ntohl (msgbuf->data_len); - VAPI_DBG ("recv msg %p", *msg); +#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 { @@ -534,7 +564,6 @@ vapi_recv (vapi_ctx_t ctx, void **msg, size_t * msg_size) vapi_error_e vapi_wait (vapi_ctx_t ctx, vapi_wait_mode_e mode) { - /* FIXME */ return VAPI_ENOTSUP; } @@ -657,7 +686,7 @@ vapi_dispatch_event (vapi_ctx_t ctx, vapi_msg_id_t id, void *msg) return VAPI_OK; } -static bool +bool vapi_msg_is_with_context (vapi_msg_id_t id) { assert (id <= __vapi_metadata.count); @@ -785,10 +814,6 @@ vapi_is_nonblocking (vapi_ctx_t ctx) return (VAPI_MODE_NONBLOCKING == ctx->mode); } -bool vapi_requests_full (vapi_ctx_t ctx); - -size_t vapi_get_request_count (vapi_ctx_t ctx); - size_t vapi_get_max_request_count (vapi_ctx_t ctx) { @@ -886,6 +911,18 @@ vapi_producer_unlock (vapi_ctx_t ctx) 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 * diff --git a/src/vpp-api/vapi/vapi.h b/src/vpp-api/vapi/vapi.h index 1e1d567a..245bf654 100644 --- a/src/vpp-api/vapi/vapi.h +++ b/src/vpp-api/vapi/vapi.h @@ -21,6 +21,12 @@ #include #include #include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif /** * @file vapi.h @@ -36,39 +42,7 @@ * process). It's not recommended to mix the higher and lower level APIs. Due * to version issues, the higher-level APIs are not part of the shared library. */ - -typedef enum -{ - VAPI_OK = 0, /**< success */ - VAPI_EINVAL, /**< invalid value encountered */ - VAPI_EAGAIN, /**< operation would block */ - VAPI_ENOTSUP, /**< operation not supported */ - VAPI_ENOMEM, /**< out of memory */ - VAPI_ENORESP, /**< no response to request */ - VAPI_EMAP_FAIL, /**< failure while mapping api */ - VAPI_ECON_FAIL, /**< failure while connecting to vpp */ - VAPI_EINCOMPATIBLE, /**< fundamental incompatibility while connecting to vpp - (control ping/control ping reply mismatch) */ - VAPI_MUTEX_FAILURE, /**< failure manipulating internal mutex(es) */ - VAPI_EUSER, /**< user error used for breaking dispatch, - never used by VAPI */ -} vapi_error_e; - -typedef enum -{ - VAPI_MODE_BLOCKING = 1, /**< operations block until response received */ - VAPI_MODE_NONBLOCKING = 2, /**< operations never block */ -} vapi_mode_e; - -typedef enum -{ - VAPI_WAIT_FOR_READ, /**< wait until a message can be read */ - VAPI_WAIT_FOR_WRITE, /**< wait until a message can be written */ - VAPI_WAIT_FOR_READ_WRITE, /**< wait until a read or write can be done */ -} vapi_wait_mode_e; - -typedef int vapi_msg_id_t; -typedef struct vapi_ctx_s *vapi_ctx_t; + typedef struct vapi_ctx_s *vapi_ctx_t; /** * @brief allocate vapi message of given size @@ -80,7 +54,7 @@ typedef struct vapi_ctx_s *vapi_ctx_t; * * @return pointer to message or NULL if out of memory */ -void *vapi_msg_alloc (vapi_ctx_t ctx, size_t size); + void *vapi_msg_alloc (vapi_ctx_t ctx, size_t size); /** * @brief free a vapi message @@ -90,7 +64,7 @@ void *vapi_msg_alloc (vapi_ctx_t ctx, size_t size); * @param ctx opaque vapi context * @param msg message to be freed */ -void vapi_msg_free (vapi_ctx_t ctx, void *msg); + void vapi_msg_free (vapi_ctx_t ctx, void *msg); /** * @brief allocate vapi context @@ -99,18 +73,18 @@ void vapi_msg_free (vapi_ctx_t ctx, void *msg); * * @return VAPI_OK on success, other error code on error */ -vapi_error_e vapi_ctx_alloc (vapi_ctx_t * result); + vapi_error_e vapi_ctx_alloc (vapi_ctx_t * result); /** * @brief free vapi context */ -void vapi_ctx_free (vapi_ctx_t ctx); + 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); + bool vapi_is_msg_available (vapi_ctx_t ctx, vapi_msg_id_t type); /** * @brief connect to vpp @@ -124,10 +98,10 @@ bool vapi_is_msg_available (vapi_ctx_t ctx, vapi_msg_id_t type); * * @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); + 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 @@ -136,7 +110,7 @@ vapi_error_e vapi_connect (vapi_ctx_t ctx, const char *name, * * @return VAPI_OK on success, other error code on error */ -vapi_error_e vapi_disconnect (vapi_ctx_t ctx); + vapi_error_e vapi_disconnect (vapi_ctx_t ctx); /** * @brief get event file descriptor @@ -149,7 +123,7 @@ vapi_error_e vapi_disconnect (vapi_ctx_t ctx); * * @return VAPI_OK on success, other error code on error */ -vapi_error_e vapi_get_fd (vapi_ctx_t ctx, int *fd); + vapi_error_e vapi_get_fd (vapi_ctx_t ctx, int *fd); /** * @brief low-level api for sending messages to vpp @@ -162,7 +136,7 @@ vapi_error_e vapi_get_fd (vapi_ctx_t ctx, int *fd); * * @return VAPI_OK on success, other error code on error */ -vapi_error_e vapi_send (vapi_ctx_t ctx, void *msg); + vapi_error_e vapi_send (vapi_ctx_t ctx, void *msg); /** * @brief low-level api for atomically sending two messages to vpp - either @@ -177,7 +151,7 @@ vapi_error_e vapi_send (vapi_ctx_t ctx, void *msg); * * @return VAPI_OK on success, other error code on error */ -vapi_error_e vapi_send2 (vapi_ctx_t ctx, void *msg1, void *msg2); + vapi_error_e vapi_send2 (vapi_ctx_t ctx, void *msg1, void *msg2); /** * @brief low-level api for reading messages from vpp @@ -191,7 +165,7 @@ vapi_error_e vapi_send2 (vapi_ctx_t ctx, void *msg1, void *msg2); * * @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); + vapi_error_e vapi_recv (vapi_ctx_t ctx, void **msg, size_t * msg_size); /** * @brief wait for connection to become readable or writable @@ -201,14 +175,14 @@ vapi_error_e vapi_recv (vapi_ctx_t ctx, void **msg, size_t * msg_size); * * @return VAPI_OK on success, other error code on error */ -vapi_error_e vapi_wait (vapi_ctx_t ctx, vapi_wait_mode_e mode); + 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); + vapi_error_e vapi_dispatch_one (vapi_ctx_t ctx); /** * @brief loop vapi_dispatch_one until responses to all currently outstanding @@ -224,11 +198,11 @@ vapi_error_e vapi_dispatch_one (vapi_ctx_t ctx); * * @return VAPI_OK on success, other error code on error */ -vapi_error_e vapi_dispatch (vapi_ctx_t ctx); + 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); + 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 @@ -238,8 +212,8 @@ typedef vapi_error_e (*vapi_event_cb) (vapi_ctx_t ctx, void *callback_ctx, * @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); + 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 @@ -247,12 +221,12 @@ void vapi_set_event_cb (vapi_ctx_t ctx, vapi_msg_id_t id, * @param ctx opaque vapi context * @param id message id */ -void vapi_clear_event_cb (vapi_ctx_t ctx, vapi_msg_id_t 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); + 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 * @@ -263,16 +237,20 @@ typedef vapi_error_e (*vapi_generic_event_cb) (vapi_ctx_t ctx, * @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); + 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); + void vapi_clear_generic_event_cb (vapi_ctx_t ctx); + +#ifdef __cplusplus +} +#endif #endif 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if VAPI_CPP_DEBUG_LEAKS +#include +#endif + +/** + * @file + * @brief C++ VPP API + */ + +namespace vapi +{ + +class Connection; + +template class Request; +template class Msg; +template void vapi_swap_to_be (M *msg); +template void vapi_swap_to_host (M *msg); +template +M *vapi_alloc (Connection &con, Args...); +template vapi_msg_id_t vapi_get_msg_id_t (); +template 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 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 friend class Msg; + + template + friend class Request; + + template friend class Dump; + + template 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 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 requests_lock (requests_mutex); + std::lock_guard events_lock (events_mutex); + vapi_msg_id_t id = vapi_lookup_vapi_msg_id_t ( + vapi_ctx, be16toh (*static_cast (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 ( + (static_cast (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