diff options
author | Sachin Saxena <sachin.saxena@freescale.com> | 2018-02-28 20:28:52 +0530 |
---|---|---|
committer | Sachin Saxena <sachin.saxena@nxp.com> | 2018-02-28 20:34:56 +0530 |
commit | 0689fce93ba269c48f83a2f70f971b3976d04c90 (patch) | |
tree | 4cc2908df3598507cc1828ac19d8c43b22450ffa /src/vpp-api | |
parent | 746b57564deede624261ab8a96c94f562f24d22c (diff) | |
parent | d594711a5d79859a7d0bde83a516f7ab52051d9b (diff) |
Merge branch 'stable/1710' of https://gerrit.fd.io/r/vpp into 17101710
Diffstat (limited to 'src/vpp-api')
143 files changed, 19434 insertions, 0 deletions
diff --git a/src/vpp-api/client/client.c b/src/vpp-api/client/client.c new file mode 100644 index 00000000..8bdcda01 --- /dev/null +++ b/src/vpp-api/client/client.c @@ -0,0 +1,489 @@ +/* + * Copyright (c) 2016 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include <stdio.h> +#include <stdlib.h> +#include <stddef.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <netinet/in.h> +#include <netdb.h> +#include <signal.h> +#include <stdbool.h> +#include <vnet/vnet.h> +#include <vlib/vlib.h> +#include <vlib/unix/unix.h> +#include <vlibapi/api.h> +#include <vlibmemory/api.h> + +#include <vpp/api/vpe_msg_enum.h> + +#include "vppapiclient.h" + +/* + * Asynchronous mode: + * Client registers a callback. All messages are sent to the callback. + * Synchronous mode: + * Client calls blocking read(). + * Clients are expected to collate events on a queue. + * vac_write() -> suspends RX thread + * vac_read() -> resumes RX thread + */ + +#define vl_typedefs /* define message structures */ +#include <vpp/api/vpe_all_api_h.h> +#undef vl_typedefs + +#define vl_endianfun /* define message structures */ +#include <vpp/api/vpe_all_api_h.h> +#undef vl_endianfun + +vlib_main_t vlib_global_main; +vlib_main_t **vlib_mains; + +typedef struct { + u8 connected_to_vlib; + pthread_t rx_thread_handle; + pthread_t timeout_thread_handle; + pthread_mutex_t queue_lock; + pthread_cond_t suspend_cv; + pthread_cond_t resume_cv; + pthread_mutex_t timeout_lock; + pthread_cond_t timeout_cv; + pthread_cond_t timeout_cancel_cv; + pthread_cond_t terminate_cv; +} vac_main_t; + +vac_main_t vac_main; +vac_callback_t vac_callback; +u16 read_timeout = 0; +bool rx_is_running = false; + +static void +init (void) +{ + vac_main_t *pm = &vac_main; + memset(pm, 0, sizeof(*pm)); + pthread_mutex_init(&pm->queue_lock, NULL); + pthread_cond_init(&pm->suspend_cv, NULL); + pthread_cond_init(&pm->resume_cv, NULL); + pthread_mutex_init(&pm->timeout_lock, NULL); + pthread_cond_init(&pm->timeout_cv, NULL); + pthread_cond_init(&pm->timeout_cancel_cv, NULL); + pthread_cond_init(&pm->terminate_cv, NULL); +} + +static void +cleanup (void) +{ + vac_main_t *pm = &vac_main; + pthread_cond_destroy(&pm->suspend_cv); + pthread_cond_destroy(&pm->resume_cv); + pthread_cond_destroy(&pm->timeout_cv); + pthread_cond_destroy(&pm->timeout_cancel_cv); + pthread_cond_destroy(&pm->terminate_cv); + pthread_mutex_destroy(&pm->queue_lock); + pthread_mutex_destroy(&pm->timeout_lock); + memset (pm, 0, sizeof (*pm)); +} + +/* + * Satisfy external references when -lvlib is not available. + */ +void vlib_cli_output (struct vlib_main_t * vm, char * fmt, ...) +{ + clib_warning ("vlib_cli_output called..."); +} + +void +vac_free (void * msg) +{ + vl_msg_api_free (msg); +} + +static void +vac_api_handler (void *msg) +{ + u16 id = ntohs(*((u16 *)msg)); + msgbuf_t *msgbuf = (msgbuf_t *)(((u8 *)msg) - offsetof(msgbuf_t, data)); + int l = ntohl(msgbuf->data_len); + if (l == 0) + clib_warning("Message ID %d has wrong length: %d\n", id, l); + + /* Call Python callback */ + ASSERT(vac_callback); + (vac_callback)(msg, l); + vac_free(msg); +} + +static void * +vac_rx_thread_fn (void *arg) +{ + unix_shared_memory_queue_t *q; + vac_main_t *pm = &vac_main; + api_main_t *am = &api_main; + uword msg; + + q = am->vl_input_queue; + + while (1) + while (!unix_shared_memory_queue_sub(q, (u8 *)&msg, 0)) + { + u16 id = ntohs(*((u16 *)msg)); + switch (id) { + case VL_API_RX_THREAD_EXIT: + vl_msg_api_free((void *) msg); + /* signal waiting threads that this thread is about to terminate */ + pthread_mutex_lock(&pm->queue_lock); + pthread_cond_signal(&pm->terminate_cv); + pthread_mutex_unlock(&pm->queue_lock); + pthread_exit(0); + return 0; + break; + + case VL_API_MEMCLNT_RX_THREAD_SUSPEND: + vl_msg_api_free((void * )msg); + /* Suspend thread and signal reader */ + pthread_mutex_lock(&pm->queue_lock); + pthread_cond_signal(&pm->suspend_cv); + /* Wait for the resume signal */ + pthread_cond_wait (&pm->resume_cv, &pm->queue_lock); + pthread_mutex_unlock(&pm->queue_lock); + break; + + case VL_API_MEMCLNT_READ_TIMEOUT: + clib_warning("Received read timeout in async thread\n"); + vl_msg_api_free((void *) msg); + break; + + default: + vac_api_handler((void *)msg); + } + } +} + +static void * +vac_timeout_thread_fn (void *arg) +{ + vl_api_memclnt_read_timeout_t *ep; + vac_main_t *pm = &vac_main; + api_main_t *am = &api_main; + struct timespec ts; + struct timeval tv; + u16 timeout; + int rv; + + while (1) + { + /* Wait for poke */ + pthread_mutex_lock(&pm->timeout_lock); + pthread_cond_wait (&pm->timeout_cv, &pm->timeout_lock); + timeout = read_timeout; + gettimeofday(&tv, NULL); + ts.tv_sec = tv.tv_sec + timeout; + ts.tv_nsec = 0; + rv = pthread_cond_timedwait (&pm->timeout_cancel_cv, + &pm->timeout_lock, &ts); + pthread_mutex_unlock(&pm->timeout_lock); + if (rv == ETIMEDOUT) + { + ep = vl_msg_api_alloc (sizeof (*ep)); + ep->_vl_msg_id = ntohs(VL_API_MEMCLNT_READ_TIMEOUT); + vl_msg_api_send_shmem(am->vl_input_queue, (u8 *)&ep); + } + } + pthread_exit(0); +} + +void +vac_rx_suspend (void) +{ + api_main_t *am = &api_main; + vac_main_t *pm = &vac_main; + vl_api_memclnt_rx_thread_suspend_t *ep; + + if (!pm->rx_thread_handle) return; + pthread_mutex_lock(&pm->queue_lock); + if (rx_is_running) + { + ep = vl_msg_api_alloc (sizeof (*ep)); + ep->_vl_msg_id = ntohs(VL_API_MEMCLNT_RX_THREAD_SUSPEND); + vl_msg_api_send_shmem(am->vl_input_queue, (u8 *)&ep); + /* Wait for RX thread to tell us it has suspendend */ + pthread_cond_wait(&pm->suspend_cv, &pm->queue_lock); + rx_is_running = false; + } + pthread_mutex_unlock(&pm->queue_lock); +} + +void +vac_rx_resume (void) +{ + vac_main_t *pm = &vac_main; + if (!pm->rx_thread_handle) return; + pthread_mutex_lock(&pm->queue_lock); + if (rx_is_running) goto unlock; + pthread_cond_signal(&pm->resume_cv); + rx_is_running = true; + unlock: + pthread_mutex_unlock(&pm->queue_lock); +} + +static uword * +vac_msg_table_get_hash (void) +{ + api_main_t *am = &api_main; + return (am->msg_index_by_name_and_crc); +} + +int +vac_msg_table_size(void) +{ + api_main_t *am = &api_main; + return hash_elts(am->msg_index_by_name_and_crc); +} + +int +vac_connect (char * name, char * chroot_prefix, vac_callback_t cb, + int rx_qlen) +{ + int rv = 0; + vac_main_t *pm = &vac_main; + + init(); + if (chroot_prefix != NULL) + vl_set_memory_root_path (chroot_prefix); + + if ((rv = vl_client_api_map("/vpe-api"))) { + clib_warning ("vl_client_api map rv %d", rv); + return rv; + } + + if (vl_client_connect(name, 0, rx_qlen) < 0) { + vl_client_api_unmap(); + return (-1); + } + + if (cb) { + /* Start the rx queue thread */ + rv = pthread_create(&pm->rx_thread_handle, NULL, vac_rx_thread_fn, 0); + if (rv) { + clib_warning("pthread_create returned %d", rv); + vl_client_api_unmap(); + return (-1); + } + vac_callback = cb; + rx_is_running = true; + } + + /* Start read timeout thread */ + rv = pthread_create(&pm->timeout_thread_handle, NULL, + vac_timeout_thread_fn, 0); + if (rv) { + clib_warning("pthread_create returned %d", rv); + vl_client_api_unmap(); + return (-1); + } + + pm->connected_to_vlib = 1; + + return (0); +} + +int +vac_disconnect (void) +{ + api_main_t *am = &api_main; + vac_main_t *pm = &vac_main; + + if (!pm->connected_to_vlib) return 0; + + if (pm->rx_thread_handle) { + vl_api_rx_thread_exit_t *ep; + uword junk; + ep = vl_msg_api_alloc (sizeof (*ep)); + ep->_vl_msg_id = ntohs(VL_API_RX_THREAD_EXIT); + vl_msg_api_send_shmem(am->vl_input_queue, (u8 *)&ep); + + /* wait (with timeout) until RX thread has finished */ + struct timespec ts; + struct timeval tv; + gettimeofday(&tv, NULL); + ts.tv_sec = tv.tv_sec + 5; + ts.tv_nsec = 0; + pthread_mutex_lock(&pm->queue_lock); + int rv = pthread_cond_timedwait(&pm->terminate_cv, &pm->queue_lock, &ts); + pthread_mutex_unlock(&pm->queue_lock); + /* now join so we wait until thread has -really- finished */ + if (rv == ETIMEDOUT) + pthread_cancel(pm->rx_thread_handle); + else + pthread_join(pm->rx_thread_handle, (void **) &junk); + } + if (pm->timeout_thread_handle) + pthread_cancel(pm->timeout_thread_handle); + + vl_client_disconnect(); + vl_client_api_unmap(); + vac_callback = 0; + + cleanup(); + + return (0); +} + +static void +set_timeout (unsigned short timeout) +{ + vac_main_t *pm = &vac_main; + pthread_mutex_lock(&pm->timeout_lock); + read_timeout = timeout; + pthread_cond_signal(&pm->timeout_cv); + pthread_mutex_unlock(&pm->timeout_lock); +} + +static void +unset_timeout (void) +{ + vac_main_t *pm = &vac_main; + pthread_mutex_lock(&pm->timeout_lock); + pthread_cond_signal(&pm->timeout_cancel_cv); + pthread_mutex_unlock(&pm->timeout_lock); +} + +int +vac_read (char **p, int *l, u16 timeout) +{ + unix_shared_memory_queue_t *q; + api_main_t *am = &api_main; + vac_main_t *pm = &vac_main; + uword msg; + msgbuf_t *msgbuf; + + if (!pm->connected_to_vlib) return -1; + + *l = 0; + + if (am->our_pid == 0) return (-1); + + /* Poke timeout thread */ + if (timeout) + set_timeout(timeout); + + q = am->vl_input_queue; + int rv = unix_shared_memory_queue_sub(q, (u8 *)&msg, 0); + if (rv == 0) { + u16 msg_id = ntohs(*((u16 *)msg)); + switch (msg_id) { + case VL_API_RX_THREAD_EXIT: + printf("Received thread exit\n"); + return -1; + case VL_API_MEMCLNT_RX_THREAD_SUSPEND: + printf("Received thread suspend\n"); + goto error; + case VL_API_MEMCLNT_READ_TIMEOUT: + printf("Received read timeout %ds\n", timeout); + goto error; + + default: + msgbuf = (msgbuf_t *)(((u8 *)msg) - offsetof(msgbuf_t, data)); + *l = ntohl(msgbuf->data_len); + if (*l == 0) { + printf("Unregistered API message: %d\n", msg_id); + goto error; + } + } + *p = (char *)msg; + + /* Let timeout notification thread know we're done */ + unset_timeout(); + + } else { + printf("Read failed with %d\n", rv); + } + return (rv); + + error: + vl_msg_api_free((void *) msg); + /* Client might forget to resume RX thread on failure */ + vac_rx_resume (); + return -1; +} + +/* + * XXX: Makes the assumption that client_index is the first member + */ +typedef VL_API_PACKED(struct _vl_api_header { + u16 _vl_msg_id; + u32 client_index; +}) vl_api_header_t; + +static unsigned int +vac_client_index (void) +{ + return (api_main.my_client_index); +} + +int +vac_write (char *p, int l) +{ + int rv = -1; + api_main_t *am = &api_main; + vl_api_header_t *mp = vl_msg_api_alloc(l); + unix_shared_memory_queue_t *q; + vac_main_t *pm = &vac_main; + + if (!pm->connected_to_vlib) return -1; + if (!mp) return (-1); + + memcpy(mp, p, l); + mp->client_index = vac_client_index(); + q = am->shmem_hdr->vl_input_queue; + rv = unix_shared_memory_queue_add(q, (u8 *)&mp, 0); + if (rv != 0) { + clib_warning("vpe_api_write fails: %d\n", rv); + /* Clear message */ + vac_free(mp); + } + return (rv); +} + +int +vac_get_msg_index (unsigned char * name) +{ + return vl_api_get_msg_index (name); +} + +int +vac_msg_table_max_index(void) +{ + int max = 0; + hash_pair_t *hp; + uword *h = vac_msg_table_get_hash(); + hash_foreach_pair (hp, h, + ({ + if (hp->value[0] > max) + max = hp->value[0]; + })); + + return max; +} + +void +vac_set_error_handler (vac_error_callback_t cb) +{ + if (cb) clib_error_register_handler (cb, 0); +} diff --git a/src/vpp-api/client/libvppapiclient.map b/src/vpp-api/client/libvppapiclient.map new file mode 100644 index 00000000..a9d8f7dd --- /dev/null +++ b/src/vpp-api/client/libvppapiclient.map @@ -0,0 +1,19 @@ + +VPPAPICLIENT_17.07 { + global: + vac_read; + vac_write; + vac_connect; + vac_disconnect; + vac_set_error_handler; + vac_msg_table_max_index; + vac_get_msg_index; + vac_rx_suspend; + vac_rx_resume; + vac_free; + vac_msg_table_size; + + api_main; + + local: *; +}; diff --git a/src/vpp-api/client/test.c b/src/vpp-api/client/test.c new file mode 100644 index 00000000..020115d9 --- /dev/null +++ b/src/vpp-api/client/test.c @@ -0,0 +1,140 @@ +/* + *------------------------------------------------------------------ + * test.c + * + * Copyright (c) 2016 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *------------------------------------------------------------------ + */ +#include <stdio.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <netinet/in.h> +#include <netdb.h> + +#include <time.h> /* time_t, time (for timestamp in second) */ +#include <sys/timeb.h> /* ftime, timeb (for timestamp in millisecond) */ +#include <sys/time.h> /* gettimeofday, timeval (for timestamp in microsecond) */ + +#include <vnet/vnet.h> +#include <vlib/vlib.h> +#include <vlib/unix/unix.h> +#include <vlibapi/api.h> + +#include <vpp/api/vpe_msg_enum.h> +#include <signal.h> +#include "vppapiclient.h" + +#define vl_typedefs /* define message structures */ +#include <vpp/api/vpe_all_api_h.h> +#undef vl_typedefs + +/* we are not linking with vlib */ +vlib_main_t vlib_global_main; +vlib_main_t **vlib_mains; + +volatile int sigterm_received = 0; +volatile u32 result_ready; +volatile u16 result_msg_id; + +/* M_NOALLOC: construct, but don't yet send a message */ + +#define M_NOALLOC(T,t) \ + do { \ + result_ready = 0; \ + memset (mp, 0, sizeof (*mp)); \ + mp->_vl_msg_id = ntohs (VL_API_##T); \ + mp->client_index = am->my_client_index; \ + } while(0); + + + +int +wrap_vac_callback (char *data, int len) +{ + //printf("Callback %d\n", len); + result_ready = 1; + result_msg_id = ntohs(*((u16 *)data)); + return (0); +} + +int main (int argc, char ** argv) +{ + api_main_t * am = &api_main; + vl_api_show_version_t message; + vl_api_show_version_t *mp; + int async = 1; + int rv = vac_connect("vac_client", NULL, NULL, 32 /* rx queue-length*/); + + if (rv != 0) { + printf("Connect failed: %d\n", rv); + exit(rv); + } + + struct timeb timer_msec; + long long int timestamp_msec_start; /* timestamp in millisecond. */ + if (!ftime(&timer_msec)) { + timestamp_msec_start = ((long long int) timer_msec.time) * 1000ll + + (long long int) timer_msec.millitm; + } + else { + timestamp_msec_start = -1; + } + + + /* + * Test vpe_api_write and vpe_api_read to send and recv message for an + * API + */ + int i; + long int no_msgs = 10000; + mp = &message; + + for (i = 0; i < no_msgs; i++) { + /* Construct the API message */ + M_NOALLOC(SHOW_VERSION, show_version); + vac_write((char *)mp, sizeof(*mp)); +#ifndef __COVERITY__ + /* As given, async is always 1. Shut up Coverity about it */ + if (!async) + while (result_ready == 0); +#endif + } + if (async) { + vl_api_control_ping_t control; + vl_api_control_ping_t *mp; + mp = &control; + M_NOALLOC(CONTROL_PING, control_ping); + vac_write((char *)mp, sizeof(*mp)); + + while (result_msg_id != VL_API_CONTROL_PING_REPLY); + } + + long long int timestamp_msec_end; /* timestamp in millisecond. */ + if (!ftime(&timer_msec)) { + timestamp_msec_end = ((long long int) timer_msec.time) * 1000ll + + (long long int) timer_msec.millitm; + } + else { + timestamp_msec_end = -1; + } + + printf("Took %lld msec, %lld msgs/msec \n", (timestamp_msec_end - timestamp_msec_start), + no_msgs/(timestamp_msec_end - timestamp_msec_start)); + printf("Exiting...\n"); + vac_disconnect(); + exit (0); +} diff --git a/src/vpp-api/client/vppapiclient.h b/src/vpp-api/client/vppapiclient.h new file mode 100644 index 00000000..839ec1f8 --- /dev/null +++ b/src/vpp-api/client/vppapiclient.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2016 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_vppapiclient_h +#define included_vppapiclient_h + +#include <stdint.h> + +typedef void (*vac_callback_t)(unsigned char * data, int len); +typedef void (*vac_error_callback_t)(void *, unsigned char *, int); +int vac_connect(char * name, char * chroot_prefix, vac_callback_t cb, + int rx_qlen); +int vac_disconnect(void); +int vac_read(char **data, int *l, unsigned short timeout); +int vac_write(char *data, int len); +void vac_free(void * msg); + +int vac_get_msg_index(unsigned char * name); +int vac_msg_table_size(void); +int vac_msg_table_max_index(void); + +void vac_rx_suspend (void); +void vac_rx_resume (void); +void vac_set_error_handler(vac_error_callback_t); +#endif diff --git a/src/vpp-api/java/Makefile.am b/src/vpp-api/java/Makefile.am new file mode 100644 index 00000000..637bb774 --- /dev/null +++ b/src/vpp-api/java/Makefile.am @@ -0,0 +1,263 @@ +# Copyright (c) 2016 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$(JAVA_HOME)/include -I$(JAVA_HOME)/include/linux \ + -I@top_srcdir@/plugins -I@top_builddir@/plugins + +AM_LDFLAGS = -shared -avoid-version -rpath /none -no-undefined + +BUILT_SOURCES = +bin_PROGRAMS = +noinst_LTLIBRARIES = +JAR_FILES = +CLEANDIRS = + +# +# jvpp-common +# + +nobase_include_HEADERS = \ + jvpp-common/jvpp_common.h + +lib_LTLIBRARIES = libjvpp_common.la +libjvpp_common_la_SOURCES = jvpp-common/jvpp_common.c +libjvpp_common_la_LDFLAGS = shared -rpath /none -no-undefined + +JVPP_LIBS = \ + libjvpp_common.la \ + $(top_builddir)/libvppinfra.la \ + $(top_builddir)/libvlibmemoryclient.la \ + $(top_builddir)/libsvm.la \ + -lpthread -lm -lrt + +# +# jvpp-registry (connection management + plugin registry) +# + +noinst_LTLIBRARIES += libjvpp_registry.la + +libjvpp_registry_la_SOURCES = jvpp-registry/jvpp_registry.c +libjvpp_registry_la_CPPFLAGS = -Ijvpp-registry +libjvpp_registry_la_LIBADD = $(JVPP_LIBS) +libjvpp_registry_la_DEPENDENCIES = libjvpp_common.la + +packagedir_jvpp_registry = io/fd/vpp/jvpp +jvpp_registry_src_files := \ + $(wildcard @srcdir@/jvpp-registry/$(packagedir_jvpp_registry)/*.java) \ + $(wildcard @srcdir@/jvpp-registry/$(packagedir_jvpp_registry)/**/*.java) + +BUILT_SOURCES += jvpp-registry/io_fd_vpp_jvpp_VppJNIConnection.h +CLEANDIRS += jvpp-registry/target +JAR_FILES += jvpp-registry-$(PACKAGE_VERSION).jar + +jvpp_registry_ok = jvpp-registry/io_fd_vpp_jvpp_VppJNIConnection.h + +jvpp-registry/io_fd_vpp_jvpp_VppJNIConnection.h: $(jvpp_registry_src_files) + @echo " JAPIGEN $@" + @rm -rf jvpp-registry/target + @mkdir -p jvpp-registry/target + @$(JAVAC) -d jvpp-registry/target $^ + @$(JAVAH) -force -classpath jvpp-registry/target -d jvpp-registry io.fd.vpp.jvpp.VppJNIConnection + @$(JAVAH) -force -classpath jvpp-registry/target -d jvpp-registry io.fd.vpp.jvpp.JVppRegistryImpl + @touch jvpp-registry.ok + +define japigen + @echo " JAPIGEN $@" + @rm -rf jvpp-$(1)/target + @ @srcdir@/jvpp/gen/jvpp_gen.py --plugin_name $(1) --root_dir jvpp-$(1) \ + -i $(jvpp_$(1)_json_files) > /dev/null + @find jvpp-$(1)/target -name \*.java > jvpp-$(1).generated.files + @find @srcdir@/jvpp-$(1) -name \*.java > jvpp-$(1).static.files + @$(JAVAC) -classpath jvpp-registry/target \ + -d jvpp-$(1)/target @jvpp-$(1).generated.files @jvpp-$(1).static.files + @$(JAVAH) -force \ + -classpath jvpp-registry/target:jvpp-$(1)/target \ + -d jvpp-$(1) io.fd.vpp.jvpp.$(1).$(2) +endef + +# +# jvpp-core (Java wrapper for vpe.api) +# +noinst_LTLIBRARIES += libjvpp_core.la +libjvpp_core_la_SOURCES = jvpp-core/jvpp_core.c jvpp-core/jvpp_core_gen.h +libjvpp_core_la_CPPFLAGS = -Ijvpp-registry -Ijvpp-core +libjvpp_core_la_LIBADD = $(JVPP_LIBS) +libjvpp_core_la_DEPENDENCIES = libjvpp_common.la + +BUILT_SOURCES += jvpp-core/io_fd_vpp_jvpp_core_JVppCoreImpl.h +JAR_FILES += jvpp-core-$(PACKAGE_VERSION).jar +CLEANDIRS += jvpp-core/target +jvpp_core_json_files = $(shell find @top_builddir@/vnet/ -type f -name '*.api.json') +jvpp_core_json_files += @top_builddir@/vpp/api/vpe.api.json + +jvpp-core/io_fd_vpp_jvpp_core_JVppCoreImpl.h: $(jvpp_registry_ok) $(jvpp_core_json_files) + $(call japigen,core,JVppCoreImpl) + +# +# ACL Plugin +# +if ENABLE_ACL_PLUGIN +noinst_LTLIBRARIES += libjvpp_acl.la +libjvpp_acl_la_SOURCES = jvpp-acl/jvpp_acl.c +libjvpp_acl_la_CPPFLAGS = -Ijvpp-acl +libjvpp_acl_la_LIBADD = $(JVPP_LIBS) +libjvpp_acl_la_DEPENDENCIES = libjvpp_common.la + +BUILT_SOURCES += jvpp-acl/io_fd_vpp_jvpp_acl_JVppAclImpl.h +JAR_FILES += jvpp-acl-$(PACKAGE_VERSION).jar +CLEANDIRS += jvpp-acl/target + +jvpp_acl_json_files = @top_builddir@/plugins/acl/acl.api.json + +jvpp-acl/io_fd_vpp_jvpp_acl_JVppAclImpl.h: $(jvpp_registry_ok) $(jvpp_acl_json_files) + $(call japigen,acl,JVppAclImpl) +endif + +# +# GTPU Plugin +# +if ENABLE_GTPU_PLUGIN +noinst_LTLIBRARIES += libjvpp_gtpu.la +libjvpp_gtpu_la_SOURCES = jvpp-gtpu/jvpp_gtpu.c +libjvpp_gtpu_la_CPPFLAGS = -Ijvpp-gtpu +libjvpp_gtpu_la_LIBADD = $(JVPP_LIBS) +libjvpp_gtpu_la_DEPENDENCIES = libjvpp_common.la + +BUILT_SOURCES += jvpp-gtpu/io_fd_vpp_jvpp_gtpu_JVppGtpuImpl.h +JAR_FILES += jvpp-gtpu-$(PACKAGE_VERSION).jar +CLEANDIRS += jvpp-gtpu/target + +jvpp_gtpu_json_files = @top_builddir@/plugins/gtpu/gtpu.api.json + +jvpp-gtpu/io_fd_vpp_jvpp_gtpu_JVppGtpuImpl.h: $(jvpp_registry_ok) $(jvpp_gtpu_json_files) + $(call japigen,gtpu,JVppGtpuImpl) +endif + +# +# PPPOE Plugin +# +if ENABLE_PPPOE_PLUGIN +noinst_LTLIBRARIES += libjvpp_pppoe.la +libjvpp_pppoe_la_SOURCES = jvpp-pppoe/jvpp_pppoe.c +libjvpp_pppoe_la_CPPFLAGS = -Ijvpp-pppoe +libjvpp_pppoe_la_LIBADD = $(JVPP_LIBS) +libjvpp_pppoe_la_DEPENDENCIES = libjvpp_common.la + +BUILT_SOURCES += jvpp-pppoe/io_fd_vpp_jvpp_pppoe_JVppPppoeImpl.h +JAR_FILES += jvpp-pppoe-$(PACKAGE_VERSION).jar +CLEANDIRS += jvpp-pppoe/target + +jvpp_pppoe_json_files = @top_builddir@/plugins/pppoe/pppoe.api.json + +jvpp-pppoe/io_fd_vpp_jvpp_pppoe_JVppPppoeImpl.h: $(jvpp_registry_ok) $(jvpp_pppoe_json_files) + $(call japigen,pppoe,JVppPppoeImpl) +endif + +# +# NAT Plugin +# +if ENABLE_NAT_PLUGIN +noinst_LTLIBRARIES += libjvpp_nat.la +libjvpp_nat_la_SOURCES = jvpp-nat/jvpp_nat.c +libjvpp_nat_la_CPPFLAGS = -Ijvpp-nat +libjvpp_nat_la_LIBADD = $(JVPP_LIBS) +libjvpp_nat_la_DEPENDENCIES = libjvpp_common.la + +BUILT_SOURCES += jvpp-nat/io_fd_vpp_jvpp_nat_JVppNatImpl.h +JAR_FILES += jvpp-nat-$(PACKAGE_VERSION).jar +CLEANDIRS += jvpp-nat/target + +jvpp_nat_json_files = @top_builddir@/plugins/nat/nat.api.json + +jvpp-nat/io_fd_vpp_jvpp_nat_JVppNatImpl.h: $(jvpp_registry_ok) $(jvpp_nat_json_files) + $(call japigen,nat,JVppNatImpl) +endif + +# +# iOAM Trace Plugin +# +if ENABLE_IOAM_PLUGIN +noinst_LTLIBRARIES += libjvpp_ioamtrace.la +libjvpp_ioamtrace_la_SOURCES = jvpp-ioamtrace/jvpp_ioam_trace.c +libjvpp_ioamtrace_la_LIBADD = $(JVPP_LIBS) +libjvpp_ioamtrace_la_DEPENDENCIES = libjvpp_common.la + +BUILT_SOURCES += jvpp-ioamtrace/io_fd_vpp_jvpp_ioamtrace_JVppIoamtraceImpl.h +JAR_FILES += jvpp-ioamtrace-$(PACKAGE_VERSION).jar +CLEANDIRS += jvpp-ioamtrace/target + +jvpp_ioamtrace_json_files = @top_builddir@/plugins/ioam/lib-trace/trace.api.json + +jvpp-ioamtrace/io_fd_vpp_jvpp_ioamtrace_JVppIoamtraceImpl.h: $(jvpp_registry_ok) $(jvpp_ioamtrace_json_files) + $(call japigen,ioamtrace,JVppIoamtraceImpl) + +# +# iOAM POT Plugin +# +noinst_LTLIBRARIES += libjvpp_ioampot.la +libjvpp_ioampot_la_SOURCES = jvpp-ioampot/jvpp_ioam_pot.c +libjvpp_ioampot_la_LIBADD = $(JVPP_LIBS) +libjvpp_ioampot_la_DEPENDENCIES = libjvpp_common.la + +BUILT_SOURCES += jvpp-ioampot/io_fd_vpp_jvpp_ioampot_JVppIoampotImpl.h +JAR_FILES += jvpp-ioampot-$(PACKAGE_VERSION).jar +CLEANDIRS += jvpp-ioampot/target + +jvpp_ioampot_json_files = @top_builddir@/plugins/ioam/lib-pot/pot.api.json + +jvpp-ioampot/io_fd_vpp_jvpp_ioampot_JVppIoampotImpl.h: $(jvpp_registry_ok) $(jvpp_ioampot_json_files) + $(call japigen,ioampot,JVppIoampotImpl) + +# +# iOAM Export Plugin +# +noinst_LTLIBRARIES += libjvpp_ioamexport.la +libjvpp_ioamexport_la_SOURCES = jvpp-ioamexport/jvpp_ioam_export.c +libjvpp_ioamexport_la_LIBADD = $(JVPP_LIBS) +libjvpp_ioamexport_la_DEPENDENCIES = libjvpp_common.la + +BUILT_SOURCES += jvpp-ioamexport/io_fd_vpp_jvpp_ioamexport_JVppIoamexportImpl.h +JAR_FILES += jvpp-ioamexport-$(PACKAGE_VERSION).jar +CLEANDIRS += jvpp-ioamexport/target + +jvpp_ioamexport_json_files = @top_builddir@/plugins/ioam/export/ioam_export.api.json + +jvpp-ioamexport/io_fd_vpp_jvpp_ioamexport_JVppIoamexportImpl.h: $(jvpp_registry_ok) $(jvpp_ioamexport_json_files) + $(call japigen,ioamexport,JVppIoamexportImpl) +endif + +# +# JAR creation +# +jvpp-%-$(PACKAGE_VERSION).jar: libjvpp_%.la + @echo " JAR $@" + @cp .libs/libjvpp_$*.so jvpp-$*/target + @$(JAR) cf $(JARFLAGS) $@ -C jvpp-$*/target . + +jardir = $(prefix)/share/java +jar_DATA = $(JAR_FILES) + +all-local: $(JAR_FILES) + +# +# Cleanup +# +CLEANFILES = jvpp-registry.ok $(JAR_FILES) $(BUILT_SOURCES) *.files */*.h + +clean-local: + rm -rf $(CLEANDIRS) diff --git a/src/vpp-api/java/Readme.txt b/src/vpp-api/java/Readme.txt new file mode 100644 index 00000000..689b9b37 --- /dev/null +++ b/src/vpp-api/java/Readme.txt @@ -0,0 +1,236 @@ += JVpp + +JVpp is JNI based Java API for VPP. + +== Features +It is: + +* Asynchronous +* Fully generated +* Lightweight + +== Architecture + +=== Plugin support + + /-------------\ /--------------\ /---------------\ + | JvppPlugin1 +<-------+ JVppRegistry +--------->+ VppConnection | + \-------------/ inits \--+-----------/ uses \---------------/ + | + /-------------\ | + | JvppPlugin2 +<----------+ inits + \-------------/ | + | + ... | + | + /----------\ | + | JVppCore +<-------------+ + \----------/ + + +VppRegistry opens connection to vpp (VppConnection) and manages jvpp plugins. +Each plugin needs to be registered in the VppRegistry. Registration involves +plugin initialization (providing JNI implementation with JVppCallback reference, +vpp client identifier and vpp shared memory queue address). + +API user sends message by calling a method of appropriate plugin interface. +The call is delegated to JNI implementation provided by the particular plugin. +When JNI code receives reply, it invokes callback method of JVppCallback +that corresponds to the received message reply. + +=== JVppCore as an example of JVpp plugin architecture + + JVpp Java + + /--------------\ /----------\ /------------\ /------\ + | JVppRegistry | | JVppCore | | Callbacks | | DTOs | + \----+---------/ \----+-----/ \------+-----/ \------/ + ^ ^ ^ + | implements | implements | implements + /----+--------------\ /---+----------\ /-----+---------\ + | JVppRegistryImpl* +-------->+ JVppCoreImpl | | JVppCallback | + \-------+-----------/ inits \---+----------/ \-------+-------/ + | | ^ + | | uses | calls back + | | | +----------|--------------------------|-----------------------|--------------------- + | | | + C JNI | +-------------------+ | /-----------------\ + v | | +-->+ jvpp_core_gen.h | + /--------+--------\ | | | \-----------------/ + | jpp_registry.c* +---+ /--------+----+----\ | | | + \-----------------/ | | << shared lib >> | /-+--+---+------\ + + ->+ jvpp_common* <--------+ jvpp_core.c* | + uses \------------------/ uses \---------------/ + + +* Components marked with an asterisk contain manually crafted code, which in addition +to generated classes form jvpp. Exception applies to Callbacks and DTOs, since there are +manually crafted marker interfaces in callback and dto package (dto/JVppRequest, dto/JVppReply, +dto/JVppDump, dto/JVppReplyDump, callback/JVppCallback) + +Note: jvpp_core.c calls back the JVppCallback instance with every response. An instance of the +JVppCallback is provided to jvpp_core.c by JVppRegistryImpl on JVppCoreImpl initialization. + +Part of the JVpp is also Future facade. It is asynchronous API returning Future objects +on top of low level JVpp. It wraps dump reply messages in one DTO using control_ping message +(provided by JVppRegistry). + + +Future facade + + /----------------\ /---------------\ + | FutureJVppCore | +-->+ JVppRegistry* | + \-----+----------/ | \---------------/ + ^ | + | implements | uses + | | + /--------+-------------\ | /------------------------------\ + | FutureJVppCoreFacade +---+--->+ FutureJVppCoreFacadeCallback | + \---------+------------/ uses \-------+----------------------/ + | | +---------------|-----------------------------|------------------------------- + | uses | implements +JVpp Java | | + | | + /----------\ | | + | JVppCore +<-+ | + \----+-----/ | + ^ | + | implements v + /----+---------\ /--------+---------------\ + | JVppCoreImpl | | JVppCoreGlobalCallback | + \--------------/ \------------------------/ + + + +Another useful utility of the JVpp is Callback facade. It is asynchronous API +capable of calling specific callback instance (provided when performing a call) +per call. + + +Callback facade + + /------------------\ /---------------\ + | CallbackJVppCore | +-->+ JVppRegistry* | + \-----+------------/ | \---------------/ + ^ | + | implements | uses + | | + /--------+---------------\ | /--------------------------\ + | CallbackJVppCoreFacade +---+--->+ CallbackJVppCoreCallback | + \---------+--------------/ uses \-----+--------------------/ + | | +---------------|-----------------------------|------------------------------- + | uses | implements +JVpp Java | | + | | + /----------\ | | + | JVppCore +<-+ | + \----+-----/ | + ^ | + | implements v + /----+---------\ /----------+-------------\ + | JVppCoreImpl | | JVppCoreGlobalCallback | + \--------------/ \------------------------/ + + +== Package structure + +* *io.fd.vpp.jvpp* - top level package for generated JVpp interface+ implementation and hand-crafted +VppConnection interface + implementation - packaged as jvpp-registry-version.jar + +* *io.fd.vpp.jvpp.[plugin]* - top level package for generated JVpp interface + implementation ++ plugin's API tests - packaged as jvpp-[plugin]-version.jar + +** *dto* - package for DTOs generated from VPP API structures + base/marker hand-crafted interfaces +(in case of jvpp-registry) +** *callback* - package for low-level JVpp callbacks and a global callback interface implementing each of +the low-level JVppcallbacks +** *future* - package for future based facade on top of JVpp and callbacks +** *callfacade* - package for callback based facade on top of JVpp and callbacks. Allowing +users to provide callback per request +** *test* - package for JVpp standalone tests. Can also serve as samples for JVpp. + +C code is structured into modules: + +* *jvpp_common* - shared library that provides jvpp_main_t reference used by jvpp_registry and plugins. + +* *jvpp_registry* - native library used by JVppRegistryImpl, responsible for: + +** VPP connection open/close +** Rx thread to java thread attach +** control ping message handling + +* *jvpp_core* - native library used by jvpp core plugin: +** *jvpp_core.c* - contains hand crafted code for core plugin initialization +** *jvpp_core_gen.h* - contains generated JNI compatible handlers for all requests and replies defined in vpe.api + +== Code generators +All of the required code except the base/marker interfaces is generated using +simple python2 code generators. The generators use __defs_vpp_papi.py__ input +file produced by __vppapigen__ from vpe.api file. + +=== JNI compatible C code +Produces __jvpp_[plugin]_gen.h__ file containing JNI compatible handlers for each VPP +request and reply. + +[NOTE] +==== +Source: jvpp_c_gen.py +==== + +=== Request/Reply DTOs +For all the structures in __defs_vpp_papi.py__ a POJO DTO is produced. Logically, +there are 4 types of DTOs: + +* Request - requests that can be sent to VPP and only a single response is expected +* DumpRequest - requests that can be sent to VPP and a stream of responses is expected +* Reply - reply to a simple request or a single response from dump triggered response stream +* ReplyDump - collection of replies from a single dump request +* Notifications/Events - Not implemented yet + +[NOTE] +==== +Source: dto_gen.py +==== + +=== JVpp +Produces __JVpp.java__ and __JVppImpl.java__. This is the layer right above JNI compatible C +code. + +[NOTE] +==== +Source: jvpp_impl_gen.py +==== + +=== Callbacks +Produces callback interface for each VPP reply + a global callback interface called +__JVpp[plugin]GlobalCallback.java__ aggregating all of the callback interfaces. The JNI +compatible C code expects only a single instance of this global callback and calls +it with every reply. + +[NOTE] +==== +Source: callback_gen.py +==== + +=== Future facade +Produces an asynchronous facade on top of JVpp and callbacks, which returns a Future that provides +matching reply once VPP invocation finishes. Sources produced: +__FutureJVpp[plugin].java, FutureJVpp[plugin]Facade.java and FutureJVpp[plugin]Callback.java__ + +[NOTE] +==== +Source: jvpp_future_facade_gen.py +==== + +=== Callback facade +Similar to future facade, only this facade takes callback objects as part of the invocation +and the callback is called with result once VPP invocation finishes. Sources produced: +__CallbackJVpp[plugin].java, CallbackJVpp[plugin]Facade.java and CallbackJVpp[plugin]Callback.java__ + +[NOTE] +==== +Source: jvpp_callback_facade_gen.py +==== diff --git a/src/vpp-api/java/jvpp-acl/io/fd/vpp/jvpp/acl/examples/AclExpectedDumpData.java b/src/vpp-api/java/jvpp-acl/io/fd/vpp/jvpp/acl/examples/AclExpectedDumpData.java new file mode 100644 index 00000000..4806052f --- /dev/null +++ b/src/vpp-api/java/jvpp-acl/io/fd/vpp/jvpp/acl/examples/AclExpectedDumpData.java @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.acl.examples; + + +import static io.fd.vpp.jvpp.acl.examples.AclTestData.FIRST_RULE_ADDRESS_2_AS_ARRAY; +import static io.fd.vpp.jvpp.acl.examples.AclTestData.FIRST_RULE_ADDRESS_AS_ARRAY; +import static io.fd.vpp.jvpp.acl.examples.AclTestData.FIRST_RULE_DST_ICMP_TYPE_END; +import static io.fd.vpp.jvpp.acl.examples.AclTestData.FIRST_RULE_DST_ICMP_TYPE_START; +import static io.fd.vpp.jvpp.acl.examples.AclTestData.FIRST_RULE_MAC; +import static io.fd.vpp.jvpp.acl.examples.AclTestData.FIRST_RULE_MAC_MASK; +import static io.fd.vpp.jvpp.acl.examples.AclTestData.FIRST_RULE_PREFIX; +import static io.fd.vpp.jvpp.acl.examples.AclTestData.FIRST_RULE_PREFIX_2; +import static io.fd.vpp.jvpp.acl.examples.AclTestData.FIRST_RULE_SRC_ICMP_TYPE_END; +import static io.fd.vpp.jvpp.acl.examples.AclTestData.FIRST_RULE_SRC_ICMP_TYPE_START; +import static io.fd.vpp.jvpp.acl.examples.AclTestData.ICMP_PROTOCOL; +import static io.fd.vpp.jvpp.acl.examples.AclTestData.SECOND_RULE_ADDRESS_2_AS_ARRAY; +import static io.fd.vpp.jvpp.acl.examples.AclTestData.SECOND_RULE_ADDRESS_AS_ARRAY; +import static io.fd.vpp.jvpp.acl.examples.AclTestData.SECOND_RULE_DST_PORT_RANGE_END; +import static io.fd.vpp.jvpp.acl.examples.AclTestData.SECOND_RULE_DST_PORT_RANGE_START; +import static io.fd.vpp.jvpp.acl.examples.AclTestData.SECOND_RULE_MAC; +import static io.fd.vpp.jvpp.acl.examples.AclTestData.SECOND_RULE_MAC_MASK; +import static io.fd.vpp.jvpp.acl.examples.AclTestData.SECOND_RULE_PREFIX; +import static io.fd.vpp.jvpp.acl.examples.AclTestData.SECOND_RULE_PREFIX_2; +import static io.fd.vpp.jvpp.acl.examples.AclTestData.SECOND_RULE_SRC_PORT_RANGE_END; +import static io.fd.vpp.jvpp.acl.examples.AclTestData.SECOND_RULE_SRC_PORT_RANGE_START; +import static io.fd.vpp.jvpp.acl.examples.AclTestData.UDP_PROTOCOL; + +import io.fd.vpp.jvpp.acl.dto.AclDetails; +import io.fd.vpp.jvpp.acl.dto.AclInterfaceListDetails; +import io.fd.vpp.jvpp.acl.dto.MacipAclDetails; +import io.fd.vpp.jvpp.acl.types.AclRule; +import io.fd.vpp.jvpp.acl.types.MacipAclRule; +import java.util.Arrays; + +class AclExpectedDumpData { + + static void verifyMacIpDump(final MacipAclDetails macipAclDetails) { + // asserting data create by previous call + assertEquals(0, macipAclDetails.aclIndex); + assertEquals(2, macipAclDetails.count); + + final MacipAclRule currentIpv4Rule = macipAclDetails.r[0]; + final MacipAclRule currentIpv6Rule = macipAclDetails.r[1]; + + // Comparing one property at the time to better pointer if something is wrong + //Ipv4 rule + assertEquals(0, currentIpv4Rule.isIpv6); + assertEquals(1, currentIpv4Rule.isPermit); + + // cutting expected ipv4 to 4 bytes,vpp sends it as 16 always + assertArrays(FIRST_RULE_ADDRESS_AS_ARRAY, Arrays.copyOfRange(currentIpv4Rule.srcIpAddr, 0, 4)); + assertEquals(FIRST_RULE_PREFIX, currentIpv4Rule.srcIpPrefixLen); + assertArrays(FIRST_RULE_MAC, currentIpv4Rule.srcMac); + assertArrays(FIRST_RULE_MAC_MASK, currentIpv4Rule.srcMacMask); + + //Ipv6 rule + assertEquals(1, currentIpv6Rule.isIpv6); + assertEquals(0, currentIpv6Rule.isPermit); + assertArrays(SECOND_RULE_ADDRESS_AS_ARRAY, currentIpv6Rule.srcIpAddr); + assertEquals(SECOND_RULE_PREFIX, currentIpv6Rule.srcIpPrefixLen); + assertArrays(SECOND_RULE_MAC, currentIpv6Rule.srcMac); + assertArrays(SECOND_RULE_MAC_MASK, currentIpv6Rule.srcMacMask); + } + + static void verifyAclDump(final AclDetails aclDetails) { + assertEquals(0, aclDetails.aclIndex); + assertEquals(2, aclDetails.count); + + final AclRule currentIpv4Rule = aclDetails.r[0]; + final AclRule currentIpv6Rule = aclDetails.r[1]; + + // Comparing one property at the time to better pointer if something is wrong + //Ipv4 rule + assertEquals(0, currentIpv4Rule.isIpv6); + assertEquals(1, currentIpv4Rule.isPermit); + + // cutting expected ipv4 to 4 bytes,vpp sends it as 16 always + assertArrays(FIRST_RULE_ADDRESS_AS_ARRAY, Arrays.copyOfRange(currentIpv4Rule.srcIpAddr, 0, 4)); + assertEquals(FIRST_RULE_PREFIX, currentIpv4Rule.srcIpPrefixLen); + assertArrays(FIRST_RULE_ADDRESS_2_AS_ARRAY, Arrays.copyOfRange(currentIpv4Rule.dstIpAddr, 0, 4)); + assertEquals(FIRST_RULE_PREFIX_2, currentIpv4Rule.dstIpPrefixLen); + + assertEquals(ICMP_PROTOCOL, currentIpv4Rule.proto); + assertEquals(FIRST_RULE_SRC_ICMP_TYPE_START, currentIpv4Rule.srcportOrIcmptypeFirst); + assertEquals(FIRST_RULE_SRC_ICMP_TYPE_END, currentIpv4Rule.srcportOrIcmptypeLast); + assertEquals(FIRST_RULE_DST_ICMP_TYPE_START, currentIpv4Rule.dstportOrIcmpcodeFirst); + assertEquals(FIRST_RULE_DST_ICMP_TYPE_END, currentIpv4Rule.dstportOrIcmpcodeLast); + + assertArrays(SECOND_RULE_ADDRESS_AS_ARRAY, currentIpv6Rule.srcIpAddr); + assertEquals(SECOND_RULE_PREFIX, currentIpv6Rule.srcIpPrefixLen); + assertArrays(SECOND_RULE_ADDRESS_2_AS_ARRAY, currentIpv6Rule.dstIpAddr); + assertEquals(SECOND_RULE_PREFIX_2, currentIpv6Rule.dstIpPrefixLen); + + assertEquals(UDP_PROTOCOL, currentIpv6Rule.proto); + assertEquals(SECOND_RULE_SRC_PORT_RANGE_START, currentIpv6Rule.srcportOrIcmptypeFirst); + assertEquals(SECOND_RULE_SRC_PORT_RANGE_END, currentIpv6Rule.srcportOrIcmptypeLast); + assertEquals(SECOND_RULE_DST_PORT_RANGE_START, currentIpv6Rule.dstportOrIcmpcodeFirst); + assertEquals(SECOND_RULE_DST_PORT_RANGE_END, currentIpv6Rule.dstportOrIcmpcodeLast); + } + + static void verifyAclInterfaceList(final AclInterfaceListDetails aclInterfaceListDetails) { + assertEquals(1, aclInterfaceListDetails.count); + assertEquals(1, aclInterfaceListDetails.acls[0]); + assertEquals(0, aclInterfaceListDetails.nInput); + assertEquals(0, aclInterfaceListDetails.swIfIndex); + } + + private static void assertArrays(final byte[] expected, final byte[] actual) { + if (!Arrays.equals(expected, actual)) { + throw new IllegalArgumentException( + String.format("Expected[%s]/Actual[%s]", Arrays.toString(expected), Arrays.toString(actual))); + } + } + + private static void assertEquals(final int expected, final int actual) { + if (expected != actual) { + throw new IllegalArgumentException(String.format("Expected[%s]/Actual[%s]", expected, actual)); + } + } +} diff --git a/src/vpp-api/java/jvpp-acl/io/fd/vpp/jvpp/acl/examples/AclTestData.java b/src/vpp-api/java/jvpp-acl/io/fd/vpp/jvpp/acl/examples/AclTestData.java new file mode 100644 index 00000000..199b1b6b --- /dev/null +++ b/src/vpp-api/java/jvpp-acl/io/fd/vpp/jvpp/acl/examples/AclTestData.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.acl.examples; + + +import io.fd.vpp.jvpp.acl.types.AclRule; +import io.fd.vpp.jvpp.acl.types.MacipAclRule; + +class AclTestData { + + static final byte[] FIRST_RULE_ADDRESS_AS_ARRAY = {-64, -88, 2, 1}; + static final byte[] FIRST_RULE_ADDRESS_2_AS_ARRAY = {-64, -88, 2, 3}; + static final byte[] SECOND_RULE_ADDRESS_AS_ARRAY = + {32, 1, 13, -72, 10, 11, 18, -16, 0, 0, 0, 0, 0, 0, 0, 1}; + static final byte[] SECOND_RULE_ADDRESS_2_AS_ARRAY = + {32, 1, 13, -72, 10, 11, 18, -16, 0, 0, 0, 0, 0, 0, 0, 1}; + static final byte[] FIRST_RULE_MAC = {11, 11, 11, 11, 11, 11}; + static final byte[] FIRST_RULE_MAC_MASK = {0, 0, 0, 0, 0, 0}; + static final byte[] SECOND_RULE_MAC = {11, 12, 11, 11, 12, 11}; + static final byte[] SECOND_RULE_MAC_MASK = {(byte) 170, 0, 0, 0, 0, 0}; + static final int FIRST_RULE_PREFIX = 32; + static final int FIRST_RULE_PREFIX_2 = 24; + static final int SECOND_RULE_PREFIX = 64; + static final int SECOND_RULE_PREFIX_2 = 62; + static final int FIRST_RULE_DST_ICMP_TYPE_START = 0; + static final int FIRST_RULE_DST_ICMP_TYPE_END = 8; + static final int FIRST_RULE_SRC_ICMP_TYPE_START = 1; + static final int FIRST_RULE_SRC_ICMP_TYPE_END = 7; + static final int ICMP_PROTOCOL = 1; + static final int SECOND_RULE_DST_PORT_RANGE_START = 2000; + static final int SECOND_RULE_DST_PORT_RANGE_END = 6000; + static final int SECOND_RULE_SRC_PORT_RANGE_START = 400; + static final int SECOND_RULE_SRC_PORT_RANGE_END = 2047; + static final int UDP_PROTOCOL = 17; + + + static MacipAclRule[] createMacipRules() { + MacipAclRule ruleOne = new MacipAclRule(); + ruleOne.isIpv6 = 0; + ruleOne.isPermit = 1; + ruleOne.srcIpAddr = FIRST_RULE_ADDRESS_AS_ARRAY; + ruleOne.srcIpPrefixLen = FIRST_RULE_PREFIX; + ruleOne.srcMac = FIRST_RULE_MAC; + ruleOne.srcMacMask = FIRST_RULE_MAC_MASK;// no mask + + MacipAclRule ruleTwo = new MacipAclRule(); + ruleTwo.isIpv6 = 1; + ruleTwo.isPermit = 0; + ruleTwo.srcIpAddr = SECOND_RULE_ADDRESS_AS_ARRAY; + ruleTwo.srcIpPrefixLen = SECOND_RULE_PREFIX; + ruleTwo.srcMac = SECOND_RULE_MAC; + ruleTwo.srcMacMask = SECOND_RULE_MAC_MASK; + + return new MacipAclRule[]{ruleOne, ruleTwo}; + } + + static AclRule[] createAclRules() { + AclRule ruleOne = new AclRule(); + + ruleOne.isIpv6 = 0; + ruleOne.isPermit = 1; + ruleOne.srcIpAddr = FIRST_RULE_ADDRESS_AS_ARRAY; + ruleOne.srcIpPrefixLen = FIRST_RULE_PREFIX; + ruleOne.dstIpAddr = FIRST_RULE_ADDRESS_2_AS_ARRAY; + ruleOne.dstIpPrefixLen = FIRST_RULE_PREFIX_2; + ruleOne.dstportOrIcmpcodeFirst = FIRST_RULE_DST_ICMP_TYPE_START; + ruleOne.dstportOrIcmpcodeLast = FIRST_RULE_DST_ICMP_TYPE_END; + ruleOne.srcportOrIcmptypeFirst = FIRST_RULE_SRC_ICMP_TYPE_START; + ruleOne.srcportOrIcmptypeLast = FIRST_RULE_SRC_ICMP_TYPE_END; + ruleOne.proto = ICMP_PROTOCOL; //ICMP + + AclRule ruleTwo = new AclRule(); + ruleTwo.isIpv6 = 1; + ruleTwo.isPermit = 0; + ruleTwo.srcIpAddr = SECOND_RULE_ADDRESS_AS_ARRAY; + ruleTwo.srcIpPrefixLen = SECOND_RULE_PREFIX; + ruleTwo.dstIpAddr = SECOND_RULE_ADDRESS_2_AS_ARRAY; + ruleTwo.dstIpPrefixLen = SECOND_RULE_PREFIX_2; + ruleTwo.dstportOrIcmpcodeFirst = SECOND_RULE_DST_PORT_RANGE_START; + ruleTwo.dstportOrIcmpcodeLast = SECOND_RULE_DST_PORT_RANGE_END; + ruleTwo.srcportOrIcmptypeFirst = SECOND_RULE_SRC_PORT_RANGE_START; + ruleTwo.srcportOrIcmptypeLast = SECOND_RULE_SRC_PORT_RANGE_END; + ruleTwo.proto = UDP_PROTOCOL; //UDP + + return new AclRule[]{ruleOne, ruleTwo}; + } +} diff --git a/src/vpp-api/java/jvpp-acl/io/fd/vpp/jvpp/acl/examples/AclTestRequests.java b/src/vpp-api/java/jvpp-acl/io/fd/vpp/jvpp/acl/examples/AclTestRequests.java new file mode 100644 index 00000000..149ea46e --- /dev/null +++ b/src/vpp-api/java/jvpp-acl/io/fd/vpp/jvpp/acl/examples/AclTestRequests.java @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.acl.examples; + +import static io.fd.vpp.jvpp.acl.examples.AclTestData.createAclRules; +import static io.fd.vpp.jvpp.acl.examples.AclTestData.createMacipRules; + +import io.fd.vpp.jvpp.VppInvocationException; +import io.fd.vpp.jvpp.acl.dto.AclAddReplace; +import io.fd.vpp.jvpp.acl.dto.AclAddReplaceReply; +import io.fd.vpp.jvpp.acl.dto.AclDel; +import io.fd.vpp.jvpp.acl.dto.AclDelReply; +import io.fd.vpp.jvpp.acl.dto.AclDetailsReplyDump; +import io.fd.vpp.jvpp.acl.dto.AclDump; +import io.fd.vpp.jvpp.acl.dto.AclInterfaceListDetailsReplyDump; +import io.fd.vpp.jvpp.acl.dto.AclInterfaceListDump; +import io.fd.vpp.jvpp.acl.dto.AclInterfaceSetAclList; +import io.fd.vpp.jvpp.acl.dto.AclInterfaceSetAclListReply; +import io.fd.vpp.jvpp.acl.dto.MacipAclAdd; +import io.fd.vpp.jvpp.acl.dto.MacipAclAddReply; +import io.fd.vpp.jvpp.acl.dto.MacipAclAddReplace; +import io.fd.vpp.jvpp.acl.dto.MacipAclAddReplaceReply; +import io.fd.vpp.jvpp.acl.dto.MacipAclDel; +import io.fd.vpp.jvpp.acl.dto.MacipAclDelReply; +import io.fd.vpp.jvpp.acl.dto.MacipAclDetailsReplyDump; +import io.fd.vpp.jvpp.acl.dto.MacipAclDump; +import io.fd.vpp.jvpp.acl.future.FutureJVppAclFacade; +import java.util.concurrent.ExecutionException; + +class AclTestRequests { + + static MacipAclDetailsReplyDump sendMacIpDumpRequest(final FutureJVppAclFacade jvpp) + throws ExecutionException, InterruptedException { + System.out.println("Sending MacipAclDump request..."); + MacipAclDetailsReplyDump dump = jvpp.macipAclDump(new MacipAclDump()).toCompletableFuture().get(); + System.out.println("MacipAclDump returned"); + return dump; + } + + static void sendMacIpAddRequest(final FutureJVppAclFacade jvpp) throws InterruptedException, ExecutionException { + final MacipAclAdd request = createMacIpAddRequest(); + System.out.printf("Sending MacipAclAdd request %s%n", request.toString()); + final MacipAclAddReply reply = jvpp.macipAclAdd(createMacIpAddRequest()).toCompletableFuture().get(); + System.out.printf("MacipAclAdd send result = %s%n", reply); + } + + static void sendMacIpAddReplaceRequest(final FutureJVppAclFacade jvpp) throws InterruptedException, ExecutionException { + final MacipAclAddReplace request = createMacIpAddReplaceRequest(); + System.out.printf("Sending MacipAclAddReplace request %s%n", request.toString()); + final MacipAclAddReplaceReply reply = jvpp.macipAclAddReplace(createMacIpAddReplaceRequest()).toCompletableFuture().get(); + System.out.printf("MacipAclAddReplace send result = %s%n", reply); + } + + static void sendMacIpDelRequest(final FutureJVppAclFacade jvpp) throws InterruptedException, ExecutionException { + final MacipAclDel request = new MacipAclDel(); + request.aclIndex = 0; + System.out.printf("Sending MacipAclDel request %s%n", request.toString()); + final MacipAclDelReply reply = jvpp.macipAclDel(request).toCompletableFuture().get(); + System.out.printf("MacipAclDel send result = %s%n", reply); + } + + static void sendAclAddRequest(final FutureJVppAclFacade jvpp) throws InterruptedException, ExecutionException { + final AclAddReplace request = createAclAddRequest(); + System.out.printf("Sending AclAddReplace request %s%n", request.toString()); + final AclAddReplaceReply reply = jvpp.aclAddReplace(request).toCompletableFuture().get(); + System.out.printf("AclAddReplace send result = %s%n", reply); + } + + static AclDetailsReplyDump sendAclDumpRequest(final FutureJVppAclFacade jvpp) + throws InterruptedException, VppInvocationException, ExecutionException { + System.out.println("Sending AclDump request..."); + final AclDetailsReplyDump dump = jvpp.aclDump(new AclDump()).toCompletableFuture().get(); + System.out.printf("AclDump send result = %s%n", dump); + return dump; + } + + static void sendAclDelRequest(final FutureJVppAclFacade jvpp) throws InterruptedException, ExecutionException { + final AclDel request = new AclDel(); + request.aclIndex = 0; + System.out.printf("Sending AclDel request %s%n", request.toString()); + final AclDelReply reply = jvpp.aclDel(request).toCompletableFuture().get(); + System.out.printf("AclDel send result = %s%n", reply); + } + + static AclInterfaceListDetailsReplyDump sendAclInterfaceListDumpRequest(final FutureJVppAclFacade jvpp) + throws InterruptedException, ExecutionException { + final AclInterfaceListDump request = new AclInterfaceListDump(); + request.swIfIndex = 0; + System.out.printf("Sending AclInterfaceListDump request %s%n", request.toString()); + final AclInterfaceListDetailsReplyDump dump = jvpp.aclInterfaceListDump(request).toCompletableFuture().get(); + System.out.printf("AclInterfaceListDump send result = %s%n", dump); + return dump; + } + + static void sendAclInterfaceSetAclList(final FutureJVppAclFacade jvpp) + throws InterruptedException, ExecutionException { + final AclInterfaceSetAclList request = new AclInterfaceSetAclList(); + request.count = 1; + request.acls = new int[]{1}; + request.swIfIndex = 0; + request.nInput = 0; + System.out.printf("Sending AclInterfaceSetAclList request %s%n", request.toString()); + final AclInterfaceSetAclListReply reply = jvpp.aclInterfaceSetAclList(request).toCompletableFuture().get(); + System.out.printf("AclInterfaceSetAclList send result = %s%n", reply); + } + + static void sendAclInterfaceDeleteList(final FutureJVppAclFacade jvpp) + throws InterruptedException, ExecutionException { + // uses same api but sets list to empty + final AclInterfaceSetAclList request = new AclInterfaceSetAclList(); + request.count = 0; + request.acls = new int[]{}; + request.swIfIndex = 0; + request.nInput = 0; + System.out.printf("Sending AclInterfaceSetAclList(Delete) request %s%n", request.toString()); + final AclInterfaceSetAclListReply reply = jvpp.aclInterfaceSetAclList(request).toCompletableFuture().get(); + System.out.printf("AclInterfaceSetAclList(Delete) send result = %s%n", reply); + } + + private static MacipAclAdd createMacIpAddRequest() { + MacipAclAdd request = new MacipAclAdd(); + + request.count = 2; + request.r = createMacipRules(); + return request; + } + + private static MacipAclAddReplace createMacIpAddReplaceRequest() { + MacipAclAddReplace request = new MacipAclAddReplace(); + + request.count = 2; + request.aclIndex = 0; + request.r = createMacipRules(); + return request; + } + + private static AclAddReplace createAclAddRequest() { + AclAddReplace request = new AclAddReplace(); + + request.aclIndex = -1;// to define new one + request.count = 2; + request.r = createAclRules(); + return request; + } +} diff --git a/src/vpp-api/java/jvpp-acl/io/fd/vpp/jvpp/acl/examples/FutureApiExample.java b/src/vpp-api/java/jvpp-acl/io/fd/vpp/jvpp/acl/examples/FutureApiExample.java new file mode 100644 index 00000000..862df8df --- /dev/null +++ b/src/vpp-api/java/jvpp-acl/io/fd/vpp/jvpp/acl/examples/FutureApiExample.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.acl.examples; + +import static io.fd.vpp.jvpp.acl.examples.AclExpectedDumpData.verifyAclDump; +import static io.fd.vpp.jvpp.acl.examples.AclExpectedDumpData.verifyAclInterfaceList; +import static io.fd.vpp.jvpp.acl.examples.AclExpectedDumpData.verifyMacIpDump; +import static io.fd.vpp.jvpp.acl.examples.AclTestRequests.sendAclAddRequest; +import static io.fd.vpp.jvpp.acl.examples.AclTestRequests.sendAclDelRequest; +import static io.fd.vpp.jvpp.acl.examples.AclTestRequests.sendAclDumpRequest; +import static io.fd.vpp.jvpp.acl.examples.AclTestRequests.sendAclInterfaceDeleteList; +import static io.fd.vpp.jvpp.acl.examples.AclTestRequests.sendAclInterfaceListDumpRequest; +import static io.fd.vpp.jvpp.acl.examples.AclTestRequests.sendAclInterfaceSetAclList; +import static io.fd.vpp.jvpp.acl.examples.AclTestRequests.sendMacIpAddRequest; +import static io.fd.vpp.jvpp.acl.examples.AclTestRequests.sendMacIpDelRequest; +import static io.fd.vpp.jvpp.acl.examples.AclTestRequests.sendMacIpDumpRequest; + +import io.fd.vpp.jvpp.JVppRegistry; +import io.fd.vpp.jvpp.JVppRegistryImpl; +import io.fd.vpp.jvpp.acl.JVppAclImpl; +import io.fd.vpp.jvpp.acl.future.FutureJVppAclFacade; + +public class FutureApiExample { + + public static void main(String[] args) throws Exception { + testCallbackApi(); + } + + private static void testCallbackApi() throws Exception { + System.out.println("Testing Java callback API for acl plugin"); + try (final JVppRegistry registry = new JVppRegistryImpl("macipAclAddTest"); + final FutureJVppAclFacade jvpp = new FutureJVppAclFacade(registry, new JVppAclImpl())) { + + // adds,dump and verifies Mac-Ip acl + sendMacIpAddRequest(jvpp); + verifyMacIpDump(sendMacIpDumpRequest(jvpp).macipAclDetails.get(0)); + + // adds,dumps and verifies Acl acl + sendAclAddRequest(jvpp); + verifyAclDump(sendAclDumpRequest(jvpp).aclDetails.get(0)); + + // adds,dumps and verifies Interface for acl + sendAclInterfaceSetAclList(jvpp); + verifyAclInterfaceList(sendAclInterfaceListDumpRequest(jvpp).aclInterfaceListDetails.get(0)); + + // deletes all created data + sendAclInterfaceDeleteList(jvpp); + sendAclDelRequest(jvpp); + sendMacIpDelRequest(jvpp); + + System.out.println("Disconnecting..."); + } + } +} diff --git a/src/vpp-api/java/jvpp-acl/io/fd/vpp/jvpp/acl/examples/Readme.txt b/src/vpp-api/java/jvpp-acl/io/fd/vpp/jvpp/acl/examples/Readme.txt new file mode 100644 index 00000000..d17fbfc2 --- /dev/null +++ b/src/vpp-api/java/jvpp-acl/io/fd/vpp/jvpp/acl/examples/Readme.txt @@ -0,0 +1,4 @@ +release version: +sudo java -cp build-vpp-native/vpp/vpp-api/java/jvpp-registry-17.10.jar:build-vpp-native/vpp/vpp-api/java/jvpp-acl-17.10.jar io.fd.vpp.jvpp.acl.examples.FutureApiExample +debug version: +sudo java -cp build-vpp_debug-native/vpp/vpp-api/java/jvpp-registry-17.10.jar:build-vpp-debug-native/vpp/vpp-api/java/jvpp-acl-17.10.jar io.fd.vpp.jvpp.acl.examples.FutureApiExample diff --git a/src/vpp-api/java/jvpp-acl/io/fd/vpp/jvpp/acl/test/CallbackApiTest.java b/src/vpp-api/java/jvpp-acl/io/fd/vpp/jvpp/acl/test/CallbackApiTest.java new file mode 100644 index 00000000..a7bbb7f4 --- /dev/null +++ b/src/vpp-api/java/jvpp-acl/io/fd/vpp/jvpp/acl/test/CallbackApiTest.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.acl.test; + +import io.fd.vpp.jvpp.AbstractCallbackApiTest; +import io.fd.vpp.jvpp.acl.JVppAclImpl; + +import java.util.logging.Logger; + +public class CallbackApiTest extends AbstractCallbackApiTest { + + private static Logger LOG = Logger.getLogger(CallbackApiTest.class.getName()); + + + public static void main(String[] args) throws Exception { + LOG.info("Testing ControlPing using Java callback API for core plugin"); + testControlPing(args[0], new JVppAclImpl()); + } +} diff --git a/src/vpp-api/java/jvpp-acl/io/fd/vpp/jvpp/acl/test/FutureApiTest.java b/src/vpp-api/java/jvpp-acl/io/fd/vpp/jvpp/acl/test/FutureApiTest.java new file mode 100644 index 00000000..ff1c73c4 --- /dev/null +++ b/src/vpp-api/java/jvpp-acl/io/fd/vpp/jvpp/acl/test/FutureApiTest.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.acl.test; + +import io.fd.vpp.jvpp.Assertions; +import io.fd.vpp.jvpp.JVppRegistry; +import io.fd.vpp.jvpp.JVppRegistryImpl; +import io.fd.vpp.jvpp.acl.JVppAclImpl; +import io.fd.vpp.jvpp.acl.dto.AclDetailsReplyDump; +import io.fd.vpp.jvpp.acl.dto.AclDump; +import io.fd.vpp.jvpp.acl.future.FutureJVppAclFacade; + +import java.util.concurrent.CompletableFuture; +import java.util.logging.Logger; + +public class FutureApiTest { + + private static final Logger LOG = Logger.getLogger(FutureApiTest.class.getName()); + + public static void main(String[] args) throws Exception { + testFutureApi(args); + } + + private static void testFutureApi(String[] args) throws Exception { + LOG.info("Testing Java future API for core plugin"); + try (final JVppRegistry registry = new JVppRegistryImpl("FutureApiTest", args[0]); + final FutureJVppAclFacade jvppFacade = new FutureJVppAclFacade(registry, new JVppAclImpl())) { + LOG.info("Successfully connected to VPP"); + + testAclDump(jvppFacade); + + LOG.info("Disconnecting..."); + } + } + + private static void testAclDump(final FutureJVppAclFacade jvpp) throws Exception { + LOG.info("Sending AclDump request..."); + final AclDump request = new AclDump(); + + final CompletableFuture<AclDetailsReplyDump> + replyFuture = jvpp.aclDump(request).toCompletableFuture(); + final AclDetailsReplyDump reply = replyFuture.get(); + + Assertions.assertNotNull(reply); + } + + +} diff --git a/src/vpp-api/java/jvpp-acl/io/fd/vpp/jvpp/acl/test/Readme.txt b/src/vpp-api/java/jvpp-acl/io/fd/vpp/jvpp/acl/test/Readme.txt new file mode 100644 index 00000000..1b465851 --- /dev/null +++ b/src/vpp-api/java/jvpp-acl/io/fd/vpp/jvpp/acl/test/Readme.txt @@ -0,0 +1,4 @@ +release version: +sudo java -cp build-vpp-native/vpp/vpp-api/java/jvpp-registry-17.10.jar:build-vpp-native/vpp/vpp-api/java/jvpp-acl-17.10.jar io.fd.vpp.jvpp.acl.test.[test-name] +debug version: +sudo java -cp build-vpp_debug-native/vpp/vpp-api/java/jvpp-registry-17.10.jar:build-vpp_debug-native/vpp/vpp-api/java/jvpp-acl-17.10.jar io.fd.vpp.jvpp.acl.test.[test-name] diff --git a/src/vpp-api/java/jvpp-acl/jvpp_acl.c b/src/vpp-api/java/jvpp-acl/jvpp_acl.c new file mode 100644 index 00000000..f5467e99 --- /dev/null +++ b/src/vpp-api/java/jvpp-acl/jvpp_acl.c @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2016 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 <vnet/vnet.h> + +#include <acl/acl_msg_enum.h> +#define vl_typedefs /* define message structures */ +#include <acl/acl_all_api_h.h> +#undef vl_typedefs + +#include <vnet/api_errno.h> +#include <vlibapi/api.h> +#include <vlibmemory/api.h> + +#if VPPJNI_DEBUG == 1 + #define DEBUG_LOG(...) clib_warning(__VA_ARGS__) +#else + #define DEBUG_LOG(...) +#endif + +#include <jvpp-common/jvpp_common.h> + +#include "jvpp-acl/io_fd_vpp_jvpp_acl_JVppAclImpl.h" +#include "jvpp_acl.h" +#include "jvpp-acl/jvpp_acl_gen.h" + +/* + * Class: io_fd_vpp_jvpp_acl_JVppaclImpl + * Method: init0 + * Signature: (JI)V + */ +JNIEXPORT void JNICALL Java_io_fd_vpp_jvpp_acl_JVppAclImpl_init0 + (JNIEnv *env, jclass clazz, jobject callback, jlong queue_address, jint my_client_index) { + acl_main_t * plugin_main = &acl_main; + clib_warning ("Java_io_fd_vpp_jvpp_acl_JVppAclImpl_init0"); + + plugin_main->my_client_index = my_client_index; + plugin_main->vl_input_queue = uword_to_pointer (queue_address, unix_shared_memory_queue_t *); + + plugin_main->callbackObject = (*env)->NewGlobalRef(env, callback); + plugin_main->callbackClass = (jclass)(*env)->NewGlobalRef(env, (*env)->GetObjectClass(env, callback)); + + // verify API has not changed since jar generation + #define _(N) \ + get_message_id(env, #N); \ + foreach_supported_api_message; + #undef _ + + #define _(N,n) \ + vl_msg_api_set_handlers(get_message_id(env, #N), #n, \ + vl_api_##n##_t_handler, \ + vl_noop_handler, \ + vl_noop_handler, \ + vl_noop_handler, \ + sizeof(vl_api_##n##_t), 1); + foreach_api_reply_handler; + #undef _ +} + +JNIEXPORT void JNICALL Java_io_fd_vpp_jvpp_acl_JVppAclImpl_close0 +(JNIEnv *env, jclass clazz) { + acl_main_t * plugin_main = &acl_main; + + // cleanup: + (*env)->DeleteGlobalRef(env, plugin_main->callbackClass); + (*env)->DeleteGlobalRef(env, plugin_main->callbackObject); + + plugin_main->callbackClass = NULL; + plugin_main->callbackObject = NULL; +} + +/* Attach thread to JVM and cache class references when initiating JVPP ACL */ +jint JNI_OnLoad(JavaVM *vm, void *reserved) { + JNIEnv* env; + + if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_8) != JNI_OK) { + return JNI_EVERSION; + } + + if (cache_class_references(env) != 0) { + clib_warning ("Failed to cache class references\n"); + return JNI_ERR; + } + + return JNI_VERSION_1_8; +} + +/* Clean up cached references when disposing JVPP ACL */ +void JNI_OnUnload(JavaVM *vm, void *reserved) { + JNIEnv* env; + if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_8) != JNI_OK) { + return; + } + delete_class_references(env); +} diff --git a/src/vpp-api/java/jvpp-acl/jvpp_acl.h b/src/vpp-api/java/jvpp-acl/jvpp_acl.h new file mode 100644 index 00000000..726f7298 --- /dev/null +++ b/src/vpp-api/java/jvpp-acl/jvpp_acl.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2016 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_jvpp_acl_h__ +#define __included_jvpp_acl_h__ + +#include <vnet/vnet.h> +#include <vnet/ip/ip.h> +#include <vnet/api_errno.h> +#include <vlibapi/api.h> +#include <vlibmemory/api.h> +#include <jni.h> + +/* Global state for JVPP-acl */ +typedef struct { + /* Pointer to shared memory queue */ + unix_shared_memory_queue_t * vl_input_queue; + + /* VPP api client index */ + u32 my_client_index; + + /* Callback object and class references enabling asynchronous Java calls */ + jobject callbackObject; + jclass callbackClass; + +} acl_main_t; + +acl_main_t acl_main __attribute__((aligned (64))); + + +#endif /* __included_jvpp_acl_h__ */ diff --git a/src/vpp-api/java/jvpp-common/jvpp_common.c b/src/vpp-api/java/jvpp-common/jvpp_common.c new file mode 100644 index 00000000..c00298bf --- /dev/null +++ b/src/vpp-api/java/jvpp-common/jvpp_common.c @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2016 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. + */ +#define _GNU_SOURCE /* for strcasestr(3) */ + +#include <vnet/api_errno.h> +#include "jvpp_common.h" + +#ifndef JVPP_DEBUG +#define JVPP_DEBUG 0 +#endif + +#if JVPP_DEBUG == 1 +#define DEBUG_LOG(...) clib_warning(__VA_ARGS__) +#else +#define DEBUG_LOG(...) +#endif + +#define _(error,errorCode,msg) \ +if (errorCode == code) \ + message = msg; \ +else + +#define get_error_message(errno) \ +int code = errno; \ +foreach_vnet_api_error \ + message = "Reason unknown"; + +/* shared jvpp main structure */ +jvpp_main_t jvpp_main __attribute__((aligned (64))); + +void call_on_error(const char* callName, int contextId, int retval, + jclass callbackClass, jobject callbackObject, + jclass callbackExceptionClass) { + DEBUG_LOG("\nCallOnError : callback=%s, retval=%d, context=%d\n", callName, + clib_net_to_host_u32(retval), clib_net_to_host_u32(context)); + JNIEnv *env = jvpp_main.jenv; + if (!callbackClass) { + DEBUG_LOG("CallOnError : jm->callbackClass is null!\n"); + return; + } + jmethodID excConstructor = (*env)->GetMethodID(env, callbackExceptionClass, + "<init>", "(Ljava/lang/String;Ljava/lang/String;II)V"); + if (!excConstructor) { + DEBUG_LOG("CallOnError : excConstructor is null!\n"); + return; + } + jmethodID callbackExcMethod = (*env)->GetMethodID(env, callbackClass, + "onError", "(Lio/fd/vpp/jvpp/VppCallbackException;)V"); + if (!callbackExcMethod) { + DEBUG_LOG("CallOnError : callbackExcMethod is null!\n"); + return; + } + + char *message; + get_error_message(clib_net_to_host_u32(retval)); + jobject excObject = (*env)->NewObject(env, callbackExceptionClass, + excConstructor, (*env)->NewStringUTF(env, callName), + (*env)->NewStringUTF(env, message), + clib_net_to_host_u32(contextId), clib_net_to_host_u32(retval)); + if (!excObject) { + DEBUG_LOG("CallOnError : excObject is null!\n"); + return; + } + + (*env)->CallVoidMethod(env, callbackObject, callbackExcMethod, excObject); + DEBUG_LOG("CallOnError : Response sent\n"); +} +#undef _ + +u32 get_message_id(JNIEnv *env, const char *key) { + uword *p = hash_get(jvpp_main.messages_hash, key); + if (!p) { + jclass exClass = (*env)->FindClass(env, "java/lang/IllegalStateException"); + char *msgBuf = clib_mem_alloc(strlen(key) + 40); + strcpy(msgBuf, "API mismatch detected: "); + strcat(msgBuf, key); + strcat(msgBuf, " is missing"); + DEBUG_LOG("get_message_id : %s\n", msgBuf); + (*env)->ThrowNew(env, exClass, msgBuf); + clib_mem_free(msgBuf); + return 0; + } + return (u32) p[0]; +} diff --git a/src/vpp-api/java/jvpp-common/jvpp_common.h b/src/vpp-api/java/jvpp-common/jvpp_common.h new file mode 100644 index 00000000..34502d04 --- /dev/null +++ b/src/vpp-api/java/jvpp-common/jvpp_common.h @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2016 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_jvpp_common_h__ +#define __included_jvpp_common_h__ +// +#include <vppinfra/types.h> +#include <vlibapi/api.h> +#include <vlibmemory/api.h> +#include <jni.h> + +typedef struct { + /* Unique identifier used for matching replays with requests */ + volatile u32 context_id; + + /* Spinlock */ + volatile u32 lock; + u32 tag; + + /* JNI Native Method Interface pointer for message handlers */ + JNIEnv *jenv; + + /* JNI Invoke Interface pointer for attachment of rx thread to java thread */ + JavaVM *jvm; + + /* Convenience */ + unix_shared_memory_queue_t * vl_input_queue; + u32 my_client_index; + uword *messages_hash; +} jvpp_main_t; + +extern jvpp_main_t jvpp_main __attribute__((aligned (64))); + +static_always_inline u32 vppjni_get_context_id(jvpp_main_t * jm) { + return __sync_add_and_fetch(&jm->context_id, 1); +} + +static_always_inline void vppjni_lock(jvpp_main_t * jm, u32 tag) { + while (__sync_lock_test_and_set(&jm->lock, 1)) + ; + jm->tag = tag; +} + +static_always_inline void vppjni_unlock(jvpp_main_t * jm) { + jm->tag = 0; + CLIB_MEMORY_BARRIER(); + jm->lock = 0; +} + +/** + * Calls onError callback on callbackObject reference. Passes instance of callbackExceptionClass as parameter. + */ +void call_on_error(const char* callName, int contextId, int retval, + jclass callbackClass, jobject callbackObject, + jclass callbackExceptionClass); + +/** + * Retrieves message id based on message name and crc (key format: name_crc). + * Throws java/lang/IllegalStateException on failure. + */ +u32 get_message_id(JNIEnv *env, const char* key); + +#endif /* __included_jvpp_common_h__ */ diff --git a/src/vpp-api/java/jvpp-core/io/fd/vpp/jvpp/core/examples/CallbackApiExample.java b/src/vpp-api/java/jvpp-core/io/fd/vpp/jvpp/core/examples/CallbackApiExample.java new file mode 100644 index 00000000..554a21bd --- /dev/null +++ b/src/vpp-api/java/jvpp-core/io/fd/vpp/jvpp/core/examples/CallbackApiExample.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.core.examples; + +import io.fd.vpp.jvpp.JVpp; +import io.fd.vpp.jvpp.JVppRegistry; +import io.fd.vpp.jvpp.JVppRegistryImpl; +import io.fd.vpp.jvpp.VppCallbackException; +import io.fd.vpp.jvpp.core.JVppCoreImpl; +import io.fd.vpp.jvpp.core.callback.GetNodeIndexCallback; +import io.fd.vpp.jvpp.core.callback.ShowVersionCallback; +import io.fd.vpp.jvpp.core.callback.SwInterfaceCallback; +import io.fd.vpp.jvpp.core.dto.GetNodeIndex; +import io.fd.vpp.jvpp.core.dto.GetNodeIndexReply; +import io.fd.vpp.jvpp.core.dto.ShowVersion; +import io.fd.vpp.jvpp.core.dto.ShowVersionReply; +import io.fd.vpp.jvpp.core.dto.SwInterfaceDetails; +import io.fd.vpp.jvpp.core.dto.SwInterfaceDump; +import java.nio.charset.StandardCharsets; + +public class CallbackApiExample { + + public static void main(String[] args) throws Exception { + testCallbackApi(); + } + + private static void testCallbackApi() throws Exception { + System.out.println("Testing Java callback API with JVppRegistry"); + try (final JVppRegistry registry = new JVppRegistryImpl("CallbackApiExample"); + final JVpp jvpp = new JVppCoreImpl()) { + registry.register(jvpp, new TestCallback()); + + System.out.println("Sending ShowVersion request..."); + final int result = jvpp.send(new ShowVersion()); + System.out.printf("ShowVersion send result = %d%n", result); + + System.out.println("Sending GetNodeIndex request..."); + GetNodeIndex getNodeIndexRequest = new GetNodeIndex(); + getNodeIndexRequest.nodeName = "non-existing-node".getBytes(StandardCharsets.UTF_8); + jvpp.send(getNodeIndexRequest); + + System.out.println("Sending SwInterfaceDump request..."); + SwInterfaceDump swInterfaceDumpRequest = new SwInterfaceDump(); + swInterfaceDumpRequest.nameFilterValid = 0; + swInterfaceDumpRequest.nameFilter = "".getBytes(StandardCharsets.UTF_8); + jvpp.send(swInterfaceDumpRequest); + + Thread.sleep(1000); + System.out.println("Disconnecting..."); + } + Thread.sleep(1000); + } + + static class TestCallback implements GetNodeIndexCallback, ShowVersionCallback, SwInterfaceCallback { + + @Override + public void onGetNodeIndexReply(final GetNodeIndexReply msg) { + System.out.printf("Received GetNodeIndexReply: %s%n", msg); + } + + @Override + public void onShowVersionReply(final ShowVersionReply msg) { + System.out.printf("Received ShowVersionReply: context=%d, program=%s, version=%s, " + + "buildDate=%s, buildDirectory=%s%n", + msg.context, + new String(msg.program, StandardCharsets.UTF_8), + new String(msg.version, StandardCharsets.UTF_8), + new String(msg.buildDate, StandardCharsets.UTF_8), + new String(msg.buildDirectory, StandardCharsets.UTF_8)); + } + + @Override + public void onSwInterfaceDetails(final SwInterfaceDetails msg) { + System.out.printf("Received SwInterfaceDetails: interfaceName=%s, l2AddressLength=%d, adminUpDown=%d, " + + "linkUpDown=%d, linkSpeed=%d, linkMtu=%d%n", + new String(msg.interfaceName, StandardCharsets.UTF_8), msg.l2AddressLength, msg.adminUpDown, + msg.linkUpDown, msg.linkSpeed, (int) msg.linkMtu); + } + + @Override + public void onError(VppCallbackException ex) { + System.out.printf("Received onError exception: call=%s, context=%d, retval=%d%n", ex.getMethodName(), + ex.getCtxId(), ex.getErrorCode()); + } + } +} diff --git a/src/vpp-api/java/jvpp-core/io/fd/vpp/jvpp/core/examples/CallbackJVppFacadeExample.java b/src/vpp-api/java/jvpp-core/io/fd/vpp/jvpp/core/examples/CallbackJVppFacadeExample.java new file mode 100644 index 00000000..2f77f0f1 --- /dev/null +++ b/src/vpp-api/java/jvpp-core/io/fd/vpp/jvpp/core/examples/CallbackJVppFacadeExample.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.core.examples; + +import io.fd.vpp.jvpp.JVppRegistry; +import io.fd.vpp.jvpp.JVppRegistryImpl; +import io.fd.vpp.jvpp.VppCallbackException; +import io.fd.vpp.jvpp.core.JVppCoreImpl; +import io.fd.vpp.jvpp.core.callback.GetNodeIndexCallback; +import io.fd.vpp.jvpp.core.callback.ShowVersionCallback; +import io.fd.vpp.jvpp.core.callfacade.CallbackJVppCoreFacade; +import io.fd.vpp.jvpp.core.dto.GetNodeIndex; +import io.fd.vpp.jvpp.core.dto.GetNodeIndexReply; +import io.fd.vpp.jvpp.core.dto.ShowVersionReply; +import java.nio.charset.StandardCharsets; + +/** + * CallbackJVppFacade together with CallbackJVppFacadeCallback allow for setting different callback for each request. + * This is more convenient than the approach shown in CallbackApiExample. + */ +public class CallbackJVppFacadeExample { + + private static ShowVersionCallback showVersionCallback1 = new ShowVersionCallback() { + @Override + public void onShowVersionReply(final ShowVersionReply msg) { + System.out.printf("ShowVersionCallback1 received ShowVersionReply: context=%d, program=%s," + + "version=%s, buildDate=%s, buildDirectory=%s%n", msg.context, + new String(msg.program, StandardCharsets.UTF_8), + new String(msg.version, StandardCharsets.UTF_8), + new String(msg.buildDate, StandardCharsets.UTF_8), + new String(msg.buildDirectory, StandardCharsets.UTF_8)); + } + + @Override + public void onError(VppCallbackException ex) { + System.out.printf("Received onError exception in showVersionCallback1: call=%s, reply=%d, context=%d%n", + ex.getMethodName(), ex.getErrorCode(), ex.getCtxId()); + } + }; + + private static ShowVersionCallback showVersionCallback2 = new ShowVersionCallback() { + @Override + public void onShowVersionReply(final ShowVersionReply msg) { + System.out.printf("ShowVersionCallback2 received ShowVersionReply: context=%d, program=%s," + + "version=%s, buildDate=%s, buildDirectory=%s%n", msg.context, + new String(msg.program, StandardCharsets.UTF_8), + new String(msg.version, StandardCharsets.UTF_8), + new String(msg.buildDate, StandardCharsets.UTF_8), + new String(msg.buildDirectory, StandardCharsets.UTF_8)); + } + + @Override + public void onError(VppCallbackException ex) { + System.out.printf("Received onError exception in showVersionCallback2: call=%s, reply=%d, context=%d%n", + ex.getMethodName(), ex.getErrorCode(), ex.getCtxId()); + } + + }; + + private static GetNodeIndexCallback getNodeIndexCallback = new GetNodeIndexCallback() { + @Override + public void onGetNodeIndexReply(final GetNodeIndexReply msg) { + System.out.printf("Received GetNodeIndexReply: %s%n", msg); + } + + @Override + public void onError(VppCallbackException ex) { + System.out.printf("Received onError exception in getNodeIndexCallback: call=%s, reply=%d, context=%d%n", + ex.getMethodName(), ex.getErrorCode(), ex.getCtxId()); + } + }; + + private static void testCallbackFacade() throws Exception { + System.out.println("Testing CallbackJVppFacade"); + + try (final JVppRegistry registry = new JVppRegistryImpl("CallbackFacadeExample"); + final CallbackJVppCoreFacade callbackFacade = new CallbackJVppCoreFacade(registry, new JVppCoreImpl())) { + System.out.println("Successfully connected to VPP"); + + callbackFacade.showVersion(showVersionCallback1); + callbackFacade.showVersion(showVersionCallback2); + + GetNodeIndex getNodeIndexRequest = new GetNodeIndex(); + getNodeIndexRequest.nodeName = "dummyNode".getBytes(StandardCharsets.UTF_8); + callbackFacade.getNodeIndex(getNodeIndexRequest, getNodeIndexCallback); + + Thread.sleep(2000); + System.out.println("Disconnecting..."); + } + Thread.sleep(1000); + } + + public static void main(String[] args) throws Exception { + testCallbackFacade(); + } +} diff --git a/src/vpp-api/java/jvpp-core/io/fd/vpp/jvpp/core/examples/CallbackJVppFacadeNotificationExample.java b/src/vpp-api/java/jvpp-core/io/fd/vpp/jvpp/core/examples/CallbackJVppFacadeNotificationExample.java new file mode 100644 index 00000000..308dad9f --- /dev/null +++ b/src/vpp-api/java/jvpp-core/io/fd/vpp/jvpp/core/examples/CallbackJVppFacadeNotificationExample.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.core.examples; + +import io.fd.vpp.jvpp.JVppRegistry; +import io.fd.vpp.jvpp.JVppRegistryImpl; +import io.fd.vpp.jvpp.VppCallbackException; +import io.fd.vpp.jvpp.core.JVppCore; +import io.fd.vpp.jvpp.core.JVppCoreImpl; +import io.fd.vpp.jvpp.core.callback.WantInterfaceEventsCallback; +import io.fd.vpp.jvpp.core.callfacade.CallbackJVppCoreFacade; +import io.fd.vpp.jvpp.core.dto.WantInterfaceEventsReply; + +public class CallbackJVppFacadeNotificationExample { + + private static void testCallbackFacade() throws Exception { + System.out.println("Testing CallbackJVppFacade for notifications"); + + try (final JVppRegistry registry = new JVppRegistryImpl("CallbackFacadeExample"); + final JVppCore jvpp = new JVppCoreImpl()) { + final CallbackJVppCoreFacade jvppCallbackFacade = new CallbackJVppCoreFacade(registry, jvpp); + System.out.println("Successfully connected to VPP"); + + final AutoCloseable notificationListenerReg = + jvppCallbackFacade.getNotificationRegistry().registerSwInterfaceEventNotificationCallback( + NotificationUtils::printNotification + ); + + jvppCallbackFacade.wantInterfaceEvents(NotificationUtils.getEnableInterfaceNotificationsReq(), + new WantInterfaceEventsCallback() { + @Override + public void onWantInterfaceEventsReply(final WantInterfaceEventsReply reply) { + System.out.println("Interface events started"); + } + + @Override + public void onError(final VppCallbackException ex) { + System.out.printf("Received onError exception: call=%s, context=%d, retval=%d%n", + ex.getMethodName(), ex.getCtxId(), ex.getErrorCode()); + } + }); + + System.out.println("Changing interface configuration"); + NotificationUtils.getChangeInterfaceState().send(jvpp); + + Thread.sleep(1000); + + jvppCallbackFacade.wantInterfaceEvents(NotificationUtils.getDisableInterfaceNotificationsReq(), + new WantInterfaceEventsCallback() { + @Override + public void onWantInterfaceEventsReply(final WantInterfaceEventsReply reply) { + System.out.println("Interface events stopped"); + } + + @Override + public void onError(final VppCallbackException ex) { + System.out.printf("Received onError exception: call=%s, context=%d, retval=%d%n", + ex.getMethodName(), ex.getCtxId(), ex.getErrorCode()); + } + }); + + notificationListenerReg.close(); + + Thread.sleep(2000); + System.out.println("Disconnecting..."); + } + Thread.sleep(1000); + } + + public static void main(String[] args) throws Exception { + testCallbackFacade(); + } +} diff --git a/src/vpp-api/java/jvpp-core/io/fd/vpp/jvpp/core/examples/CallbackNotificationApiExample.java b/src/vpp-api/java/jvpp-core/io/fd/vpp/jvpp/core/examples/CallbackNotificationApiExample.java new file mode 100644 index 00000000..7d56b7ea --- /dev/null +++ b/src/vpp-api/java/jvpp-core/io/fd/vpp/jvpp/core/examples/CallbackNotificationApiExample.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.core.examples; + +import static io.fd.vpp.jvpp.core.examples.NotificationUtils.getChangeInterfaceState; +import static io.fd.vpp.jvpp.core.examples.NotificationUtils.getDisableInterfaceNotificationsReq; +import static io.fd.vpp.jvpp.core.examples.NotificationUtils.getEnableInterfaceNotificationsReq; +import static io.fd.vpp.jvpp.core.examples.NotificationUtils.printNotification; + +import io.fd.vpp.jvpp.JVpp; +import io.fd.vpp.jvpp.JVppRegistry; +import io.fd.vpp.jvpp.JVppRegistryImpl; +import io.fd.vpp.jvpp.VppCallbackException; +import io.fd.vpp.jvpp.core.JVppCoreImpl; +import io.fd.vpp.jvpp.core.callback.SwInterfaceEventNotificationCallback; +import io.fd.vpp.jvpp.core.callback.WantInterfaceEventsCallback; +import io.fd.vpp.jvpp.core.dto.SwInterfaceEventNotification; +import io.fd.vpp.jvpp.core.dto.SwInterfaceSetFlagsReply; +import io.fd.vpp.jvpp.core.dto.WantInterfaceEventsReply; + +public class CallbackNotificationApiExample { + + private static void testCallbackApi() throws Exception { + System.out.println("Testing Java callback API for notifications"); + try (final JVppRegistry registry = new JVppRegistryImpl("CallbackNotificationApiExample"); + final JVpp jvpp = new JVppCoreImpl()) { + registry.register(jvpp, new TestCallback()); + System.out.println("Successfully connected to VPP"); + + getEnableInterfaceNotificationsReq().send(jvpp); + System.out.println("Interface notifications started"); + // TODO test ifc dump which also triggers interface flags send + + System.out.println("Changing interface configuration"); + getChangeInterfaceState().send(jvpp); + + // Notifications are received + Thread.sleep(500); + + getDisableInterfaceNotificationsReq().send(jvpp); + System.out.println("Interface events stopped"); + + Thread.sleep(2000); + System.out.println("Disconnecting..."); + } + Thread.sleep(1000); + } + + public static void main(String[] args) throws Exception { + testCallbackApi(); + } + + private static class TestCallback implements SwInterfaceEventNotificationCallback, + WantInterfaceEventsCallback { + + @Override + public void onSwInterfaceEventNotification( + final SwInterfaceEventNotification msg) { + printNotification(msg); + } + + @Override + public void onWantInterfaceEventsReply(final WantInterfaceEventsReply wantInterfaceEventsReply) { + System.out.println("Interface notification stream updated"); + } + + @Override + public void onError(VppCallbackException ex) { + System.out.printf("Received onError exception in getNodeIndexCallback: call=%s, reply=%d, context=%d%n", + ex.getMethodName(), ex.getErrorCode(), ex.getCtxId()); + + } + } +} diff --git a/src/vpp-api/java/jvpp-core/io/fd/vpp/jvpp/core/examples/CreateSubInterfaceExample.java b/src/vpp-api/java/jvpp-core/io/fd/vpp/jvpp/core/examples/CreateSubInterfaceExample.java new file mode 100644 index 00000000..3db6d30a --- /dev/null +++ b/src/vpp-api/java/jvpp-core/io/fd/vpp/jvpp/core/examples/CreateSubInterfaceExample.java @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.core.examples; + +import static java.util.Objects.requireNonNull; + +import io.fd.vpp.jvpp.JVppRegistry; +import io.fd.vpp.jvpp.JVppRegistryImpl; +import io.fd.vpp.jvpp.core.JVppCoreImpl; +import io.fd.vpp.jvpp.core.dto.CreateSubif; +import io.fd.vpp.jvpp.core.dto.CreateSubifReply; +import io.fd.vpp.jvpp.core.dto.SwInterfaceDetailsReplyDump; +import io.fd.vpp.jvpp.core.dto.SwInterfaceDump; +import io.fd.vpp.jvpp.core.future.FutureJVppCoreFacade; +import java.nio.charset.StandardCharsets; + +/** + * <p>Tests sub-interface creation.<br> Equivalent to:<br> + * + * <pre>{@code + * vppctl create sub GigabitEthernet0/9/0 1 dot1q 100 inner-dot1q any + * } + * </pre> + * + * To verify invoke:<br> + * <pre>{@code + * vpp_api_test json + * vat# sw_interface_dump + * } + */ +public class CreateSubInterfaceExample { + + private static SwInterfaceDump createSwInterfaceDumpRequest(final String ifaceName) { + SwInterfaceDump request = new SwInterfaceDump(); + request.nameFilter = ifaceName.getBytes(StandardCharsets.UTF_8); + request.nameFilterValid = 1; + return request; + } + + private static void requireSingleIface(final SwInterfaceDetailsReplyDump response, final String ifaceName) { + if (response.swInterfaceDetails.size() != 1) { + throw new IllegalStateException( + String.format("Expected one interface matching filter %s but was %d", ifaceName, + response.swInterfaceDetails.size())); + } + } + + private static CreateSubif createSubifRequest(final int swIfIndex, final int subId) { + CreateSubif request = new CreateSubif(); + request.swIfIndex = swIfIndex; // super interface id + request.subId = subId; + request.noTags = 0; + request.oneTag = 0; + request.twoTags = 1; + request.dot1Ad = 0; + request.exactMatch = 1; + request.defaultSub = 0; + request.outerVlanIdAny = 0; + request.innerVlanIdAny = 1; + request.outerVlanId = 100; + request.innerVlanId = 0; + return request; + } + + private static void print(CreateSubifReply reply) { + System.out.printf("CreateSubifReply: %s%n", reply); + } + + private static void testCreateSubInterface() throws Exception { + System.out.println("Testing sub-interface creation using Java callback API"); + try (final JVppRegistry registry = new JVppRegistryImpl("CreateSubInterfaceExample"); + final FutureJVppCoreFacade jvppFacade = new FutureJVppCoreFacade(registry, new JVppCoreImpl())) { + System.out.println("Successfully connected to VPP"); + Thread.sleep(1000); + + final String ifaceName = "Gigabitethernet0/8/0"; + + final SwInterfaceDetailsReplyDump swInterfaceDetails = + jvppFacade.swInterfaceDump(createSwInterfaceDumpRequest(ifaceName)).toCompletableFuture().get(); + + requireNonNull(swInterfaceDetails, "swInterfaceDump returned null"); + requireNonNull(swInterfaceDetails.swInterfaceDetails, "swInterfaceDetails is null"); + requireSingleIface(swInterfaceDetails, ifaceName); + + final int swIfIndex = swInterfaceDetails.swInterfaceDetails.get(0).swIfIndex; + final int subId = 1; + + final CreateSubifReply createSubifReply = + jvppFacade.createSubif(createSubifRequest(swIfIndex, subId)).toCompletableFuture().get(); + print(createSubifReply); + + final String subIfaceName = "Gigabitethernet0/8/0." + subId; + final SwInterfaceDetailsReplyDump subIface = + jvppFacade.swInterfaceDump(createSwInterfaceDumpRequest(subIfaceName)).toCompletableFuture().get(); + requireNonNull(swInterfaceDetails, "swInterfaceDump returned null"); + requireNonNull(subIface.swInterfaceDetails, "swInterfaceDump returned null"); + requireSingleIface(swInterfaceDetails, ifaceName); + + System.out.println("Disconnecting..."); + } + Thread.sleep(1000); + } + + public static void main(String[] args) throws Exception { + testCreateSubInterface(); + } +} diff --git a/src/vpp-api/java/jvpp-core/io/fd/vpp/jvpp/core/examples/FutureApiExample.java b/src/vpp-api/java/jvpp-core/io/fd/vpp/jvpp/core/examples/FutureApiExample.java new file mode 100644 index 00000000..931c9b33 --- /dev/null +++ b/src/vpp-api/java/jvpp-core/io/fd/vpp/jvpp/core/examples/FutureApiExample.java @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.core.examples; + +import io.fd.vpp.jvpp.JVppRegistry; +import io.fd.vpp.jvpp.JVppRegistryImpl; +import io.fd.vpp.jvpp.core.JVppCoreImpl; +import io.fd.vpp.jvpp.core.dto.BridgeDomainDetailsReplyDump; +import io.fd.vpp.jvpp.core.dto.BridgeDomainDump; +import io.fd.vpp.jvpp.core.dto.GetNodeIndex; +import io.fd.vpp.jvpp.core.dto.GetNodeIndexReply; +import io.fd.vpp.jvpp.core.dto.ShowVersion; +import io.fd.vpp.jvpp.core.dto.ShowVersionReply; +import io.fd.vpp.jvpp.core.dto.SwInterfaceDetails; +import io.fd.vpp.jvpp.core.dto.SwInterfaceDetailsReplyDump; +import io.fd.vpp.jvpp.core.dto.SwInterfaceDump; +import io.fd.vpp.jvpp.core.future.FutureJVppCoreFacade; +import java.nio.charset.StandardCharsets; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Future; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class FutureApiExample { + + private static final Logger LOG = Logger.getLogger(FutureApiExample.class.getName()); + + private static void testShowVersion(final FutureJVppCoreFacade jvpp) throws Exception { + LOG.info("Sending ShowVersion request..."); + final Future<ShowVersionReply> replyFuture = jvpp.showVersion(new ShowVersion()).toCompletableFuture(); + final ShowVersionReply reply = replyFuture.get(); + LOG.info( + String.format( + "Received ShowVersionReply: context=%d, program=%s, version=%s, buildDate=%s, buildDirectory=%s%n", + reply.context, new String(reply.program, StandardCharsets.UTF_8), + new String(reply.version, StandardCharsets.UTF_8), + new String(reply.buildDate, StandardCharsets.UTF_8), + new String(reply.buildDirectory, StandardCharsets.UTF_8))); + } + + private static void testEmptyBridgeDomainDump(final FutureJVppCoreFacade jvpp) throws Exception { + LOG.info("Sending ShowVersion request..."); + final BridgeDomainDump request = new BridgeDomainDump(); + request.bdId = -1; // dump call + + final CompletableFuture<BridgeDomainDetailsReplyDump> + replyFuture = jvpp.bridgeDomainDump(request).toCompletableFuture(); + final BridgeDomainDetailsReplyDump reply = replyFuture.get(); + + if (reply == null || reply.bridgeDomainDetails == null) { + LOG.severe("Received null response for empty dump: " + reply); + } else { + LOG.info( + String.format( + "Received bridge-domain dump reply with list of bridge-domains: %s", + reply.bridgeDomainDetails)); + } + } + + private static void testGetNodeIndex(final FutureJVppCoreFacade jvpp) { + LOG.info("Sending GetNodeIndex request..."); + final GetNodeIndex request = new GetNodeIndex(); + request.nodeName = "non-existing-node".getBytes(StandardCharsets.UTF_8); + final Future<GetNodeIndexReply> replyFuture = jvpp.getNodeIndex(request).toCompletableFuture(); + try { + final GetNodeIndexReply reply = replyFuture.get(); + LOG.info( + String.format( + "Received GetNodeIndexReply: context=%d, nodeIndex=%d%n", reply.context, reply.nodeIndex)); + } catch (Exception e) { + LOG.log(Level.SEVERE, "GetNodeIndex request failed", e); + } + } + + private static void testSwInterfaceDump(final FutureJVppCoreFacade jvpp) throws Exception { + LOG.info("Sending SwInterfaceDump request..."); + final SwInterfaceDump request = new SwInterfaceDump(); + request.nameFilterValid = 0; + request.nameFilter = "".getBytes(StandardCharsets.UTF_8); + + final Future<SwInterfaceDetailsReplyDump> replyFuture = jvpp.swInterfaceDump(request).toCompletableFuture(); + final SwInterfaceDetailsReplyDump reply = replyFuture.get(); + for (SwInterfaceDetails details : reply.swInterfaceDetails) { + Objects.requireNonNull(details, "reply.swInterfaceDetails contains null element!"); + LOG.info( + String.format("Received SwInterfaceDetails: interfaceName=%s, l2AddressLength=%d, adminUpDown=%d, " + + "linkUpDown=%d, linkSpeed=%d, linkMtu=%d%n", + new String(details.interfaceName, StandardCharsets.UTF_8), + details.l2AddressLength, details.adminUpDown, + details.linkUpDown, details.linkSpeed, (int) details.linkMtu)); + } + } + + private static void testFutureApi() throws Exception { + LOG.info("Testing Java future API"); + try (final JVppRegistry registry = new JVppRegistryImpl("FutureApiExample"); + final FutureJVppCoreFacade jvppFacade = new FutureJVppCoreFacade(registry, new JVppCoreImpl())) { + LOG.info("Successfully connected to VPP"); + + testEmptyBridgeDomainDump(jvppFacade); + testShowVersion(jvppFacade); + testGetNodeIndex(jvppFacade); + testSwInterfaceDump(jvppFacade); + + LOG.info("Disconnecting..."); + } + } + + public static void main(String[] args) throws Exception { + testFutureApi(); + } +} diff --git a/src/vpp-api/java/jvpp-core/io/fd/vpp/jvpp/core/examples/FutureApiNotificationExample.java b/src/vpp-api/java/jvpp-core/io/fd/vpp/jvpp/core/examples/FutureApiNotificationExample.java new file mode 100644 index 00000000..7460401e --- /dev/null +++ b/src/vpp-api/java/jvpp-core/io/fd/vpp/jvpp/core/examples/FutureApiNotificationExample.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.core.examples; + +import static io.fd.vpp.jvpp.core.examples.NotificationUtils.getChangeInterfaceState; +import static io.fd.vpp.jvpp.core.examples.NotificationUtils.getDisableInterfaceNotificationsReq; +import static io.fd.vpp.jvpp.core.examples.NotificationUtils.getEnableInterfaceNotificationsReq; + +import io.fd.vpp.jvpp.JVppRegistry; +import io.fd.vpp.jvpp.JVppRegistryImpl; +import io.fd.vpp.jvpp.core.JVppCoreImpl; +import io.fd.vpp.jvpp.core.future.FutureJVppCoreFacade; + +public class FutureApiNotificationExample { + + private static void testFutureApi() throws Exception { + System.out.println("Testing Java future API for notifications"); + try (final JVppRegistry registry = new JVppRegistryImpl("FutureApiNotificationExample"); + final FutureJVppCoreFacade jvppFacade = new FutureJVppCoreFacade(registry, new JVppCoreImpl()); + final AutoCloseable notificationListenerReg = + jvppFacade.getNotificationRegistry() + .registerSwInterfaceEventNotificationCallback(NotificationUtils::printNotification)) { + System.out.println("Successfully connected to VPP"); + jvppFacade.wantInterfaceEvents(getEnableInterfaceNotificationsReq()).toCompletableFuture().get(); + System.out.println("Interface events started"); + + System.out.println("Changing interface configuration"); + jvppFacade.swInterfaceSetFlags(getChangeInterfaceState()).toCompletableFuture().get(); + + Thread.sleep(1000); + + jvppFacade.wantInterfaceEvents(getDisableInterfaceNotificationsReq()).toCompletableFuture().get(); + System.out.println("Interface events stopped"); + System.out.println("Disconnecting..."); + } + } + + public static void main(String[] args) throws Exception { + testFutureApi(); + } +} diff --git a/src/vpp-api/java/jvpp-core/io/fd/vpp/jvpp/core/examples/L2AclExample.java b/src/vpp-api/java/jvpp-core/io/fd/vpp/jvpp/core/examples/L2AclExample.java new file mode 100644 index 00000000..f89043a3 --- /dev/null +++ b/src/vpp-api/java/jvpp-core/io/fd/vpp/jvpp/core/examples/L2AclExample.java @@ -0,0 +1,195 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.core.examples; + +import io.fd.vpp.jvpp.JVppRegistry; +import io.fd.vpp.jvpp.JVppRegistryImpl; +import io.fd.vpp.jvpp.core.JVppCoreImpl; +import io.fd.vpp.jvpp.core.dto.ClassifyAddDelSession; +import io.fd.vpp.jvpp.core.dto.ClassifyAddDelSessionReply; +import io.fd.vpp.jvpp.core.dto.ClassifyAddDelTable; +import io.fd.vpp.jvpp.core.dto.ClassifyAddDelTableReply; +import io.fd.vpp.jvpp.core.dto.ClassifySessionDetailsReplyDump; +import io.fd.vpp.jvpp.core.dto.ClassifySessionDump; +import io.fd.vpp.jvpp.core.dto.ClassifyTableByInterface; +import io.fd.vpp.jvpp.core.dto.ClassifyTableByInterfaceReply; +import io.fd.vpp.jvpp.core.dto.ClassifyTableIds; +import io.fd.vpp.jvpp.core.dto.ClassifyTableIdsReply; +import io.fd.vpp.jvpp.core.dto.ClassifyTableInfo; +import io.fd.vpp.jvpp.core.dto.ClassifyTableInfoReply; +import io.fd.vpp.jvpp.core.dto.InputAclSetInterface; +import io.fd.vpp.jvpp.core.dto.InputAclSetInterfaceReply; +import io.fd.vpp.jvpp.core.future.FutureJVppCoreFacade; +import javax.xml.bind.DatatypeConverter; + +/** + * <p>Tests L2 ACL creation and read.<br> Equivalent to the following vppctl commands:<br> + * + * <pre>{@code + * vppctl classify table mask l2 src + * vppctl classify session acl-hit-next deny opaque-index 0 table-index 0 match l2 src 01:02:03:04:05:06 + * vppctl set int input acl intfc local0 l2-table 0 + * vppctl sh class table verbose + * } + * </pre> + */ +public class L2AclExample { + + private static final int LOCAL0_IFACE_ID = 0; + + private static ClassifyAddDelTable createClassifyTable() { + ClassifyAddDelTable request = new ClassifyAddDelTable(); + request.isAdd = 1; + request.tableIndex = ~0; // default + request.nbuckets = 2; + request.memorySize = 2 << 20; + request.nextTableIndex = ~0; // default + request.missNextIndex = ~0; // default + request.skipNVectors = 0; + request.matchNVectors = 1; + request.mask = + new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, 0x00, 0x00, 0x00, 0x00}; + return request; + } + + private static ClassifyTableInfo createClassifyTableInfoRequest(final int tableId) { + ClassifyTableInfo request = new ClassifyTableInfo(); + request.tableId = tableId; + return request; + } + + private static ClassifyAddDelSession createClassifySession(final int tableIndex) { + ClassifyAddDelSession request = new ClassifyAddDelSession(); + request.isAdd = 1; + request.tableIndex = tableIndex; + request.hitNextIndex = 0; // deny + request.opaqueIndex = 0; + request.advance = 0; // default + // match 01:02:03:04:05:06 mac address + request.match = + new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, (byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x04, + (byte) 0x05, (byte) 0x06, 0x00, 0x00, 0x00, 0x00}; + return request; + } + + private static ClassifySessionDump createClassifySessionDumpRequest(final int newTableIndex) { + ClassifySessionDump request = new ClassifySessionDump(); + request.tableId = newTableIndex; + return request; + } + + private static InputAclSetInterface aclSetInterface() { + InputAclSetInterface request = new InputAclSetInterface(); + request.isAdd = 1; + request.swIfIndex = LOCAL0_IFACE_ID; + request.ip4TableIndex = ~0; // skip + request.ip6TableIndex = ~0; // skip + request.l2TableIndex = 0; + return request; + } + + private static ClassifyTableByInterface createClassifyTableByInterfaceRequest() { + ClassifyTableByInterface request = new ClassifyTableByInterface(); + request.swIfIndex = LOCAL0_IFACE_ID; + return request; + } + + private static void print(ClassifyAddDelTableReply reply) { + System.out.printf("ClassifyAddDelTableReply: %s%n", reply); + } + + private static void print(ClassifyTableIdsReply reply) { + System.out.printf("ClassifyTableIdsReply: %s%n", reply); + } + + private static void print(final ClassifyTableInfoReply reply) { + System.out.println(reply); + if (reply != null) { + System.out.println("Mask hex: " + DatatypeConverter.printHexBinary(reply.mask)); + } + } + + private static void print(ClassifyAddDelSessionReply reply) { + System.out.printf("ClassifyAddDelSessionReply: context=%s%n", reply); + } + + private static void print(final ClassifySessionDetailsReplyDump reply) { + System.out.println(reply); + reply.classifySessionDetails.forEach(detail -> { + System.out.println(detail); + System.out.println("Match hex: " + DatatypeConverter.printHexBinary(detail.match)); + }); + } + + private static void print(final InputAclSetInterfaceReply reply) { + System.out.printf("InputAclSetInterfaceReply: context=%s%n", reply); + } + + private static void print(final ClassifyTableByInterfaceReply reply) { + System.out.printf("ClassifyAddDelTableReply: %s%n", reply); + } + + private static void testL2Acl() throws Exception { + System.out.println("Testing L2 ACLs using Java callback API"); + try (final JVppRegistry registry = new JVppRegistryImpl("L2AclExample"); + final FutureJVppCoreFacade jvppFacade = new FutureJVppCoreFacade(registry, new JVppCoreImpl())) { + + System.out.println("Successfully connected to VPP"); + Thread.sleep(1000); + + final ClassifyAddDelTableReply classifyAddDelTableReply = + jvppFacade.classifyAddDelTable(createClassifyTable()).toCompletableFuture().get(); + print(classifyAddDelTableReply); + + final ClassifyTableIdsReply classifyTableIdsReply = + jvppFacade.classifyTableIds(new ClassifyTableIds()).toCompletableFuture().get(); + print(classifyTableIdsReply); + + final ClassifyTableInfoReply classifyTableInfoReply = + jvppFacade.classifyTableInfo(createClassifyTableInfoRequest(classifyAddDelTableReply.newTableIndex)) + .toCompletableFuture().get(); + print(classifyTableInfoReply); + + final ClassifyAddDelSessionReply classifyAddDelSessionReply = + jvppFacade.classifyAddDelSession(createClassifySession(classifyAddDelTableReply.newTableIndex)) + .toCompletableFuture().get(); + print(classifyAddDelSessionReply); + + final ClassifySessionDetailsReplyDump classifySessionDetailsReplyDump = + jvppFacade.classifySessionDump(createClassifySessionDumpRequest(classifyAddDelTableReply.newTableIndex)) + .toCompletableFuture().get(); + print(classifySessionDetailsReplyDump); + + final InputAclSetInterfaceReply inputAclSetInterfaceReply = + jvppFacade.inputAclSetInterface(aclSetInterface()).toCompletableFuture().get(); + print(inputAclSetInterfaceReply); + + final ClassifyTableByInterfaceReply classifyTableByInterfaceReply = + jvppFacade.classifyTableByInterface(createClassifyTableByInterfaceRequest()).toCompletableFuture() + .get(); + print(classifyTableByInterfaceReply); + + System.out.println("Disconnecting..."); + } + Thread.sleep(1000); + } + + public static void main(String[] args) throws Exception { + testL2Acl(); + } +} diff --git a/src/vpp-api/java/jvpp-core/io/fd/vpp/jvpp/core/examples/LispAdjacencyExample.java b/src/vpp-api/java/jvpp-core/io/fd/vpp/jvpp/core/examples/LispAdjacencyExample.java new file mode 100644 index 00000000..f637669d --- /dev/null +++ b/src/vpp-api/java/jvpp-core/io/fd/vpp/jvpp/core/examples/LispAdjacencyExample.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.core.examples; + +import io.fd.vpp.jvpp.JVppRegistry; +import io.fd.vpp.jvpp.JVppRegistryImpl; +import io.fd.vpp.jvpp.core.JVppCoreImpl; +import io.fd.vpp.jvpp.core.dto.LispAddDelAdjacency; +import io.fd.vpp.jvpp.core.dto.LispAddDelLocalEid; +import io.fd.vpp.jvpp.core.dto.LispAddDelLocatorSet; +import io.fd.vpp.jvpp.core.dto.LispAddDelRemoteMapping; +import io.fd.vpp.jvpp.core.dto.LispAdjacenciesGet; +import io.fd.vpp.jvpp.core.dto.LispAdjacenciesGetReply; +import io.fd.vpp.jvpp.core.dto.LispEnableDisable; +import io.fd.vpp.jvpp.core.future.FutureJVppCoreFacade; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.ExecutionException; +import java.util.logging.Logger; + +/** + * Tests lisp adjacency creation and read (custom vpe.api type support showcase). + */ +public class LispAdjacencyExample { + + private static final Logger LOG = Logger.getLogger(LispAdjacencyExample.class.getName()); + + private static void enableLisp(final FutureJVppCoreFacade jvpp) throws ExecutionException, InterruptedException { + final LispEnableDisable request = new LispEnableDisable(); + request.isEn = 1; + jvpp.lispEnableDisable(request).toCompletableFuture().get(); + LOG.info("Lisp enabled successfully"); + } + + private static void addLocatorSet(final FutureJVppCoreFacade jvpp) throws ExecutionException, InterruptedException { + final LispAddDelLocatorSet request = new LispAddDelLocatorSet(); + request.isAdd = 1; + request.locatorSetName = "ls1".getBytes(StandardCharsets.UTF_8); + jvpp.lispAddDelLocatorSet(request).toCompletableFuture().get(); + LOG.info("Locator set created successfully:" + request.toString()); + } + + private static void addLocalEid(final FutureJVppCoreFacade jvpp) throws ExecutionException, InterruptedException { + final LispAddDelLocalEid request = new LispAddDelLocalEid(); + request.isAdd = 1; + request.locatorSetName = "ls1".getBytes(StandardCharsets.UTF_8); + request.eid = new byte[] {1, 2, 1, 10}; + request.eidType = 0; // ip4 + request.vni = 0; + request.prefixLen = 32; + jvpp.lispAddDelLocalEid(request).toCompletableFuture().get(); + LOG.info("Local EID created successfully:" + request.toString()); + } + + private static void addRemoteMapping(final FutureJVppCoreFacade jvpp) + throws ExecutionException, InterruptedException { + final LispAddDelRemoteMapping request = new LispAddDelRemoteMapping(); + request.isAdd = 1; + request.vni = 0; + request.eid = new byte[] {1, 2, 1, 20}; + request.eidLen = 32; + request.rlocNum = 1; + // FIXME!!!! + //request.rlocs = new byte[] {1, 1, 1, 1, 2, 1, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + jvpp.lispAddDelRemoteMapping(request).toCompletableFuture().get(); + LOG.info("Remote mapping created successfully:" + request.toString()); + } + + private static void addAdjacency(final FutureJVppCoreFacade jvpp) throws ExecutionException, InterruptedException { + final LispAddDelAdjacency request = new LispAddDelAdjacency(); + request.isAdd = 1; + request.leid = new byte[] {1, 2, 1, 10}; + request.leidLen = 32; + request.reid = new byte[] {1, 2, 1, 20}; + request.reidLen = 32; + request.eidType = 0; // ip4 + request.vni = 0; + jvpp.lispAddDelAdjacency(request).toCompletableFuture().get(); + LOG.info("Lisp adjacency created successfully:" + request.toString()); + } + + private static void showAdjacencies(final FutureJVppCoreFacade jvpp) + throws ExecutionException, InterruptedException { + final LispAdjacenciesGetReply reply = + jvpp.lispAdjacenciesGet(new LispAdjacenciesGet()).toCompletableFuture().get(); + LOG.info("Lisp adjacency received successfully:" + reply.toString()); + } + + private static void testAdjacency(final FutureJVppCoreFacade jvpp) throws Exception { + enableLisp(jvpp); + addLocatorSet(jvpp); + addLocalEid(jvpp); + addRemoteMapping(jvpp); + addAdjacency(jvpp); + showAdjacencies(jvpp); + } + + private static void testFutureApi() throws Exception { + LOG.info("Create lisp adjacency test"); + try (final JVppRegistry registry = new JVppRegistryImpl("LispAdjacencyExample"); + final FutureJVppCoreFacade jvppFacade = new FutureJVppCoreFacade(registry, new JVppCoreImpl())) { + LOG.info("Successfully connected to VPP"); + + testAdjacency(jvppFacade); + LOG.info("Disconnecting..."); + } + } + + public static void main(String[] args) throws Exception { + testFutureApi(); + } +} diff --git a/src/vpp-api/java/jvpp-core/io/fd/vpp/jvpp/core/examples/NotificationUtils.java b/src/vpp-api/java/jvpp-core/io/fd/vpp/jvpp/core/examples/NotificationUtils.java new file mode 100644 index 00000000..d3f9dd2c --- /dev/null +++ b/src/vpp-api/java/jvpp-core/io/fd/vpp/jvpp/core/examples/NotificationUtils.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.core.examples; + +import java.io.PrintStream; +import io.fd.vpp.jvpp.core.dto.SwInterfaceSetFlags; +import io.fd.vpp.jvpp.core.dto.SwInterfaceEventNotification; +import io.fd.vpp.jvpp.core.dto.WantInterfaceEvents; + +final class NotificationUtils { + + private NotificationUtils() {} + + static PrintStream printNotification(final SwInterfaceEventNotification msg) { + return System.out.printf("Received interface notification: ifc: %s%n", msg); + } + + static SwInterfaceSetFlags getChangeInterfaceState() { + final SwInterfaceSetFlags swInterfaceSetFlags = new SwInterfaceSetFlags(); + swInterfaceSetFlags.swIfIndex = 0; + swInterfaceSetFlags.adminUpDown = 1; + return swInterfaceSetFlags; + } + + static WantInterfaceEvents getEnableInterfaceNotificationsReq() { + WantInterfaceEvents wantInterfaceEvents = new WantInterfaceEvents(); + wantInterfaceEvents.pid = 1; + wantInterfaceEvents.enableDisable = 1; + return wantInterfaceEvents; + } + + static WantInterfaceEvents getDisableInterfaceNotificationsReq() { + WantInterfaceEvents wantInterfaceEvents = new WantInterfaceEvents(); + wantInterfaceEvents.pid = 1; + wantInterfaceEvents.enableDisable = 0; + return wantInterfaceEvents; + } +} diff --git a/src/vpp-api/java/jvpp-core/io/fd/vpp/jvpp/core/examples/Readme.txt b/src/vpp-api/java/jvpp-core/io/fd/vpp/jvpp/core/examples/Readme.txt new file mode 100644 index 00000000..10c603f5 --- /dev/null +++ b/src/vpp-api/java/jvpp-core/io/fd/vpp/jvpp/core/examples/Readme.txt @@ -0,0 +1,17 @@ +This package contains basic examples for jvpp. To run the examples: + +- Make sure VPP is running +- From VPP's build-root/ folder execute: + - release version: sudo java -cp build-vpp-native/vpp/vpp-api/java/jvpp-registry-17.10.jar:build-vpp-native/vpp/vpp-api/java/jvpp-core-17.10.jar io.fd.vpp.jvpp.core.examples.[test name] + - debug version: sudo java -cp build-vpp_debug-native/vpp/vpp-api/java/jvpp-registry-17.10.jar:build-vpp_debug-native/vpp/vpp-api/java/jvpp-core-17.10.jar io.fd.vpp.jvpp.core.examples.[test name] + +Available examples: +CallbackApiExample - Similar to ControlPingTest, invokes more complex calls (e.g. interface dump) using low level JVpp APIs +CallbackJVppFacadeNotificationExample - Example of interface notifications using Callback based JVpp facade +CallbackJVppFacadeExample - Execution of more complex calls using Callback based JVpp facade +CallbackNotificationApiExample - Example of interface notifications using low level JVpp APIs +CreateSubInterfaceExample - Example of sub-interface creation +FutureApiNotificationExample - Example of interface notifications using Future based JVpp facade +FutureApiExample - Execution of more complex calls using Future based JVpp facade +L2AclExample - Example of L2 ACL creation +LispAdjacencyExample - Example of lisp adjacency creation and read (custom vpe.api type support showcase) diff --git a/src/vpp-api/java/jvpp-core/io/fd/vpp/jvpp/core/test/CallbackApiTest.java b/src/vpp-api/java/jvpp-core/io/fd/vpp/jvpp/core/test/CallbackApiTest.java new file mode 100644 index 00000000..493116c8 --- /dev/null +++ b/src/vpp-api/java/jvpp-core/io/fd/vpp/jvpp/core/test/CallbackApiTest.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.core.test; + +import io.fd.vpp.jvpp.AbstractCallbackApiTest; +import io.fd.vpp.jvpp.core.JVppCoreImpl; + +import java.util.logging.Logger; + +public class CallbackApiTest extends AbstractCallbackApiTest { + + private static Logger LOG = Logger.getLogger(CallbackApiTest.class.getName()); + + + public static void main(String[] args) throws Exception { + LOG.info("Testing ControlPing using Java callback API for core plugin"); + testControlPing(args[0], new JVppCoreImpl()); + } +} diff --git a/src/vpp-api/java/jvpp-core/io/fd/vpp/jvpp/core/test/FutureApiTest.java b/src/vpp-api/java/jvpp-core/io/fd/vpp/jvpp/core/test/FutureApiTest.java new file mode 100644 index 00000000..d3acecc2 --- /dev/null +++ b/src/vpp-api/java/jvpp-core/io/fd/vpp/jvpp/core/test/FutureApiTest.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.core.test; + +import io.fd.vpp.jvpp.JVppRegistry; +import io.fd.vpp.jvpp.JVppRegistryImpl; +import io.fd.vpp.jvpp.core.JVppCoreImpl; +import io.fd.vpp.jvpp.core.dto.BridgeDomainDetailsReplyDump; +import io.fd.vpp.jvpp.core.dto.BridgeDomainDump; +import io.fd.vpp.jvpp.core.dto.GetNodeIndex; +import io.fd.vpp.jvpp.core.dto.GetNodeIndexReply; +import io.fd.vpp.jvpp.core.dto.ShowVersion; +import io.fd.vpp.jvpp.core.dto.ShowVersionReply; +import io.fd.vpp.jvpp.core.dto.SwInterfaceDetails; +import io.fd.vpp.jvpp.core.dto.SwInterfaceDetailsReplyDump; +import io.fd.vpp.jvpp.core.dto.SwInterfaceDump; +import io.fd.vpp.jvpp.core.future.FutureJVppCoreFacade; +import java.nio.charset.StandardCharsets; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Future; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class FutureApiTest { + + private static final Logger LOG = Logger.getLogger(FutureApiTest.class.getName()); + + public static void main(String[] args) throws Exception { + testFutureApi(args); + } + + private static void testFutureApi(String[] args) throws Exception { + LOG.info("Testing Java future API for core plugin"); + try (final JVppRegistry registry = new JVppRegistryImpl("FutureApiTest", args[0]); + final FutureJVppCoreFacade jvppFacade = new FutureJVppCoreFacade(registry, new JVppCoreImpl())) { + LOG.info("Successfully connected to VPP"); + + testEmptyBridgeDomainDump(jvppFacade); + + LOG.info("Disconnecting..."); + } + } + + private static void testEmptyBridgeDomainDump(final FutureJVppCoreFacade jvpp) throws Exception { + LOG.info("Sending BridgeDomainDump request..."); + final BridgeDomainDump request = new BridgeDomainDump(); + request.bdId = -1; // dump call + + final CompletableFuture<BridgeDomainDetailsReplyDump> + replyFuture = jvpp.bridgeDomainDump(request).toCompletableFuture(); + final BridgeDomainDetailsReplyDump reply = replyFuture.get(); + + if (reply == null || reply.bridgeDomainDetails == null) { + throw new IllegalStateException("Received null response for empty dump: " + reply); + } else { + LOG.info( + String.format( + "Received bridge-domain dump reply with list of bridge-domains: %s", + reply.bridgeDomainDetails)); + } + } + + +} diff --git a/src/vpp-api/java/jvpp-core/io/fd/vpp/jvpp/core/test/Readme.txt b/src/vpp-api/java/jvpp-core/io/fd/vpp/jvpp/core/test/Readme.txt new file mode 100644 index 00000000..b74cf60a --- /dev/null +++ b/src/vpp-api/java/jvpp-core/io/fd/vpp/jvpp/core/test/Readme.txt @@ -0,0 +1,18 @@ +This package contains basic tests for jvpp. To run the tests: + +- Make sure VPP is running +- From VPP's build-root/ folder execute: + - release version: sudo java -cp build-vpp-native/vpp/vpp-api/java/jvpp-registry-17.10.jar:build-vpp-native/vpp/vpp-api/java/jvpp-core-17.10.jar io.fd.vpp.jvpp.core.test.[test name] + - debug version: sudo java -cp build-vpp_debug-native/vpp/vpp-api/java/jvpp-registry-17.10.jar:build-vpp_debug-native/vpp/vpp-api/java/jvpp-core-17.10.jar io.fd.vpp.jvpp.core.test.[test name] + +Available tests: +CallbackApiTest - Similar to ControlPingTest, invokes more complex calls (e.g. interface dump) using low level JVpp APIs +CallbackJVppFacadeNotificationTest - Tests interface notifications using Callback based JVpp facade +CallbackJVppFacadeTest - Execution of more complex calls using Callback based JVpp facade +CallbackNotificationApiTest - Tests interface notifications using low level JVpp APIs +ControlPingTest - Simple test executing a single control ping using low level JVpp APIs +CreateSubInterfaceTest - Tests sub-interface creation +FutureApiNotificationTest - Tests interface notifications using Future based JVpp facade +FutureApiTest - Execution of more complex calls using Future based JVpp facade +L2AclTest - Tests L2 ACL creation +LispAdjacencyTest - Tests lisp adjacency creation and read (custom vpe.api type support showcase) diff --git a/src/vpp-api/java/jvpp-core/jvpp_core.c b/src/vpp-api/java/jvpp-core/jvpp_core.c new file mode 100644 index 00000000..e57c62a3 --- /dev/null +++ b/src/vpp-api/java/jvpp-core/jvpp_core.c @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2016 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 <vnet/vnet.h> + +#include <vpp/api/vpe_msg_enum.h> +#define vl_typedefs /* define message structures */ +#include <vpp/api/vpe_all_api_h.h> +#undef vl_typedefs + +#include <vnet/api_errno.h> +#include <vlibapi/api.h> +#include <vlibmemory/api.h> +#include <jni.h> + +#include <jvpp-common/jvpp_common.h> + +// TODO: generate jvpp_plugin_name.c files (or at least reuse plugin's main structure) +typedef struct { + /* Pointer to shared memory queue */ + unix_shared_memory_queue_t * vl_input_queue; + + /* VPP api client index */ + u32 my_client_index; + + /* Callback object and class references enabling asynchronous Java calls */ + jobject callbackObject; + jclass callbackClass; + +} core_main_t; + +core_main_t core_main __attribute__((aligned (64))); + +#include "io_fd_vpp_jvpp_core_JVppCoreImpl.h" +#include "jvpp_core_gen.h" + +JNIEXPORT void JNICALL Java_io_fd_vpp_jvpp_core_JVppCoreImpl_init0 +(JNIEnv * env, jclass clazz, jobject callback, jlong queue_address, jint my_client_index) { + core_main_t * plugin_main = &core_main; + plugin_main->my_client_index = my_client_index; + plugin_main->vl_input_queue = uword_to_pointer (queue_address, unix_shared_memory_queue_t *); + + plugin_main->callbackObject = (*env)->NewGlobalRef(env, callback); + plugin_main->callbackClass = (jclass)(*env)->NewGlobalRef(env, (*env)->GetObjectClass(env, callback)); + + // verify API has not changed since jar generation + #define _(N) \ + get_message_id(env, #N); \ + foreach_supported_api_message; + #undef _ + + #define _(N,n) \ + vl_msg_api_set_handlers(get_message_id(env, #N), #n, \ + vl_api_##n##_t_handler, \ + vl_noop_handler, \ + vl_noop_handler, \ + vl_noop_handler, \ + sizeof(vl_api_##n##_t), 1); + foreach_api_reply_handler; + #undef _ +} + +JNIEXPORT void JNICALL Java_io_fd_vpp_jvpp_core_JVppCoreImpl_close0 +(JNIEnv *env, jclass clazz) { + core_main_t * plugin_main = &core_main; + + // cleanup: + (*env)->DeleteGlobalRef(env, plugin_main->callbackClass); + (*env)->DeleteGlobalRef(env, plugin_main->callbackObject); + + plugin_main->callbackClass = NULL; + plugin_main->callbackObject = NULL; +} + +jint JNI_OnLoad(JavaVM *vm, void *reserved) { + JNIEnv* env; + + if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_8) != JNI_OK) { + return JNI_EVERSION; + } + + if (cache_class_references(env) != 0) { + clib_warning ("Failed to cache class references\n"); + return JNI_ERR; + } + + return JNI_VERSION_1_8; +} + +void JNI_OnUnload(JavaVM *vm, void *reserved) { + JNIEnv* env; + if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_8) != JNI_OK) { + return; + } + delete_class_references(env); +} + + + diff --git a/src/vpp-api/java/jvpp-gtpu/jvpp_gtpu.c b/src/vpp-api/java/jvpp-gtpu/jvpp_gtpu.c new file mode 100644 index 00000000..12b3090e --- /dev/null +++ b/src/vpp-api/java/jvpp-gtpu/jvpp_gtpu.c @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2016 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 <vnet/vnet.h> + +#include <gtpu/gtpu_msg_enum.h> +#define vl_typedefs /* define message structures */ +#include <gtpu/gtpu_all_api_h.h> +#undef vl_typedefs + +#include <vnet/api_errno.h> +#include <vlibapi/api.h> +#include <vlibmemory/api.h> + +#if VPPJNI_DEBUG == 1 + #define DEBUG_LOG(...) clib_warning(__VA_ARGS__) +#else + #define DEBUG_LOG(...) +#endif + +#include <jvpp-common/jvpp_common.h> + +#include "jvpp-gtpu/io_fd_vpp_jvpp_gtpu_JVppGtpuImpl.h" +#include "jvpp_gtpu.h" +#include "jvpp-gtpu/jvpp_gtpu_gen.h" + +/* + * Class: io_fd_vpp_jvpp_gtpu_JVppgtpuImpl + * Method: init0 + * Signature: (JI)V + */ +JNIEXPORT void JNICALL Java_io_fd_vpp_jvpp_gtpu_JVppGtpuImpl_init0 + (JNIEnv *env, jclass clazz, jobject callback, jlong queue_address, jint my_client_index) { + gtpu_main_t * plugin_main = >pu_main; + clib_warning ("Java_io_fd_vpp_jvpp_gtpu_JVppGtpuImpl_init0"); + + plugin_main->my_client_index = my_client_index; + plugin_main->vl_input_queue = (unix_shared_memory_queue_t *)queue_address; + + plugin_main->callbackObject = (*env)->NewGlobalRef(env, callback); + plugin_main->callbackClass = (jclass)(*env)->NewGlobalRef(env, (*env)->GetObjectClass(env, callback)); + + // verify API has not changed since jar generation + #define _(N) \ + get_message_id(env, #N); \ + foreach_supported_api_message; + #undef _ + + #define _(N,n) \ + vl_msg_api_set_handlers(get_message_id(env, #N), #n, \ + vl_api_##n##_t_handler, \ + vl_noop_handler, \ + vl_noop_handler, \ + vl_noop_handler, \ + sizeof(vl_api_##n##_t), 1); + foreach_api_reply_handler; + #undef _ +} + +JNIEXPORT void JNICALL Java_io_fd_vpp_jvpp_gtpu_JVppGtpuImpl_close0 +(JNIEnv *env, jclass clazz) { + gtpu_main_t * plugin_main = >pu_main; + + // cleanup: + (*env)->DeleteGlobalRef(env, plugin_main->callbackClass); + (*env)->DeleteGlobalRef(env, plugin_main->callbackObject); + + plugin_main->callbackClass = NULL; + plugin_main->callbackObject = NULL; +} + +/* Attach thread to JVM and cache class references when initiating JVPP ACL */ +jint JNI_OnLoad(JavaVM *vm, void *reserved) { + JNIEnv* env; + + if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_8) != JNI_OK) { + return JNI_EVERSION; + } + + if (cache_class_references(env) != 0) { + clib_warning ("Failed to cache class references\n"); + return JNI_ERR; + } + + return JNI_VERSION_1_8; +} + +/* Clean up cached references when disposing JVPP ACL */ +void JNI_OnUnload(JavaVM *vm, void *reserved) { + JNIEnv* env; + if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_8) != JNI_OK) { + return; + } + delete_class_references(env); +} diff --git a/src/vpp-api/java/jvpp-gtpu/jvpp_gtpu.h b/src/vpp-api/java/jvpp-gtpu/jvpp_gtpu.h new file mode 100644 index 00000000..447776ce --- /dev/null +++ b/src/vpp-api/java/jvpp-gtpu/jvpp_gtpu.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2016 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_jvpp_gtpu_h__ +#define __included_jvpp_gtpu_h__ + +#include <vnet/vnet.h> +#include <vnet/ip/ip.h> +#include <vnet/api_errno.h> +#include <vlibapi/api.h> +#include <vlibmemory/api.h> +#include <jni.h> + +/* Global state for JVPP-gtpu */ +typedef struct { + /* Pointer to shared memory queue */ + unix_shared_memory_queue_t * vl_input_queue; + + /* VPP api client index */ + u32 my_client_index; + + /* Callback object and class references enabling asynchronous Java calls */ + jobject callbackObject; + jclass callbackClass; + +} gtpu_main_t; + +gtpu_main_t gtpu_main __attribute__((aligned (64))); + + +#endif /* __included_jvpp_gtpu_h__ */ diff --git a/src/vpp-api/java/jvpp-ioamexport/io/fd/vpp/jvpp/ioamexport/examples/IoamExportApiExample.java b/src/vpp-api/java/jvpp-ioamexport/io/fd/vpp/jvpp/ioamexport/examples/IoamExportApiExample.java new file mode 100644 index 00000000..2f5b7dbb --- /dev/null +++ b/src/vpp-api/java/jvpp-ioamexport/io/fd/vpp/jvpp/ioamexport/examples/IoamExportApiExample.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.ioamexport.examples; + +import java.net.InetAddress; + +import io.fd.vpp.jvpp.JVpp; +import io.fd.vpp.jvpp.JVppRegistry; +import io.fd.vpp.jvpp.JVppRegistryImpl; +import io.fd.vpp.jvpp.VppCallbackException; +import io.fd.vpp.jvpp.ioamexport.JVppIoamexportImpl; +import io.fd.vpp.jvpp.ioamexport.future.FutureJVppIoamexportFacade; +import io.fd.vpp.jvpp.ioamexport.dto.IoamExportIp6EnableDisable; +import io.fd.vpp.jvpp.ioamexport.dto.IoamExportIp6EnableDisableReply; + +public class IoamExportApiExample { + + public static void main(String[] args) throws Exception { + ioamExportTestApi(); + } + + private static void ioamExportTestApi() throws Exception { + System.out.println("Testing Java API for ioam export plugin"); + try (final JVppRegistry registry = new JVppRegistryImpl("ioamExportApiExample"); + final JVpp jvpp = new JVppIoamexportImpl()) { + FutureJVppIoamexportFacade ioamexportJvpp = new FutureJVppIoamexportFacade(registry,jvpp); + System.out.println("Sending ioam export request..."); + IoamExportIp6EnableDisable request = new IoamExportIp6EnableDisable(); + request.isDisable = 0; + InetAddress collectorAddress = InetAddress.getByName("2001:0DB8:AC10:FE01:0000:0000:0000:0000"); + InetAddress srcAddress = InetAddress.getByName("2001:0DB8:AC10:FE01:0000:0000:0000:0001"); + request.collectorAddress = collectorAddress.getAddress(); + request.srcAddress = srcAddress.getAddress(); + IoamExportIp6EnableDisableReply reply = ioamexportJvpp.ioamExportIp6EnableDisable(request).toCompletableFuture().get(); + System.out.printf("IoamExportIp6EnableDisableReply = "+reply.toString()+"%n"); + + Thread.sleep(1000); + + System.out.println("Disconnecting..."); + } + } +} diff --git a/src/vpp-api/java/jvpp-ioamexport/io/fd/vpp/jvpp/ioamexport/examples/Readme.txt b/src/vpp-api/java/jvpp-ioamexport/io/fd/vpp/jvpp/ioamexport/examples/Readme.txt new file mode 100644 index 00000000..f2dfe917 --- /dev/null +++ b/src/vpp-api/java/jvpp-ioamexport/io/fd/vpp/jvpp/ioamexport/examples/Readme.txt @@ -0,0 +1,4 @@ +release version: +sudo java -cp build-vpp-native/vpp/vpp-api/java/jvpp-registry-17.10.jar:build-vpp-native/vpp/vpp-api/java/jvpp-ioamexport-17.10.jar io.fd.vpp.jvpp.ioamexport.examples.IoamExportApiExample +debug vresion: +sudo java -cp build-vpp_debug-native/vpp/vpp-api/java/jvpp-registry-17.10.jar:build-vpp_debug-native/vpp/vpp-api/java/jvpp-ioamexport-17.10.jar io.fd.vpp.jvpp.ioamexport.examples.IoamExportApiExample diff --git a/src/vpp-api/java/jvpp-ioamexport/io/fd/vpp/jvpp/ioamexport/test/CallbackApiTest.java b/src/vpp-api/java/jvpp-ioamexport/io/fd/vpp/jvpp/ioamexport/test/CallbackApiTest.java new file mode 100644 index 00000000..ba49d77d --- /dev/null +++ b/src/vpp-api/java/jvpp-ioamexport/io/fd/vpp/jvpp/ioamexport/test/CallbackApiTest.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.ioamexport.test; + +import io.fd.vpp.jvpp.AbstractCallbackApiTest; +import io.fd.vpp.jvpp.ioamexport.JVppIoamexportImpl; + +import java.util.logging.Logger; + + +public class CallbackApiTest extends AbstractCallbackApiTest { + + private static Logger LOG = Logger.getLogger(CallbackApiTest.class.getName()); + + public static void main(String[] args) throws Exception { + LOG.info("Testing ControlPing using Java callback API for ioamexport plugin"); + testControlPing(args[0], new JVppIoamexportImpl()); + } +} diff --git a/src/vpp-api/java/jvpp-ioamexport/io/fd/vpp/jvpp/ioamexport/test/FutureApiTest.java b/src/vpp-api/java/jvpp-ioamexport/io/fd/vpp/jvpp/ioamexport/test/FutureApiTest.java new file mode 100644 index 00000000..048d2445 --- /dev/null +++ b/src/vpp-api/java/jvpp-ioamexport/io/fd/vpp/jvpp/ioamexport/test/FutureApiTest.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.ioamexport.test; + + +import io.fd.vpp.jvpp.Assertions; +import io.fd.vpp.jvpp.JVppRegistry; +import io.fd.vpp.jvpp.JVppRegistryImpl; +import io.fd.vpp.jvpp.ioamexport.JVppIoamexportImpl; +import io.fd.vpp.jvpp.ioamexport.dto.IoamExportIp6EnableDisable; +import io.fd.vpp.jvpp.ioamexport.dto.IoamExportIp6EnableDisableReply; +import io.fd.vpp.jvpp.ioamexport.future.FutureJVppIoamexportFacade; + +import java.util.concurrent.Future; +import java.util.logging.Logger; + +public class FutureApiTest { + + private static final Logger LOG = Logger.getLogger(FutureApiTest.class.getName()); + + public static void main(String[] args) throws Exception { + testCallbackApi(args); + } + + private static void testCallbackApi(String[] args) throws Exception { + LOG.info("Testing Java callback API for ioamexport plugin"); + try (final JVppRegistry registry = new JVppRegistryImpl("FutureApiTest", args[0]); + final FutureJVppIoamexportFacade jvpp = new FutureJVppIoamexportFacade(registry, new JVppIoamexportImpl())) { + LOG.info("Successfully connected to VPP"); + + testIoamExportIp6EnableDisable(jvpp); + + LOG.info("Disconnecting..."); + } + } + + private static void testIoamExportIp6EnableDisable(FutureJVppIoamexportFacade jvpp) throws Exception { + LOG.info("Sending IoamExportIp6EnableDisable request..."); + final IoamExportIp6EnableDisable request = new IoamExportIp6EnableDisable(); + + final Future<IoamExportIp6EnableDisableReply> replyFuture = jvpp.ioamExportIp6EnableDisable(request).toCompletableFuture(); + final IoamExportIp6EnableDisableReply reply = replyFuture.get(); + + Assertions.assertNotNull(reply); + } +} diff --git a/src/vpp-api/java/jvpp-ioamexport/io/fd/vpp/jvpp/ioamexport/test/Readme.txt b/src/vpp-api/java/jvpp-ioamexport/io/fd/vpp/jvpp/ioamexport/test/Readme.txt new file mode 100644 index 00000000..820071a8 --- /dev/null +++ b/src/vpp-api/java/jvpp-ioamexport/io/fd/vpp/jvpp/ioamexport/test/Readme.txt @@ -0,0 +1,4 @@ +release version: +sudo java -cp build-vpp-native/vpp/vpp-api/java/jvpp-registry-17.10.jar:build-vpp_debug-native/vpp/vpp-api/java/jvpp-ioamexport-17.10.jar io.fd.vpp.jvpp.ioamexport.test.[test-name] +debug version: +sudo java -cp build-vpp_debug-native/vpp/vpp-api/java/jvpp-registry-17.10.jar:build-vpp_debug-native/vpp/vpp-api/java/jvpp-ioamexport-17.10.jar io.fd.vpp.jvpp.ioamexport.test.[test-name] diff --git a/src/vpp-api/java/jvpp-ioamexport/jvpp_ioam_export.c b/src/vpp-api/java/jvpp-ioamexport/jvpp_ioam_export.c new file mode 100644 index 00000000..cf4499d5 --- /dev/null +++ b/src/vpp-api/java/jvpp-ioamexport/jvpp_ioam_export.c @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2016 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 <vnet/vnet.h> + +#include <ioam/export/ioam_export_msg_enum.h> +#define vl_typedefs /* define message structures */ +#include <ioam/export/ioam_export_all_api_h.h> +#undef vl_typedefs + +#include <vnet/api_errno.h> +#include <vlibapi/api.h> +#include <vlibmemory/api.h> + +#if VPPJNI_DEBUG == 1 + #define DEBUG_LOG(...) clib_warning(__VA_ARGS__) +#else + #define DEBUG_LOG(...) +#endif + +#include <jvpp-common/jvpp_common.h> + +#include "jvpp-ioamexport/io_fd_vpp_jvpp_ioamexport_JVppIoamexportImpl.h" +#include "jvpp_ioam_export.h" +#include "jvpp-ioamexport/jvpp_ioamexport_gen.h" + +/* + * Class: io_fd_vpp_jvpp_ioamexport_JVppIoamexportImpl + * Method: init0 + * Signature: (JI)V + */ +JNIEXPORT void JNICALL Java_io_fd_vpp_jvpp_ioamexport_JVppIoamexportImpl_init0 + (JNIEnv *env, jclass clazz, jobject callback, jlong queue_address, jint my_client_index) { + ioamexport_main_t * plugin_main = &ioamexport_main; + clib_warning ("Java_io_fd_vpp_jvpp_ioamexport_JVppIoamexportImpl_init0"); + + plugin_main->my_client_index = my_client_index; + plugin_main->vl_input_queue = uword_to_pointer (queue_address, unix_shared_memory_queue_t *); + + plugin_main->callbackObject = (*env)->NewGlobalRef(env, callback); + plugin_main->callbackClass = (jclass)(*env)->NewGlobalRef(env, (*env)->GetObjectClass(env, callback)); + + // verify API has not changed since jar generation + #define _(N) \ + get_message_id(env, #N); + foreach_supported_api_message; + #undef _ + + #define _(N,n) \ + vl_msg_api_set_handlers(get_message_id(env, #N), #n, \ + vl_api_##n##_t_handler, \ + vl_noop_handler, \ + vl_noop_handler, \ + vl_noop_handler, \ + sizeof(vl_api_##n##_t), 1); + foreach_api_reply_handler; + #undef _ +} + +JNIEXPORT void JNICALL Java_io_fd_vpp_jvpp_ioamexport_JVppIoamexportImpl_close0 +(JNIEnv *env, jclass clazz) { + ioamexport_main_t * plugin_main = &ioamexport_main; + + // cleanup: + (*env)->DeleteGlobalRef(env, plugin_main->callbackClass); + (*env)->DeleteGlobalRef(env, plugin_main->callbackObject); + + plugin_main->callbackClass = NULL; + plugin_main->callbackObject = NULL; +} + +/* Attach thread to JVM and cache class references when initiating JVPP iOAM EXPORT */ +jint JNI_OnLoad(JavaVM *vm, void *reserved) { + JNIEnv* env; + + if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_8) != JNI_OK) { + return JNI_EVERSION; + } + + if (cache_class_references(env) != 0) { + clib_warning ("Failed to cache class references\n"); + return JNI_ERR; + } + + return JNI_VERSION_1_8; +} + +/* Clean up cached references when disposing JVPP iOAM EXPORT */ +void JNI_OnUnload(JavaVM *vm, void *reserved) { + JNIEnv* env; + if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_8) != JNI_OK) { + return; + } + delete_class_references(env); +} diff --git a/src/vpp-api/java/jvpp-ioamexport/jvpp_ioam_export.h b/src/vpp-api/java/jvpp-ioamexport/jvpp_ioam_export.h new file mode 100644 index 00000000..8b243def --- /dev/null +++ b/src/vpp-api/java/jvpp-ioamexport/jvpp_ioam_export.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2016 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_jvpp_ioam_export_h__ +#define __included_jvpp_ioam_export_h__ + +#include <vnet/vnet.h> +#include <vnet/ip/ip.h> +#include <vnet/api_errno.h> +#include <vlibapi/api.h> +#include <vlibmemory/api.h> +#include <jni.h> + +/* Global state for JVPP-IOAM-EXPORT */ +typedef struct { + /* Pointer to shared memory queue */ + unix_shared_memory_queue_t * vl_input_queue; + + /* VPP api client index */ + u32 my_client_index; + + /* Callback object and class references enabling asynchronous Java calls */ + jobject callbackObject; + jclass callbackClass; + +} ioamexport_main_t; + +ioamexport_main_t ioamexport_main __attribute__((aligned (64))); + + +#endif /* __included_jvpp_ioam_export_h__ */ diff --git a/src/vpp-api/java/jvpp-ioampot/io/fd/vpp/jvpp/ioampot/examples/IoamPotApiExample.java b/src/vpp-api/java/jvpp-ioampot/io/fd/vpp/jvpp/ioampot/examples/IoamPotApiExample.java new file mode 100644 index 00000000..e97d24fb --- /dev/null +++ b/src/vpp-api/java/jvpp-ioampot/io/fd/vpp/jvpp/ioampot/examples/IoamPotApiExample.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.ioampot.examples; + +import io.fd.vpp.jvpp.JVpp; +import io.fd.vpp.jvpp.JVppRegistry; +import io.fd.vpp.jvpp.JVppRegistryImpl; +import io.fd.vpp.jvpp.VppCallbackException; +import io.fd.vpp.jvpp.ioampot.JVppIoampotImpl; +import io.fd.vpp.jvpp.ioampot.callback.PotProfileAddCallback; +import io.fd.vpp.jvpp.ioampot.dto.PotProfileAdd; +import io.fd.vpp.jvpp.ioampot.dto.PotProfileAddReply; +import java.nio.charset.StandardCharsets; + +public class IoamPotApiExample { + + static class IoamPotTestCallback implements PotProfileAddCallback { + + @Override + public void onPotProfileAddReply(final PotProfileAddReply reply) { + System.out.printf("Received PotProfileAddReply reply: context=%d%n", + reply.context); + } + + @Override + public void onError(VppCallbackException ex) { + System.out.printf("Received onError exception: call=%s, context=%d, retval=%d%n", ex.getMethodName(), + ex.getCtxId(), ex.getErrorCode()); + } + } + + public static void main(String[] args) throws Exception { + ioamPotTestApi(); + } + + private static void ioamPotTestApi() throws Exception { + System.out.println("Testing Java API for ioam pot plugin"); + try (final JVppRegistry registry = new JVppRegistryImpl("ioamPotApiExample"); + final JVpp jvpp = new JVppIoampotImpl()) { + registry.register(jvpp, new IoamPotTestCallback()); + + System.out.println("Sending ioam pot profile add request..."); + PotProfileAdd request = new PotProfileAdd(); + request.id = 0; + request.validator = 4; + request.secretKey = 1; + request.secretShare = 2; + request.prime = 1234; + request.maxBits = 53; + request.lpc = 1234; + request.polynomialPublic = 1234; + request.listNameLen = (byte)"test pot profile".getBytes(StandardCharsets.UTF_8).length; + request.listName = "test pot profile".getBytes(StandardCharsets.UTF_8); + final int result = jvpp.send(request); + System.out.printf("PotProfileAdd send result = %d%n", result); + + Thread.sleep(1000); + + System.out.println("Disconnecting..."); + } + } +} diff --git a/src/vpp-api/java/jvpp-ioampot/io/fd/vpp/jvpp/ioampot/examples/Readme.txt b/src/vpp-api/java/jvpp-ioampot/io/fd/vpp/jvpp/ioampot/examples/Readme.txt new file mode 100644 index 00000000..e91550bf --- /dev/null +++ b/src/vpp-api/java/jvpp-ioampot/io/fd/vpp/jvpp/ioampot/examples/Readme.txt @@ -0,0 +1,4 @@ +release version: +sudo java -cp build-vpp-native/vpp/vpp-api/java/jvpp-registry-17.10.jar:build-vpp-native/vpp/vpp-api/java/jvpp-ioampot-17.10.jar io.fd.vpp.jvpp.ioampot.examples.IoamPotApiExample +debug vresion: +sudo java -cp build-vpp_debug-native/vpp/vpp-api/java/jvpp-registry-17.10.jar:build-vpp_debug-native/vpp/vpp-api/java/jvpp-ioampot-17.10.jar io.fd.vpp.jvpp.ioampot.examples.IoamPotApiExample diff --git a/src/vpp-api/java/jvpp-ioampot/io/fd/vpp/jvpp/ioampot/test/CallbackApiTest.java b/src/vpp-api/java/jvpp-ioampot/io/fd/vpp/jvpp/ioampot/test/CallbackApiTest.java new file mode 100644 index 00000000..20b85d89 --- /dev/null +++ b/src/vpp-api/java/jvpp-ioampot/io/fd/vpp/jvpp/ioampot/test/CallbackApiTest.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.ioampot.test; + +import io.fd.vpp.jvpp.AbstractCallbackApiTest; +import io.fd.vpp.jvpp.ioampot.JVppIoampotImpl; + +import java.util.logging.Logger; + +public class CallbackApiTest extends AbstractCallbackApiTest { + + private static Logger LOG = Logger.getLogger(CallbackApiTest.class.getName()); + + + public static void main(String[] args) throws Exception { + LOG.info("Testing ControlPing using Java callback API for ioampot plugin"); + testControlPing(args[0], new JVppIoampotImpl()); + } +} diff --git a/src/vpp-api/java/jvpp-ioampot/io/fd/vpp/jvpp/ioampot/test/FutureApiTest.java b/src/vpp-api/java/jvpp-ioampot/io/fd/vpp/jvpp/ioampot/test/FutureApiTest.java new file mode 100644 index 00000000..6401c678 --- /dev/null +++ b/src/vpp-api/java/jvpp-ioampot/io/fd/vpp/jvpp/ioampot/test/FutureApiTest.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.ioampot.test; + + +import io.fd.vpp.jvpp.JVppRegistry; +import io.fd.vpp.jvpp.JVppRegistryImpl; +import io.fd.vpp.jvpp.ioampot.JVppIoampotImpl; +import io.fd.vpp.jvpp.ioampot.dto.PotProfileShowConfigDetailsReplyDump; +import io.fd.vpp.jvpp.ioampot.dto.PotProfileShowConfigDump; +import io.fd.vpp.jvpp.ioampot.future.FutureJVppIoampotFacade; + +import java.util.concurrent.Future; +import java.util.logging.Logger; + +public class FutureApiTest { + + private static final Logger LOG = Logger.getLogger(io.fd.vpp.jvpp.ioampot.test.FutureApiTest.class.getName()); + + public static void main(String[] args) throws Exception { + testCallbackApi(args); + } + + private static void testCallbackApi(String[] args) throws Exception { + LOG.info("Testing Java callback API for ioampot plugin"); + try (final JVppRegistry registry = new JVppRegistryImpl("FutureApiTest", args[0]); + final FutureJVppIoampotFacade jvpp = new FutureJVppIoampotFacade(registry, new JVppIoampotImpl())) { + LOG.info("Successfully connected to VPP"); + + testPotProfileShowConfigDump(jvpp); + + LOG.info("Disconnecting..."); + } + } + + private static void testPotProfileShowConfigDump(FutureJVppIoampotFacade jvpp) throws Exception { + LOG.info("Sending PotProfileShowConfigDump request..."); + final PotProfileShowConfigDump request = new PotProfileShowConfigDump(); + + final Future<PotProfileShowConfigDetailsReplyDump> replyFuture = jvpp.potProfileShowConfigDump(request).toCompletableFuture(); + final PotProfileShowConfigDetailsReplyDump reply = replyFuture.get(); + + if (reply == null || reply.potProfileShowConfigDetails == null) { + throw new IllegalStateException("Received null response for empty dump: " + reply); + } else { + LOG.info( + String.format( + "Received pot profile show config dump reply: %s", + reply.potProfileShowConfigDetails)); + } + } +} diff --git a/src/vpp-api/java/jvpp-ioampot/io/fd/vpp/jvpp/ioampot/test/Readme.txt b/src/vpp-api/java/jvpp-ioampot/io/fd/vpp/jvpp/ioampot/test/Readme.txt new file mode 100644 index 00000000..f3cae262 --- /dev/null +++ b/src/vpp-api/java/jvpp-ioampot/io/fd/vpp/jvpp/ioampot/test/Readme.txt @@ -0,0 +1,4 @@ +release version: +sudo java -cp build-vpp-native/vpp/vpp-api/java/jvpp-registry-17.10.jar:build-vpp_debug-native/vpp/vpp-api/java/jvpp-ioampot-17.10.jar io.fd.vpp.jvpp.ioampot.test.[test-name] +debug version: +sudo java -cp build-vpp_debug-native/vpp/vpp-api/java/jvpp-registry-17.10.jar:build-vpp_debug-native/vpp/vpp-api/java/jvpp-ioampot-17.10.jar io.fd.vpp.jvpp.ioampot.test.[test-name] diff --git a/src/vpp-api/java/jvpp-ioampot/jvpp_ioam_pot.c b/src/vpp-api/java/jvpp-ioampot/jvpp_ioam_pot.c new file mode 100644 index 00000000..8f396989 --- /dev/null +++ b/src/vpp-api/java/jvpp-ioampot/jvpp_ioam_pot.c @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2016 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 <vnet/vnet.h> + +#include <ioam/lib-pot/pot_msg_enum.h> +#define vl_typedefs /* define message structures */ +#include <ioam/lib-pot/pot_all_api_h.h> +#undef vl_typedefs + +#include <vnet/api_errno.h> +#include <vlibapi/api.h> +#include <vlibmemory/api.h> + +#if VPPJNI_DEBUG == 1 + #define DEBUG_LOG(...) clib_warning(__VA_ARGS__) +#else + #define DEBUG_LOG(...) +#endif + +#include <jvpp-common/jvpp_common.h> + +#include "jvpp-ioampot/io_fd_vpp_jvpp_ioampot_JVppIoampotImpl.h" +#include "jvpp_ioam_pot.h" +#include "jvpp-ioampot/jvpp_ioampot_gen.h" + +/* + * Class: io_fd_vpp_jvpp_ioampot_JVppIoampotImpl + * Method: init0 + * Signature: (JI)V + */ +JNIEXPORT void JNICALL Java_io_fd_vpp_jvpp_ioampot_JVppIoampotImpl_init0 + (JNIEnv *env, jclass clazz, jobject callback, jlong queue_address, jint my_client_index) { + ioampot_main_t * plugin_main = &ioampot_main; + clib_warning ("Java_io_fd_vpp_jvpp_ioampot_JVppIoampotImpl_init0"); + + plugin_main->my_client_index = my_client_index; + plugin_main->vl_input_queue = uword_to_pointer (queue_address, unix_shared_memory_queue_t *); + + plugin_main->callbackObject = (*env)->NewGlobalRef(env, callback); + plugin_main->callbackClass = (jclass)(*env)->NewGlobalRef(env, (*env)->GetObjectClass(env, callback)); + + // verify API has not changed since jar generation + #define _(N) \ + get_message_id(env, #N); + foreach_supported_api_message; + #undef _ + + #define _(N,n) \ + vl_msg_api_set_handlers(get_message_id(env, #N), #n, \ + vl_api_##n##_t_handler, \ + vl_noop_handler, \ + vl_noop_handler, \ + vl_noop_handler, \ + sizeof(vl_api_##n##_t), 1); + foreach_api_reply_handler; + #undef _ +} + +JNIEXPORT void JNICALL Java_io_fd_vpp_jvpp_ioampot_JVppIoampotImpl_close0 +(JNIEnv *env, jclass clazz) { + ioampot_main_t * plugin_main = &ioampot_main; + + // cleanup: + (*env)->DeleteGlobalRef(env, plugin_main->callbackClass); + (*env)->DeleteGlobalRef(env, plugin_main->callbackObject); + + plugin_main->callbackClass = NULL; + plugin_main->callbackObject = NULL; +} + +/* Attach thread to JVM and cache class references when initiating JVPP iOAM POT */ +jint JNI_OnLoad(JavaVM *vm, void *reserved) { + JNIEnv* env; + + if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_8) != JNI_OK) { + return JNI_EVERSION; + } + + if (cache_class_references(env) != 0) { + clib_warning ("Failed to cache class references\n"); + return JNI_ERR; + } + + return JNI_VERSION_1_8; +} + +/* Clean up cached references when disposing JVPP iOAM POT */ +void JNI_OnUnload(JavaVM *vm, void *reserved) { + JNIEnv* env; + if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_8) != JNI_OK) { + return; + } + delete_class_references(env); +} diff --git a/src/vpp-api/java/jvpp-ioampot/jvpp_ioam_pot.h b/src/vpp-api/java/jvpp-ioampot/jvpp_ioam_pot.h new file mode 100644 index 00000000..81e2a1bb --- /dev/null +++ b/src/vpp-api/java/jvpp-ioampot/jvpp_ioam_pot.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2016 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_jvpp_ioam_pot_h__ +#define __included_jvpp_ioam_pot_h__ + +#include <vnet/vnet.h> +#include <vnet/ip/ip.h> +#include <vnet/api_errno.h> +#include <vlibapi/api.h> +#include <vlibmemory/api.h> +#include <jni.h> + +/* Global state for JVPP-IOAM-POT */ +typedef struct { + /* Pointer to shared memory queue */ + unix_shared_memory_queue_t * vl_input_queue; + + /* VPP api client index */ + u32 my_client_index; + + /* Callback object and class references enabling asynchronous Java calls */ + jobject callbackObject; + jclass callbackClass; + +} ioampot_main_t; + +ioampot_main_t ioampot_main __attribute__((aligned (64))); + + +#endif /* __included_jvpp_ioam_pot_h__ */ diff --git a/src/vpp-api/java/jvpp-ioamtrace/io/fd/vpp/jvpp/ioamtrace/examples/IoamTraceApiExample.java b/src/vpp-api/java/jvpp-ioamtrace/io/fd/vpp/jvpp/ioamtrace/examples/IoamTraceApiExample.java new file mode 100644 index 00000000..827466bd --- /dev/null +++ b/src/vpp-api/java/jvpp-ioamtrace/io/fd/vpp/jvpp/ioamtrace/examples/IoamTraceApiExample.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.ioamtrace.examples; + +import io.fd.vpp.jvpp.JVpp; +import io.fd.vpp.jvpp.JVppRegistry; +import io.fd.vpp.jvpp.JVppRegistryImpl; +import io.fd.vpp.jvpp.VppCallbackException; +import io.fd.vpp.jvpp.ioamtrace.future.FutureJVppIoamtraceFacade; +import io.fd.vpp.jvpp.ioamtrace.JVppIoamtraceImpl; +import io.fd.vpp.jvpp.ioamtrace.callback.TraceProfileAddCallback; +import io.fd.vpp.jvpp.ioamtrace.dto.TraceProfileAdd; +import io.fd.vpp.jvpp.ioamtrace.dto.TraceProfileAddReply; +import io.fd.vpp.jvpp.ioamtrace.dto.TraceProfileShowConfig; +import io.fd.vpp.jvpp.ioamtrace.dto.TraceProfileShowConfigReply; + +public class IoamTraceApiExample { + + static class IoamTraceTestCallback implements TraceProfileAddCallback { + + @Override + public void onTraceProfileAddReply(final TraceProfileAddReply reply) { + System.out.printf("Received TraceProfileAddReply reply: context=%d%n", + reply.context); + } + + @Override + public void onError(VppCallbackException ex) { + System.out.printf("Received onError exception: call=%s, context=%d, retval=%d%n", ex.getMethodName(), + ex.getCtxId(), ex.getErrorCode()); + } + } + + public static void main(String[] args) throws Exception { + ioamTraceTestApi(); + } + + private static void ioamTraceTestApi() throws Exception { + System.out.println("Testing Java API for ioam trace plugin"); + try (final JVppRegistry registry = new JVppRegistryImpl("ioamTraceApiTest"); + final JVpp jvpp = new JVppIoamtraceImpl()) { + FutureJVppIoamtraceFacade ioamtraceJvpp = new FutureJVppIoamtraceFacade(registry,jvpp); + + System.out.println("Sending ioam trace profile add request..."); + TraceProfileAdd request = new TraceProfileAdd(); + request.traceType = 0x1f; + request.numElts = 4; + request.nodeId = 1; + request.traceTsp = 2; + request.appData = 1234; + final int result = jvpp.send(request); + System.out.printf("TraceProfileAdd send result = %d%n", result); + + Thread.sleep(1000); + + TraceProfileShowConfig showRequest = new TraceProfileShowConfig(); + TraceProfileShowConfigReply reply = ioamtraceJvpp.traceProfileShowConfig(showRequest).toCompletableFuture().get(); + System.out.printf("TraceProfileShowConfig result = "+ reply.toString()); + + System.out.println("Disconnecting..."); + } + } +} diff --git a/src/vpp-api/java/jvpp-ioamtrace/io/fd/vpp/jvpp/ioamtrace/examples/Readme.txt b/src/vpp-api/java/jvpp-ioamtrace/io/fd/vpp/jvpp/ioamtrace/examples/Readme.txt new file mode 100644 index 00000000..e8c3907e --- /dev/null +++ b/src/vpp-api/java/jvpp-ioamtrace/io/fd/vpp/jvpp/ioamtrace/examples/Readme.txt @@ -0,0 +1,4 @@ +release version: +sudo java -cp build-vpp-native/vpp/vpp-api/java/jvpp-registry-17.10.jar:build-vpp-native/vpp/vpp-api/java/jvpp-ioamtrace-17.10.jar io.fd.vpp.jvpp.ioamtrace.examples.IoamTraceApiExample +debug vresion: +sudo java -cp build-vpp_debug-native/vpp/vpp-api/java/jvpp-registry-17.10.jar:build-vpp_debug-native/vpp/vpp-api/java/jvpp-ioamtrace-17.10.jar io.fd.vpp.jvpp.ioamtrace.examples.IoamTraceApiExample diff --git a/src/vpp-api/java/jvpp-ioamtrace/io/fd/vpp/jvpp/ioamtrace/test/CallbackApiTest.java b/src/vpp-api/java/jvpp-ioamtrace/io/fd/vpp/jvpp/ioamtrace/test/CallbackApiTest.java new file mode 100644 index 00000000..4a71db52 --- /dev/null +++ b/src/vpp-api/java/jvpp-ioamtrace/io/fd/vpp/jvpp/ioamtrace/test/CallbackApiTest.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.ioamtrace.test; + +import io.fd.vpp.jvpp.AbstractCallbackApiTest; +import io.fd.vpp.jvpp.ioamtrace.JVppIoamtraceImpl; + +import java.util.logging.Logger; + +public class CallbackApiTest extends AbstractCallbackApiTest { + + private static Logger LOG = Logger.getLogger(CallbackApiTest.class.getName()); + + + public static void main(String[] args) throws Exception { + LOG.info("Testing ControlPing using Java callback API for ioamtrace plugin"); + testControlPing(args[0], new JVppIoamtraceImpl()); + } +} diff --git a/src/vpp-api/java/jvpp-ioamtrace/io/fd/vpp/jvpp/ioamtrace/test/FutureApiTest.java b/src/vpp-api/java/jvpp-ioamtrace/io/fd/vpp/jvpp/ioamtrace/test/FutureApiTest.java new file mode 100644 index 00000000..4e13ed1f --- /dev/null +++ b/src/vpp-api/java/jvpp-ioamtrace/io/fd/vpp/jvpp/ioamtrace/test/FutureApiTest.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.ioamtrace.test; + + +import io.fd.vpp.jvpp.Assertions; +import io.fd.vpp.jvpp.JVppRegistry; +import io.fd.vpp.jvpp.JVppRegistryImpl; +import io.fd.vpp.jvpp.ioamtrace.JVppIoamtraceImpl; +import io.fd.vpp.jvpp.ioamtrace.dto.TraceProfileShowConfig; +import io.fd.vpp.jvpp.ioamtrace.dto.TraceProfileShowConfigReply; +import io.fd.vpp.jvpp.ioamtrace.future.FutureJVppIoamtraceFacade; + +import java.util.concurrent.Future; +import java.util.logging.Logger; + +public class FutureApiTest { + + private static final Logger LOG = Logger.getLogger(io.fd.vpp.jvpp.ioamtrace.test.FutureApiTest.class.getName()); + + public static void main(String[] args) throws Exception { + testCallbackApi(args); + } + + private static void testCallbackApi(String[] args) throws Exception { + LOG.info("Testing Java callback API for ioamtrace plugin"); + try (final JVppRegistry registry = new JVppRegistryImpl("FutureApiTest", args[0]); + final FutureJVppIoamtraceFacade jvpp = new FutureJVppIoamtraceFacade(registry, new JVppIoamtraceImpl())) { + LOG.info("Successfully connected to VPP"); + + testTraceProfileShowConfig(jvpp); + + LOG.info("Disconnecting..."); + } + } + + private static void testTraceProfileShowConfig(FutureJVppIoamtraceFacade jvpp) throws Exception { + LOG.info("Sending TraceProfileShowConfig request..."); + final TraceProfileShowConfig request = new TraceProfileShowConfig(); + + final Future<TraceProfileShowConfigReply> replyFuture = jvpp.traceProfileShowConfig(request).toCompletableFuture(); + final TraceProfileShowConfigReply reply = replyFuture.get(); + + Assertions.assertNotNull(reply); + } +} diff --git a/src/vpp-api/java/jvpp-ioamtrace/io/fd/vpp/jvpp/ioamtrace/test/Readme.txt b/src/vpp-api/java/jvpp-ioamtrace/io/fd/vpp/jvpp/ioamtrace/test/Readme.txt new file mode 100644 index 00000000..9a1ba829 --- /dev/null +++ b/src/vpp-api/java/jvpp-ioamtrace/io/fd/vpp/jvpp/ioamtrace/test/Readme.txt @@ -0,0 +1,4 @@ +release version: +sudo java -cp build-vpp-native/vpp/vpp-api/java/jvpp-registry-17.10.jar:build-vpp_debug-native/vpp/vpp-api/java/jvpp-ioamtrace-17.10.jar io.fd.vpp.jvpp.ioamtrace.test.[test-name] +debug version: +sudo java -cp build-vpp_debug-native/vpp/vpp-api/java/jvpp-registry-17.10.jar:build-vpp_debug-native/vpp/vpp-api/java/jvpp-ioamtrace-17.10.jar io.fd.vpp.jvpp.ioamtrace.test.[test-name] diff --git a/src/vpp-api/java/jvpp-ioamtrace/jvpp_ioam_trace.c b/src/vpp-api/java/jvpp-ioamtrace/jvpp_ioam_trace.c new file mode 100644 index 00000000..f53937e7 --- /dev/null +++ b/src/vpp-api/java/jvpp-ioamtrace/jvpp_ioam_trace.c @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2016 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 <vnet/vnet.h> + +#include <ioam/lib-trace/trace_msg_enum.h> +#define vl_typedefs /* define message structures */ +#include <ioam/lib-trace/trace_all_api_h.h> +#undef vl_typedefs + +#include <vnet/api_errno.h> +#include <vlibapi/api.h> +#include <vlibmemory/api.h> + +#if VPPJNI_DEBUG == 1 + #define DEBUG_LOG(...) clib_warning(__VA_ARGS__) +#else + #define DEBUG_LOG(...) +#endif + +#include <jvpp-common/jvpp_common.h> + +#include "jvpp-ioamtrace/io_fd_vpp_jvpp_ioamtrace_JVppIoamtraceImpl.h" +#include "jvpp_ioam_trace.h" +#include "jvpp-ioamtrace/jvpp_ioamtrace_gen.h" + +/* + * Class: io_fd_vpp_jvpp_ioamtrace_JVppIoamtraceImpl + * Method: init0 + * Signature: (JI)V + */ +JNIEXPORT void JNICALL Java_io_fd_vpp_jvpp_ioamtrace_JVppIoamtraceImpl_init0 + (JNIEnv *env, jclass clazz, jobject callback, jlong queue_address, jint my_client_index) { + ioamtrace_main_t * plugin_main = &ioamtrace_main; + clib_warning ("Java_io_fd_vpp_jvpp_ioamtrace_JVppIoamtraceImpl_init0"); + + plugin_main->my_client_index = my_client_index; + plugin_main->vl_input_queue = uword_to_pointer (queue_address, unix_shared_memory_queue_t *); + + plugin_main->callbackObject = (*env)->NewGlobalRef(env, callback); + plugin_main->callbackClass = (jclass)(*env)->NewGlobalRef(env, (*env)->GetObjectClass(env, callback)); + + // verify API has not changed since jar generation + #define _(N) \ + get_message_id(env, #N); + foreach_supported_api_message; + #undef _ + + #define _(N,n) \ + vl_msg_api_set_handlers(get_message_id(env, #N), #n, \ + vl_api_##n##_t_handler, \ + vl_noop_handler, \ + vl_noop_handler, \ + vl_noop_handler, \ + sizeof(vl_api_##n##_t), 1); + foreach_api_reply_handler; + #undef _ +} + +JNIEXPORT void JNICALL Java_io_fd_vpp_jvpp_ioamtrace_JVppIoamtraceImpl_close0 +(JNIEnv *env, jclass clazz) { + ioamtrace_main_t * plugin_main = &ioamtrace_main; + + // cleanup: + (*env)->DeleteGlobalRef(env, plugin_main->callbackClass); + (*env)->DeleteGlobalRef(env, plugin_main->callbackObject); + + plugin_main->callbackClass = NULL; + plugin_main->callbackObject = NULL; +} + +/* Attach thread to JVM and cache class references when initiating JVPP iOAM Trace */ +jint JNI_OnLoad(JavaVM *vm, void *reserved) { + JNIEnv* env; + + if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_8) != JNI_OK) { + return JNI_EVERSION; + } + + if (cache_class_references(env) != 0) { + clib_warning ("Failed to cache class references\n"); + return JNI_ERR; + } + + return JNI_VERSION_1_8; +} + +/* Clean up cached references when disposing JVPP iOAM Trace */ +void JNI_OnUnload(JavaVM *vm, void *reserved) { + JNIEnv* env; + if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_8) != JNI_OK) { + return; + } + delete_class_references(env); +} diff --git a/src/vpp-api/java/jvpp-ioamtrace/jvpp_ioam_trace.h b/src/vpp-api/java/jvpp-ioamtrace/jvpp_ioam_trace.h new file mode 100644 index 00000000..cb0b27e1 --- /dev/null +++ b/src/vpp-api/java/jvpp-ioamtrace/jvpp_ioam_trace.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2016 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_jvpp_ioam_trace_h__ +#define __included_jvpp_ioam_trace_h__ + +#include <vnet/vnet.h> +#include <vnet/ip/ip.h> +#include <vnet/api_errno.h> +#include <vlibapi/api.h> +#include <vlibmemory/api.h> +#include <jni.h> + +/* Global state for JVPP-IOAM-TRACE */ +typedef struct { + /* Pointer to shared memory queue */ + unix_shared_memory_queue_t * vl_input_queue; + + /* VPP api client index */ + u32 my_client_index; + + /* Callback object and class references enabling asynchronous Java calls */ + jobject callbackObject; + jclass callbackClass; + +} ioamtrace_main_t; + +ioamtrace_main_t ioamtrace_main __attribute__((aligned (64))); + + +#endif /* __included_jvpp_ioam_trace_h__ */ diff --git a/src/vpp-api/java/jvpp-nat/io/fd/vpp/jvpp/nat/examples/CallbackApiExample.java b/src/vpp-api/java/jvpp-nat/io/fd/vpp/jvpp/nat/examples/CallbackApiExample.java new file mode 100644 index 00000000..e4d5cb33 --- /dev/null +++ b/src/vpp-api/java/jvpp-nat/io/fd/vpp/jvpp/nat/examples/CallbackApiExample.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.nat.examples; + +import io.fd.vpp.jvpp.JVpp; +import io.fd.vpp.jvpp.JVppRegistry; +import io.fd.vpp.jvpp.JVppRegistryImpl; +import io.fd.vpp.jvpp.VppCallbackException; +import io.fd.vpp.jvpp.nat.JVppNatImpl; +import io.fd.vpp.jvpp.nat.callback.Nat44InterfaceAddDelFeatureCallback; +import io.fd.vpp.jvpp.nat.dto.Nat44InterfaceAddDelFeature; +import io.fd.vpp.jvpp.nat.dto.Nat44InterfaceAddDelFeatureReply; + +public class CallbackApiExample { + + static class TestCallback implements Nat44InterfaceAddDelFeatureCallback { + + @Override + public void onNat44InterfaceAddDelFeatureReply(final Nat44InterfaceAddDelFeatureReply msg) { + System.out.printf("Received Nat44InterfaceAddDelFeatureReply: context=%d%n", + msg.context); + } + + @Override + public void onError(VppCallbackException ex) { + System.out.printf("Received onError exception: call=%s, context=%d, retval=%d%n", ex.getMethodName(), + ex.getCtxId(), ex.getErrorCode()); + } + } + + public static void main(String[] args) throws Exception { + testCallbackApi(); + } + + private static void testCallbackApi() throws Exception { + System.out.println("Testing Java callback API for nat plugin"); + try (final JVppRegistry registry = new JVppRegistryImpl("NatCallbackApiTest"); + final JVpp jvpp = new JVppNatImpl()) { + registry.register(jvpp, new TestCallback()); + + System.out.println("Sending Nat44InterfaceAddDelFeature request..."); + Nat44InterfaceAddDelFeature request = new Nat44InterfaceAddDelFeature(); + request.isAdd = 1; + request.isInside = 1; + request.swIfIndex = 1; + final int result = jvpp.send(request); + System.out.printf("Nat44InterfaceAddDelFeature send result = %d%n", result); + + Thread.sleep(1000); + + System.out.println("Disconnecting..."); + } + } +} diff --git a/src/vpp-api/java/jvpp-nat/io/fd/vpp/jvpp/nat/examples/Readme.txt b/src/vpp-api/java/jvpp-nat/io/fd/vpp/jvpp/nat/examples/Readme.txt new file mode 100644 index 00000000..ac75e04e --- /dev/null +++ b/src/vpp-api/java/jvpp-nat/io/fd/vpp/jvpp/nat/examples/Readme.txt @@ -0,0 +1 @@ +sudo java -cp build-vpp-native/vpp/vpp-api/java/jvpp-registry-17.10.jar:build-vpp-native/vpp/vpp-api/java/jvpp-nat-17.10.jar io.fd.vpp.jvpp.nat.examples.CallbackApiExample diff --git a/src/vpp-api/java/jvpp-nat/io/fd/vpp/jvpp/nat/test/CallbackApiTest.java b/src/vpp-api/java/jvpp-nat/io/fd/vpp/jvpp/nat/test/CallbackApiTest.java new file mode 100644 index 00000000..a6f82148 --- /dev/null +++ b/src/vpp-api/java/jvpp-nat/io/fd/vpp/jvpp/nat/test/CallbackApiTest.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.nat.test; + +import io.fd.vpp.jvpp.AbstractCallbackApiTest; +import io.fd.vpp.jvpp.nat.JVppNatImpl; + +import java.util.logging.Logger; + +public class CallbackApiTest extends AbstractCallbackApiTest { + + private static Logger LOG = Logger.getLogger(CallbackApiTest.class.getName()); + + + public static void main(String[] args) throws Exception { + LOG.info("Testing ControlPing using Java callback API for core plugin"); + testControlPing(args[0], new JVppNatImpl()); + } +} diff --git a/src/vpp-api/java/jvpp-nat/io/fd/vpp/jvpp/nat/test/FutureApiTest.java b/src/vpp-api/java/jvpp-nat/io/fd/vpp/jvpp/nat/test/FutureApiTest.java new file mode 100644 index 00000000..8643dcf4 --- /dev/null +++ b/src/vpp-api/java/jvpp-nat/io/fd/vpp/jvpp/nat/test/FutureApiTest.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.nat.test; + + +import io.fd.vpp.jvpp.JVppRegistry; +import io.fd.vpp.jvpp.JVppRegistryImpl; +import io.fd.vpp.jvpp.nat.JVppNatImpl; +import io.fd.vpp.jvpp.nat.dto.SnatAddressDetailsReplyDump; +import io.fd.vpp.jvpp.nat.dto.SnatAddressDump; +import io.fd.vpp.jvpp.nat.future.FutureJVppNatFacade; + +import java.util.concurrent.Future; +import java.util.logging.Logger; + +public class FutureApiTest { + + private static final Logger LOG = Logger.getLogger(io.fd.vpp.jvpp.nat.test.FutureApiTest.class.getName()); + + public static void main(String[] args) throws Exception { + testCallbackApi(args); + } + + private static void testCallbackApi(String[] args) throws Exception { + LOG.info("Testing Java callback API for snat plugin"); + try (final JVppRegistry registry = new JVppRegistryImpl("FutureApiTest", args[0]); + final FutureJVppNatFacade jvpp = new FutureJVppNatFacade(registry, new JVppNatImpl())) { + LOG.info("Successfully connected to VPP"); + + testAclDump(jvpp); + + LOG.info("Disconnecting..."); + } + } + + private static void testAclDump(FutureJVppNatFacade jvpp) throws Exception { + LOG.info("Sending SnatAddressDump request..."); + final SnatAddressDump request = new SnatAddressDump(); + + final Future<SnatAddressDetailsReplyDump> replyFuture = jvpp.snatAddressDump(request).toCompletableFuture(); + final SnatAddressDetailsReplyDump reply = replyFuture.get(); + + if (reply == null || reply.snatAddressDetails == null) { + throw new IllegalStateException("Received null response for empty dump: " + reply); + } else { + LOG.info( + String.format( + "Received snat address dump reply with list of snat address: %s", + reply.snatAddressDetails)); + } + } +} diff --git a/src/vpp-api/java/jvpp-nat/io/fd/vpp/jvpp/nat/test/Readme.txt b/src/vpp-api/java/jvpp-nat/io/fd/vpp/jvpp/nat/test/Readme.txt new file mode 100644 index 00000000..6f758089 --- /dev/null +++ b/src/vpp-api/java/jvpp-nat/io/fd/vpp/jvpp/nat/test/Readme.txt @@ -0,0 +1,4 @@ +release version: +sudo java -cp build-vpp-native/vpp/vpp-api/java/jvpp-registry-17.10.jar:build-vpp_debug-native/vpp/vpp-api/java/jvpp-nat-17.10.jar io.fd.vpp.jvpp.nat.test.[test-name] +debug version: +sudo java -cp build-vpp_debug-native/vpp/vpp-api/java/jvpp-registry-17.10.jar:build-vpp_debug-native/vpp/vpp-api/java/jvpp-nat-17.10.jar io.fd.vpp.jvpp.nat.test.[test-name] diff --git a/src/vpp-api/java/jvpp-nat/jvpp_nat.c b/src/vpp-api/java/jvpp-nat/jvpp_nat.c new file mode 100644 index 00000000..85217f04 --- /dev/null +++ b/src/vpp-api/java/jvpp-nat/jvpp_nat.c @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2016 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 <vnet/vnet.h> + +#include <nat/nat_msg_enum.h> +#define vl_typedefs /* define message structures */ +#include <nat/nat_all_api_h.h> +#undef vl_typedefs + +#include <vnet/api_errno.h> +#include <vlibapi/api.h> +#include <vlibmemory/api.h> + +#if VPPJNI_DEBUG == 1 + #define DEBUG_LOG(...) clib_warning(__VA_ARGS__) +#else + #define DEBUG_LOG(...) +#endif + +#include <jvpp-common/jvpp_common.h> + +#include "jvpp-nat/io_fd_vpp_jvpp_nat_JVppNatImpl.h" +#include "jvpp_nat.h" +#include "jvpp-nat/jvpp_nat_gen.h" + +/* + * Class: io_fd_vpp_jvpp_nat_JVppNatImpl + * Method: init0 + * Signature: (JI)V + */ +JNIEXPORT void JNICALL Java_io_fd_vpp_jvpp_nat_JVppNatImpl_init0 + (JNIEnv *env, jclass clazz, jobject callback, jlong queue_address, jint my_client_index) { + nat_main_t * plugin_main = &nat_main; + clib_warning ("Java_io_fd_vpp_jvpp_nat_JVppNatImpl_init0"); + + plugin_main->my_client_index = my_client_index; + plugin_main->vl_input_queue = uword_to_pointer (queue_address, unix_shared_memory_queue_t *); + + plugin_main->callbackObject = (*env)->NewGlobalRef(env, callback); + plugin_main->callbackClass = (jclass)(*env)->NewGlobalRef(env, (*env)->GetObjectClass(env, callback)); + + // verify API has not changed since jar generation + #define _(N) \ + get_message_id(env, #N); + foreach_supported_api_message; + #undef _ + + #define _(N,n) \ + vl_msg_api_set_handlers(get_message_id(env, #N), #n, \ + vl_api_##n##_t_handler, \ + vl_noop_handler, \ + vl_noop_handler, \ + vl_noop_handler, \ + sizeof(vl_api_##n##_t), 1); + foreach_api_reply_handler; + #undef _ +} + +JNIEXPORT void JNICALL Java_io_fd_vpp_jvpp_nat_JVppNatImpl_close0 +(JNIEnv *env, jclass clazz) { + nat_main_t * plugin_main = &nat_main; + + // cleanup: + (*env)->DeleteGlobalRef(env, plugin_main->callbackClass); + (*env)->DeleteGlobalRef(env, plugin_main->callbackObject); + + plugin_main->callbackClass = NULL; + plugin_main->callbackObject = NULL; +} + +/* Attach thread to JVM and cache class references when initiating JVPP SNAT */ +jint JNI_OnLoad(JavaVM *vm, void *reserved) { + JNIEnv* env; + + if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_8) != JNI_OK) { + return JNI_EVERSION; + } + + if (cache_class_references(env) != 0) { + clib_warning ("Failed to cache class references\n"); + return JNI_ERR; + } + + return JNI_VERSION_1_8; +} + +/* Clean up cached references when disposing JVPP SNAT */ +void JNI_OnUnload(JavaVM *vm, void *reserved) { + JNIEnv* env; + if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_8) != JNI_OK) { + return; + } + delete_class_references(env); +} diff --git a/src/vpp-api/java/jvpp-nat/jvpp_nat.h b/src/vpp-api/java/jvpp-nat/jvpp_nat.h new file mode 100644 index 00000000..c8f6b683 --- /dev/null +++ b/src/vpp-api/java/jvpp-nat/jvpp_nat.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2016 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_jvpp_nat_h__ +#define __included_jvpp_nat_h__ + +#include <vnet/vnet.h> +#include <vnet/ip/ip.h> +#include <vnet/api_errno.h> +#include <vlibapi/api.h> +#include <vlibmemory/api.h> +#include <jni.h> + +/* Global state for JVPP-NAT */ +typedef struct { + /* Pointer to shared memory queue */ + unix_shared_memory_queue_t * vl_input_queue; + + /* VPP api client index */ + u32 my_client_index; + + /* Callback object and class references enabling asynchronous Java calls */ + jobject callbackObject; + jclass callbackClass; + +} nat_main_t; + +nat_main_t nat_main __attribute__((aligned (64))); + + +#endif /* __included_jvpp_nat_h__ */ diff --git a/src/vpp-api/java/jvpp-pppoe/jvpp_pppoe.c b/src/vpp-api/java/jvpp-pppoe/jvpp_pppoe.c new file mode 100644 index 00000000..c9c30305 --- /dev/null +++ b/src/vpp-api/java/jvpp-pppoe/jvpp_pppoe.c @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2016 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 <vnet/vnet.h> + +#include <pppoe/pppoe_msg_enum.h> +#define vl_typedefs /* define message structures */ +#include <pppoe/pppoe_all_api_h.h> +#undef vl_typedefs + +#include <vnet/api_errno.h> +#include <vlibapi/api.h> +#include <vlibmemory/api.h> + +#if VPPJNI_DEBUG == 1 + #define DEBUG_LOG(...) clib_warning(__VA_ARGS__) +#else + #define DEBUG_LOG(...) +#endif + +#include <jvpp-common/jvpp_common.h> + +#include "jvpp-pppoe/io_fd_vpp_jvpp_pppoe_JVppPppoeImpl.h" +#include "jvpp_pppoe.h" +#include "jvpp-pppoe/jvpp_pppoe_gen.h" + +/* + * Class: io_fd_vpp_jvpp_pppoe_JVpppppoeImpl + * Method: init0 + * Signature: (JI)V + */ +JNIEXPORT void JNICALL Java_io_fd_vpp_jvpp_pppoe_JVppPppoeImpl_init0 + (JNIEnv *env, jclass clazz, jobject callback, jlong queue_address, jint my_client_index) { + pppoe_main_t * plugin_main = &pppoe_main; + clib_warning ("Java_io_fd_vpp_jvpp_pppoe_JVppPppoeImpl_init0"); + + plugin_main->my_client_index = my_client_index; + plugin_main->vl_input_queue = (unix_shared_memory_queue_t *)queue_address; + + plugin_main->callbackObject = (*env)->NewGlobalRef(env, callback); + plugin_main->callbackClass = (jclass)(*env)->NewGlobalRef(env, (*env)->GetObjectClass(env, callback)); + + // verify API has not changed since jar generation + #define _(N) \ + get_message_id(env, #N); \ + foreach_supported_api_message; + #undef _ + + #define _(N,n) \ + vl_msg_api_set_handlers(get_message_id(env, #N), #n, \ + vl_api_##n##_t_handler, \ + vl_noop_handler, \ + vl_noop_handler, \ + vl_noop_handler, \ + sizeof(vl_api_##n##_t), 1); + foreach_api_reply_handler; + #undef _ +} + +JNIEXPORT void JNICALL Java_io_fd_vpp_jvpp_pppoe_JVppPppoeImpl_close0 +(JNIEnv *env, jclass clazz) { + pppoe_main_t * plugin_main = &pppoe_main; + + // cleanup: + (*env)->DeleteGlobalRef(env, plugin_main->callbackClass); + (*env)->DeleteGlobalRef(env, plugin_main->callbackObject); + + plugin_main->callbackClass = NULL; + plugin_main->callbackObject = NULL; +} + +/* Attach thread to JVM and cache class references when initiating JVPP ACL */ +jint JNI_OnLoad(JavaVM *vm, void *reserved) { + JNIEnv* env; + + if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_8) != JNI_OK) { + return JNI_EVERSION; + } + + if (cache_class_references(env) != 0) { + clib_warning ("Failed to cache class references\n"); + return JNI_ERR; + } + + return JNI_VERSION_1_8; +} + +/* Clean up cached references when disposing JVPP ACL */ +void JNI_OnUnload(JavaVM *vm, void *reserved) { + JNIEnv* env; + if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_8) != JNI_OK) { + return; + } + delete_class_references(env); +} diff --git a/src/vpp-api/java/jvpp-pppoe/jvpp_pppoe.h b/src/vpp-api/java/jvpp-pppoe/jvpp_pppoe.h new file mode 100644 index 00000000..4523ba9c --- /dev/null +++ b/src/vpp-api/java/jvpp-pppoe/jvpp_pppoe.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2016 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_jvpp_pppoe_h__ +#define __included_jvpp_pppoe_h__ + +#include <vnet/vnet.h> +#include <vnet/ip/ip.h> +#include <vnet/api_errno.h> +#include <vlibapi/api.h> +#include <vlibmemory/api.h> +#include <jni.h> + +/* Global state for JVPP-pppoe */ +typedef struct { + /* Pointer to shared memory queue */ + unix_shared_memory_queue_t * vl_input_queue; + + /* VPP api client index */ + u32 my_client_index; + + /* Callback object and class references enabling asynchronous Java calls */ + jobject callbackObject; + jclass callbackClass; + +} pppoe_main_t; + +pppoe_main_t pppoe_main __attribute__((aligned (64))); + + +#endif /* __included_jvpp_pppoe_h__ */ diff --git a/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/AbstractCallbackApiTest.java b/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/AbstractCallbackApiTest.java new file mode 100644 index 00000000..d221d1e0 --- /dev/null +++ b/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/AbstractCallbackApiTest.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.fd.vpp.jvpp; + +import io.fd.vpp.jvpp.callback.ControlPingCallback; +import io.fd.vpp.jvpp.dto.ControlPing; +import io.fd.vpp.jvpp.dto.ControlPingReply; + +public abstract class AbstractCallbackApiTest { + + private static int receivedPingCount = 0; + private static int errorPingCount = 0; + + public static void testControlPing(String shm_prefix, JVpp jvpp) throws Exception { + try (JVppRegistry registry = new JVppRegistryImpl("CallbackApiTest", shm_prefix)) { + + registry.register(jvpp, new ControlPingCallback() { + @Override + public void onControlPingReply(final ControlPingReply reply) { + System.out.printf("Received ControlPingReply: %s%n", reply); + receivedPingCount++; + } + + @Override + public void onError(VppCallbackException ex) { + System.out.printf("Received onError exception: call=%s, reply=%d, context=%d ", ex.getMethodName(), + ex.getErrorCode(), ex.getCtxId()); + errorPingCount++; + } + + }); + System.out.println("Successfully connected to VPP"); + Thread.sleep(1000); + + System.out.println("Sending control ping using JVppRegistry"); + registry.controlPing(jvpp.getClass()); + + Thread.sleep(2000); + + System.out.println("Sending control ping using JVpp plugin"); + jvpp.send(new ControlPing()); + + Thread.sleep(2000); + System.out.println("Disconnecting..."); + Assertions.assertEquals(2, receivedPingCount); + Assertions.assertEquals(0, errorPingCount); + } + } +} diff --git a/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/Assertions.java b/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/Assertions.java new file mode 100644 index 00000000..f8b591f5 --- /dev/null +++ b/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/Assertions.java @@ -0,0 +1,32 @@ +/* + * 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. + */ + +package io.fd.vpp.jvpp; + +public class Assertions { + + public static void assertEquals(final int expected, final int actual) { + if (expected != actual) { + throw new IllegalArgumentException(String.format("Expected[%s]/Actual[%s]", expected, actual)); + } + } + + public static void assertNotNull(final Object value) { + if (value == null) { + throw new IllegalArgumentException("Variable is null"); + } + } +} diff --git a/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/JVpp.java b/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/JVpp.java new file mode 100644 index 00000000..55f25a7b --- /dev/null +++ b/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/JVpp.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp; + +import io.fd.vpp.jvpp.callback.JVppCallback; +import io.fd.vpp.jvpp.dto.ControlPing; +import io.fd.vpp.jvpp.dto.JVppRequest; + +/** + * Base interface for plugin's Java API. + */ +public interface JVpp extends AutoCloseable { + + /** + * Sends request to vpp. + * + * @param request request to be sent + * @return unique identifer of message in message queue + * @throws VppInvocationException when message could not be sent + */ + int send(final JVppRequest request) throws VppInvocationException; + + /** + * Initializes plugin's Java API. + * + * @param registry plugin registry + * @param callback called by vpe.api message handlers + * @param queueAddress address of vpp shared memory queue + * @param clientIndex vpp client identifier + */ + void init(final JVppRegistry registry, final JVppCallback callback, final long queueAddress, + final int clientIndex); + + /** + * Sends control_ping message. + * + * @param controlPing request DTO + * @return unique identifer of message in message queue + * @throws VppInvocationException when message could not be sent + */ + int controlPing(final ControlPing controlPing) throws VppInvocationException; +} diff --git a/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/JVppRegistry.java b/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/JVppRegistry.java new file mode 100644 index 00000000..6535db02 --- /dev/null +++ b/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/JVppRegistry.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp; + +import io.fd.vpp.jvpp.callback.JVppCallback; + +/** + * Manages VPP connection and stores plugin callbacks. + */ +public interface JVppRegistry extends AutoCloseable { + + /** + * Vpp connection managed by the registry. + * + * @return representation of vpp connection + */ + VppConnection getConnection(); + + /** + * Registers callback and initializes Java API for given plugin. + * + * @param jvpp plugin name + * @param callback callback provided by the plugin + * @throws NullPointerException if name or callback is null + * @throws IllegalArgumentException if plugin was already registered + */ + void register(final JVpp jvpp, final JVppCallback callback); + + /** + * Unregisters callback for the given plugin. + * + * @param name plugin name + * @throws NullPointerException if name is null + * @throws IllegalArgumentException if plugin was not registered + */ + void unregister(final String name); + + /** + * Returns callback registered for the plugin. + * + * @param name plugin name + * @return callback provided by the plugin + * @throws NullPointerException if name is null + * @throws IllegalArgumentException if plugin was not registered + */ + JVppCallback get(final String name); + + /** + * Sends control ping. Reply handler calls callback registered for give plugin. + * + * Control ping is used for initial RX thread to Java thread attachment + * that takes place in the plugin's JNI lib + * and to wrap dump message replies in one list. + * + * VPP plugins don't have to provide special control ping, therefore + * it is necessary to providing control ping support in JVppRegistry. + + * @param clazz identifies plugin that should receive ping callback + * @return unique identifier of message in message queue + */ + int controlPing(final Class<? extends JVpp> clazz) throws VppInvocationException; +} diff --git a/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/JVppRegistryImpl.java b/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/JVppRegistryImpl.java new file mode 100644 index 00000000..6e938ae3 --- /dev/null +++ b/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/JVppRegistryImpl.java @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp; + +import static java.util.Objects.requireNonNull; + +import io.fd.vpp.jvpp.callback.ControlPingCallback; +import io.fd.vpp.jvpp.callback.JVppCallback; +import io.fd.vpp.jvpp.dto.ControlPingReply; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Default implementation of JVppRegistry. + */ +public final class JVppRegistryImpl implements JVppRegistry, ControlPingCallback { + + private static final Logger LOG = Logger.getLogger(JVppRegistryImpl.class.getName()); + + private final VppJNIConnection connection; + // Unguarded concurrent map, no race conditions expected on top of that + private final Map<String, JVppCallback> pluginRegistry; + // Guarded by self + private final Map<Integer, ControlPingCallback> pingCalls; + + public JVppRegistryImpl(final String clientName) throws IOException { + connection = new VppJNIConnection(clientName); + connection.connect(); + pluginRegistry = new ConcurrentHashMap<>(); + pingCalls = new HashMap<>(); + } + + public JVppRegistryImpl(final String clientName, final String shmPrefix) throws IOException { + connection = new VppJNIConnection(clientName, shmPrefix); + connection.connect(); + pluginRegistry = new ConcurrentHashMap<>(); + pingCalls = new HashMap<>(); + } + + @Override + public VppConnection getConnection() { + return connection; + } + + @Override + public void register(final JVpp jvpp, final JVppCallback callback) { + requireNonNull(jvpp, "jvpp should not be null"); + requireNonNull(callback, "Callback should not be null"); + final String name = jvpp.getClass().getName(); + if (pluginRegistry.containsKey(name)) { + throw new IllegalArgumentException( + String.format("Callback for plugin %s was already registered", name)); + } + jvpp.init(this, callback, connection.getConnectionInfo().queueAddress, + connection.getConnectionInfo().clientIndex); + pluginRegistry.put(name, callback); + } + + @Override + public void unregister(final String name) { + requireNonNull(name, "Plugin name should not be null"); + final JVppCallback previous = pluginRegistry.remove(name); + assertPluginWasRegistered(name, previous); + } + + @Override + public JVppCallback get(final String name) { + requireNonNull(name, "Plugin name should not be null"); + JVppCallback value = pluginRegistry.get(name); + assertPluginWasRegistered(name, value); + return value; + } + + private native int controlPing0() throws VppInvocationException; + + @Override + public int controlPing(final Class<? extends JVpp> clazz) throws VppInvocationException { + connection.checkActive(); + final String name = clazz.getName(); + + final ControlPingCallback callback = (ControlPingCallback) pluginRegistry.get(clazz.getName()); + assertPluginWasRegistered(name, callback); + + synchronized (pingCalls) { + int context = controlPing0(); + if (context < 0) { + throw new VppInvocationException("controlPing", context); + } + + pingCalls.put(context, callback); + return context; + } + } + + @Override + public void onControlPingReply(final ControlPingReply reply) { + final ControlPingCallback callback; + synchronized (pingCalls) { + callback = pingCalls.remove(reply.context); + if (callback == null) { + LOG.log(Level.WARNING, "No callback was registered for reply context=" + reply.context + " Contexts waiting=" + + pingCalls.keySet()); + return; + } + } + // pass the reply to the callback registered by the ping caller + callback.onControlPingReply(reply); + } + + @Override + public void onError(final VppCallbackException ex) { + final int ctxId = ex.getCtxId(); + final ControlPingCallback callback; + + synchronized (pingCalls) { + callback = pingCalls.get(ctxId); + } + if (callback == null) { + LOG.log(Level.WARNING, "No callback was registered for reply id={0} ", ctxId); + return; + } + // pass the error to the callback registered by the ping caller + callback.onError(ex); + } + + private static void assertPluginWasRegistered(final String name, final JVppCallback value) { + if (value == null) { + throw new IllegalArgumentException(String.format("Callback for plugin %s is not registered", name)); + } + } + + @Override + public void close() throws Exception { + connection.close(); + } +} diff --git a/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/NativeLibraryLoader.java b/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/NativeLibraryLoader.java new file mode 100644 index 00000000..ce6d1bfc --- /dev/null +++ b/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/NativeLibraryLoader.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.PosixFilePermission; +import java.nio.file.attribute.PosixFilePermissions; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Utility class for loading JNI libraries. + */ +public final class NativeLibraryLoader { + + private static final Logger LOG = Logger.getLogger(NativeLibraryLoader.class.getName()); + + private NativeLibraryLoader() { + throw new UnsupportedOperationException("This utility class cannot be instantiated."); + } + + /** + * Loads JNI library using class loader of the given class. + * + * @param libName name of the library to be loaded + */ + public static void loadLibrary(final String libName, final Class clazz) throws IOException { + java.util.Objects.requireNonNull(libName, "libName should not be null"); + java.util.Objects.requireNonNull(clazz, "clazz should not be null"); + try (final InputStream is = clazz.getResourceAsStream('/' + libName)) { + if (is == null) { + throw new IOException("Failed to open library resource " + libName); + } + loadStream(libName, is); + } + } + + private static void loadStream(final String libName, final InputStream is) throws IOException { + final Set<PosixFilePermission> perms = PosixFilePermissions.fromString("rwxr-x---"); + final Path p = Files.createTempFile(libName, null, PosixFilePermissions.asFileAttribute(perms)); + try { + Files.copy(is, p, StandardCopyOption.REPLACE_EXISTING); + Runtime.getRuntime().load(p.toString()); + } catch (Exception e) { + throw new IOException("Failed to load library " + p, e); + } finally { + try { + Files.deleteIfExists(p); + } catch (IOException e) { + LOG.log(Level.WARNING, String.format("Failed to delete temporary file %s.", p), e); + } + } + } +} diff --git a/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/VppBaseCallException.java b/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/VppBaseCallException.java new file mode 100644 index 00000000..7fc1682b --- /dev/null +++ b/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/VppBaseCallException.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp; + + +/** + * Base exception representing failed operation of JVpp request call + */ +public abstract class VppBaseCallException extends Exception { + private final String methodName; + private final int errorCode; + + /** + * Constructs an VppCallbackException with the specified api method name and error code. + * + * @param methodName name of a method, which invocation or execution failed + * @param errorCode negative error code value associated with this failure + * @throws NullPointerException if apiMethodName is null + */ + public VppBaseCallException(final String methodName, final int errorCode) { + super(String.format("vppApi.%s failed with error code: %d", methodName, errorCode)); + this.methodName = java.util.Objects.requireNonNull(methodName, "apiMethodName is null!"); + this.errorCode = errorCode; + if(errorCode >= 0) { + throw new IllegalArgumentException("Error code must be < 0. Was " + errorCode + + " for " + methodName ); + } + } + + /** + * Constructs an VppCallbackException with the specified api method name, error description and error code. + * + * @param methodName name of a method, which invocation or execution failed + * @param message description of error reason + * @param errorCode negative error code value associated with this failure + * @throws NullPointerException if apiMethodName is null + */ + public VppBaseCallException(final String methodName, final String message, final int errorCode) { + super(String.format("vppApi.%s failed: %s (error code: %d)", methodName,message, errorCode)); + this.methodName = java.util.Objects.requireNonNull(methodName, "apiMethodName is null!"); + this.errorCode = errorCode; + if(errorCode >= 0) { + throw new IllegalArgumentException("Error code must be < 0. Was " + errorCode + + " for " + methodName ); + } + } + + /** + * Returns name of a method, which invocation failed. + * + * @return method name + */ + public String getMethodName() { + return methodName; + } + + /** + * Returns the error code associated with this failure. + * + * @return a negative integer error code + */ + public int getErrorCode() { + return errorCode; + } +} diff --git a/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/VppCallbackException.java b/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/VppCallbackException.java new file mode 100644 index 00000000..adcc5d26 --- /dev/null +++ b/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/VppCallbackException.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp; + +/** + * Callback Exception representing failed operation of JVpp request call + */ +public class VppCallbackException extends VppBaseCallException { + private final int ctxId; + + /** + * Constructs an VppCallbackException with the specified api method name and error code. + * + * @param methodName name of a method, which invocation failed. + * @param message description of error reason + * @param ctxId api request context identifier + * @param errorCode negative error code value associated with this failure + * @throws NullPointerException if apiMethodName is null + */ + public VppCallbackException(final String methodName, final String message, final int ctxId, final int errorCode ){ + super(methodName, message, errorCode); + this.ctxId = ctxId; + } + + /** + * Returns api request context identifier. + * + * @return value of context identifier + */ + public int getCtxId() { + return ctxId; + } + +} diff --git a/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/VppConnection.java b/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/VppConnection.java new file mode 100644 index 00000000..e6fd3bdb --- /dev/null +++ b/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/VppConnection.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp; + +import java.io.IOException; + +/** + * Representation of a management connection to VPP. + */ +public interface VppConnection extends AutoCloseable { + + /** + * Opens VppConnection for communication with VPP. + * + * @throws IOException if connection is not established + */ + void connect() throws IOException; + + /** + * Checks if this instance connection is active. + * + * @throws IllegalStateException if this instance was disconnected. + */ + void checkActive(); + + /** + * Closes Vpp connection. + */ + @Override + void close(); +} diff --git a/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/VppInvocationException.java b/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/VppInvocationException.java new file mode 100644 index 00000000..a7ccb197 --- /dev/null +++ b/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/VppInvocationException.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp; + +/** + * Exception thrown when Vpp jAPI method invocation failed. + */ +public class VppInvocationException extends VppBaseCallException { + /** + * Constructs an VppApiInvocationFailedException with the specified api method name and error code. + * + * @param methodName name of a method, which invocation failed. + * @param errorCode negative error code value associated with this failure + * @throws NullPointerException if apiMethodName is null + */ + public VppInvocationException(final String methodName, final int errorCode) { + super(methodName, errorCode); + } +} diff --git a/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/VppJNIConnection.java b/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/VppJNIConnection.java new file mode 100644 index 00000000..6a414f36 --- /dev/null +++ b/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/VppJNIConnection.java @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp; + +import static io.fd.vpp.jvpp.NativeLibraryLoader.loadLibrary; +import static java.lang.String.format; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * JNI based representation of a management connection to VPP. + */ +public final class VppJNIConnection implements VppConnection { + private static final Logger LOG = Logger.getLogger(VppJNIConnection.class.getName()); + private static final String DEFAULT_SHM_PREFIX = "/vpe-api"; + + static { + final String libName = "libjvpp_registry.so"; + try { + loadLibrary(libName, VppJNIConnection.class); + } catch (IOException e) { + LOG.log(Level.SEVERE, format("Can't find vpp jni library: %s", libName), e); + throw new ExceptionInInitializerError(e); + } + } + + private ConnectionInfo connectionInfo; + + private final String clientName; + private final String shmPrefix; + private volatile boolean disconnected = false; + + /** + * Create VPPJNIConnection instance for client connecting to VPP. + * + * @param clientName client name instance to be used for communication. Single connection per clientName is + * allowed. + */ + public VppJNIConnection(final String clientName) { + this.clientName = Objects.requireNonNull(clientName, "Null clientName"); + this.shmPrefix = DEFAULT_SHM_PREFIX; + } + + public VppJNIConnection(final String clientName, final String shmPrefix) { + this.clientName = Objects.requireNonNull(clientName, "Null clientName"); + this.shmPrefix = Objects.requireNonNull(shmPrefix, "Null shmPrefix"); + } + + /** + * Guarded by VppJNIConnection.class + */ + private static final Map<String, VppJNIConnection> connections = new HashMap<>(); + + /** + * Initiate VPP connection for current instance + * + * Multiple instances are allowed since this class is not a singleton (VPP allows multiple management connections). + * + * However only a single connection per clientName is allowed. + * + * @throws IOException in case the connection could not be established + */ + + @Override + public void connect() throws IOException { + _connect(shmPrefix); + } + + private void _connect(final String shmPrefix) throws IOException { + Objects.requireNonNull(shmPrefix, "Shared memory prefix must be defined"); + + synchronized (VppJNIConnection.class) { + if (connections.containsKey(clientName)) { + throw new IOException("Client " + clientName + " already connected"); + } + + connectionInfo = clientConnect(shmPrefix, clientName); + if (connectionInfo.status != 0) { + throw new IOException("Connection returned error " + connectionInfo.status); + } + connections.put(clientName, this); + } + } + + @Override + public final void checkActive() { + if (disconnected) { + throw new IllegalStateException("Disconnected client " + clientName); + } + } + + @Override + public final synchronized void close() { + if (!disconnected) { + disconnected = true; + try { + clientDisconnect(); + } finally { + synchronized (VppJNIConnection.class) { + connections.remove(clientName); + } + } + } + } + + public ConnectionInfo getConnectionInfo() { + return connectionInfo; + } + + /** + * VPP connection information used by plugins to reuse the connection. + */ + public static final class ConnectionInfo { + public final long queueAddress; + public final int clientIndex; + public final int status; // FIXME throw exception instead + public final int pid; + + public ConnectionInfo(long queueAddress, int clientIndex, int status, int pid) { + this.queueAddress = queueAddress; + this.clientIndex = clientIndex; + this.status = status; + this.pid = pid; + } + } + + private static native ConnectionInfo clientConnect(String shmPrefix, String clientName); + + private static native void clientDisconnect(); + +} diff --git a/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/callback/ControlPingCallback.java b/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/callback/ControlPingCallback.java new file mode 100644 index 00000000..efddfdbb --- /dev/null +++ b/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/callback/ControlPingCallback.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.callback; + +import io.fd.vpp.jvpp.dto.ControlPingReply; + +/** + * Represents callback for control_ping message. + */ +public interface ControlPingCallback extends JVppCallback { + + void onControlPingReply(ControlPingReply reply); + +} + diff --git a/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/callback/JVppCallback.java b/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/callback/JVppCallback.java new file mode 100644 index 00000000..ae02063b --- /dev/null +++ b/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/callback/JVppCallback.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.callback; +import io.fd.vpp.jvpp.VppCallbackException; + +/** + * Base JVppCallback interface + */ +public interface JVppCallback { + /** + * onError callback handler used to report failing operation + * @param ex VppCallbackException object containing details about failing operation + */ + void onError(VppCallbackException ex); +} diff --git a/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/callback/JVppNotificationCallback.java b/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/callback/JVppNotificationCallback.java new file mode 100644 index 00000000..8ab0cb21 --- /dev/null +++ b/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/callback/JVppNotificationCallback.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.callback; + +/** +* Notification callback +*/ +public interface JVppNotificationCallback { + +} diff --git a/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/coverity/SuppressFBWarnings.java b/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/coverity/SuppressFBWarnings.java new file mode 100644 index 00000000..1e780bb4 --- /dev/null +++ b/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/coverity/SuppressFBWarnings.java @@ -0,0 +1,40 @@ +/* + * 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. + */ + +package io.fd.vpp.jvpp.coverity; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Used to suppress FindBugs warnings found by Coverity. <br> + * We don't want extra dependency, so we define our own annotation version. + * + * @see <a href="https://sourceforge.net/p/findbugs/feature-requests/298/#5e88"/>Findbugs sourceforge</a> + */ +@Retention(RetentionPolicy.CLASS) +public @interface SuppressFBWarnings { + /** + * The set of FindBugs warnings that are to be suppressed in annotated element. The value can be a bug category, + * kind or pattern. + */ + String[] value() default {}; + + /** + * Optional documentation of the reason why the warning is suppressed + */ + String justification() default ""; +} diff --git a/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/dto/ControlPing.java b/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/dto/ControlPing.java new file mode 100644 index 00000000..984e1674 --- /dev/null +++ b/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/dto/ControlPing.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.dto; + +import io.fd.vpp.jvpp.JVpp; +import io.fd.vpp.jvpp.VppInvocationException; + +/** + * Represents request DTO for control_ping message. + */ +public final class ControlPing implements JVppRequest { + + @Override + public int send(final JVpp jvpp) throws VppInvocationException { + return jvpp.controlPing(this); + } + +} + + diff --git a/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/dto/ControlPingReply.java b/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/dto/ControlPingReply.java new file mode 100644 index 00000000..61e4d0e4 --- /dev/null +++ b/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/dto/ControlPingReply.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.dto; + +import java.util.Objects; + +/** + * Represents reply DTO for control_ping message. + */ +public final class ControlPingReply implements JVppReply<ControlPing> { + + public int context; + public int clientIndex; + public int vpePid; + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final ControlPingReply that = (ControlPingReply) o; + return context == that.context && + clientIndex == that.clientIndex && + vpePid == that.vpePid; + } + + @Override + public int hashCode() { + return Objects.hash(context, clientIndex, vpePid); + } + + @Override + public String toString() { + return "ControlPingReply{" + + "context=" + context + + ", clientIndex=" + clientIndex + + ", vpePid=" + vpePid + + '}'; + } +} + diff --git a/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/dto/JVppDump.java b/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/dto/JVppDump.java new file mode 100644 index 00000000..60b98984 --- /dev/null +++ b/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/dto/JVppDump.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.dto; + +/** +* Base interface for all dump requests +*/ +public interface JVppDump extends JVppRequest { + +} diff --git a/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/dto/JVppNotification.java b/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/dto/JVppNotification.java new file mode 100644 index 00000000..5554f501 --- /dev/null +++ b/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/dto/JVppNotification.java @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.dto; + +/** +* Base interface for all notification DTOs +*/ +public interface JVppNotification { +} diff --git a/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/dto/JVppReply.java b/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/dto/JVppReply.java new file mode 100644 index 00000000..73f512d4 --- /dev/null +++ b/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/dto/JVppReply.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.dto; + +/** +* Base interface for all reply DTOs +*/ +public interface JVppReply<REQ extends JVppRequest> { + +} diff --git a/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/dto/JVppReplyDump.java b/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/dto/JVppReplyDump.java new file mode 100644 index 00000000..15111395 --- /dev/null +++ b/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/dto/JVppReplyDump.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.dto; + +/** +* Base interface for all dump replies +*/ +public interface JVppReplyDump<REQ extends JVppRequest, RESP extends JVppReply<REQ>> + extends JVppReply<REQ> { + +} diff --git a/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/dto/JVppRequest.java b/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/dto/JVppRequest.java new file mode 100644 index 00000000..9b301da2 --- /dev/null +++ b/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/dto/JVppRequest.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.dto; + +import io.fd.vpp.jvpp.JVpp; +import io.fd.vpp.jvpp.VppInvocationException; + +/** +* Base interface for all request DTOs +*/ +public interface JVppRequest { + + /** + * Invoke current operation asynchronously on VPP + * + * @return context id of this request. Can be used to track incoming response + */ + int send(JVpp jvpp) throws VppInvocationException; + +} diff --git a/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/future/AbstractFutureJVppInvoker.java b/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/future/AbstractFutureJVppInvoker.java new file mode 100644 index 00000000..e7df528a --- /dev/null +++ b/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/future/AbstractFutureJVppInvoker.java @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.future; + + +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import io.fd.vpp.jvpp.JVpp; +import io.fd.vpp.jvpp.JVppRegistry; +import io.fd.vpp.jvpp.VppInvocationException; +import io.fd.vpp.jvpp.dto.JVppDump; +import io.fd.vpp.jvpp.dto.JVppReply; +import io.fd.vpp.jvpp.dto.JVppReplyDump; +import io.fd.vpp.jvpp.dto.JVppRequest; + +/** + * Future facade on top of JVpp + */ +public abstract class AbstractFutureJVppInvoker implements FutureJVppInvoker { + + private final JVpp jvpp; + private final JVppRegistry registry; + + /** + * Guarded by self + */ + private final Map<Integer, CompletableFuture<? extends JVppReply<?>>> requests; + + protected AbstractFutureJVppInvoker(final JVpp jvpp, final JVppRegistry registry, + final Map<Integer, CompletableFuture<? extends JVppReply<?>>> requestMap) { + this.jvpp = Objects.requireNonNull(jvpp, "jvpp should not be null"); + this.registry = Objects.requireNonNull(registry, "registry should not be null"); + // Request map represents the shared state between this facade and it's callback + // where facade puts futures in and callback completes + removes them + this.requests = Objects.requireNonNull(requestMap, "Null requestMap"); + } + + protected final Map<Integer, CompletableFuture<? extends JVppReply<?>>> getRequests() { + synchronized (requests) { + return requests; + } + } + + // TODO use Optional in Future, java8 + + @Override + @SuppressWarnings("unchecked") + public <REQ extends JVppRequest, REPLY extends JVppReply<REQ>> CompletionStage<REPLY> send(REQ req) { + synchronized(requests) { + try { + final CompletableFuture<REPLY> replyCompletableFuture; + final int contextId = jvpp.send(req); + + if(req instanceof JVppDump) { + throw new IllegalArgumentException("Send with empty reply dump has to be used in case of dump calls"); + } + replyCompletableFuture = new CompletableFuture<>(); + requests.put(contextId, replyCompletableFuture); + + // TODO in case of timeouts/missing replies, requests from the map are not removed + // consider adding cancel method, that would remove requests from the map and cancel + // associated replyCompletableFuture + + return replyCompletableFuture; + } catch (VppInvocationException ex) { + final CompletableFuture<REPLY> replyCompletableFuture = new CompletableFuture<>(); + replyCompletableFuture.completeExceptionally(ex); + return replyCompletableFuture; + } + } + } + + @Override + @SuppressWarnings("unchecked") + public <REQ extends JVppRequest, REPLY extends JVppReply<REQ>, DUMP extends JVppReplyDump<REQ, REPLY>> CompletionStage<DUMP> send( + REQ req, DUMP emptyReplyDump) { + synchronized(requests) { + try { + final CompletableDumpFuture<DUMP> replyCompletableFuture; + final int contextId = jvpp.send(req); + + if(!(req instanceof JVppDump)) { + throw new IllegalArgumentException("Send without empty reply dump has to be used in case of regular calls"); + } + replyCompletableFuture = new CompletableDumpFuture<>(contextId, emptyReplyDump); + + requests.put(contextId, replyCompletableFuture); + requests.put(registry.controlPing(jvpp.getClass()), replyCompletableFuture); + + // TODO in case of timeouts/missing replies, requests from the map are not removed + // consider adding cancel method, that would remove requests from the map and cancel + // associated replyCompletableFuture + + return replyCompletableFuture; + } catch (VppInvocationException ex) { + final CompletableFuture<DUMP> replyCompletableFuture = new CompletableFuture<>(); + replyCompletableFuture.completeExceptionally(ex); + return replyCompletableFuture; + } + } + } + + public static final class CompletableDumpFuture<T extends JVppReplyDump<?, ?>> extends CompletableFuture<T> { + private final T replyDump; + private final int contextId; + + public CompletableDumpFuture(final int contextId, final T emptyDump) { + this.contextId = contextId; + this.replyDump = emptyDump; + } + + public int getContextId() { + return contextId; + } + + public T getReplyDump() { + return replyDump; + } + } + + @Override + public void close() throws Exception { + jvpp.close(); + } +} diff --git a/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/future/FutureJVppInvoker.java b/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/future/FutureJVppInvoker.java new file mode 100644 index 00000000..7a48e418 --- /dev/null +++ b/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/future/FutureJVppInvoker.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.future; + + +import io.fd.vpp.jvpp.dto.JVppReply; +import io.fd.vpp.jvpp.dto.JVppReplyDump; +import io.fd.vpp.jvpp.dto.JVppRequest; + +import java.util.concurrent.CompletionStage; +import io.fd.vpp.jvpp.notification.NotificationRegistryProvider; + +/** +* Future facade on top of JVpp +*/ +public interface FutureJVppInvoker extends NotificationRegistryProvider, AutoCloseable { + + /** + * Invoke asynchronous operation on VPP + * + * @return CompletionStage with future result of an async VPP call + * @throws io.fd.vpp.jvpp.VppInvocationException when send request failed with details + */ + <REQ extends JVppRequest, REPLY extends JVppReply<REQ>> CompletionStage<REPLY> send(REQ req); + + + /** + * Invoke asynchronous dump operation on VPP + * + * @return CompletionStage with aggregated future result of an async VPP dump call + * @throws io.fd.vpp.jvpp.VppInvocationException when send request failed with details + */ + <REQ extends JVppRequest, REPLY extends JVppReply<REQ>, DUMP extends JVppReplyDump<REQ, REPLY>> CompletionStage<DUMP> send( + REQ req, DUMP emptyReplyDump); +} diff --git a/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/notification/NotificationRegistry.java b/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/notification/NotificationRegistry.java new file mode 100644 index 00000000..3c72ff79 --- /dev/null +++ b/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/notification/NotificationRegistry.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.notification; + +/** + * Base registry for notification callbacks. + */ +public interface NotificationRegistry extends AutoCloseable { + + void close(); +} diff --git a/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/notification/NotificationRegistryProvider.java b/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/notification/NotificationRegistryProvider.java new file mode 100644 index 00000000..4a6e06b7 --- /dev/null +++ b/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/notification/NotificationRegistryProvider.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.notification; + +/** + * Provides notification registry + */ +public interface NotificationRegistryProvider { + + /** + * Get current notification registry instance + */ + NotificationRegistry getNotificationRegistry(); +} diff --git a/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/test/ConnectionTest.java b/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/test/ConnectionTest.java new file mode 100644 index 00000000..27b4d29f --- /dev/null +++ b/src/vpp-api/java/jvpp-registry/io/fd/vpp/jvpp/test/ConnectionTest.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.test; + +import io.fd.vpp.jvpp.JVppRegistry; +import io.fd.vpp.jvpp.JVppRegistryImpl; + +/** + * Run using: + * sudo java -cp build-vpp-native/vpp-api/java/jvpp-registry-16.09.jar io.fd.vpp.jvpp.test.ConnectionTest + */ +public class ConnectionTest { + + private static void testConnect() throws Exception { + System.out.println("Testing JNI connection with JVppRegistry"); + final JVppRegistry registry = new JVppRegistryImpl("ConnectionTest"); + try { + System.out.println("Successfully connected to vpp"); + Thread.sleep(5000); + System.out.println("Disconnecting..."); + Thread.sleep(1000); + } finally { + registry.close(); + } + } + + public static void main(String[] args) throws Exception { + testConnect(); + } +} diff --git a/src/vpp-api/java/jvpp-registry/jvpp_registry.c b/src/vpp-api/java/jvpp-registry/jvpp_registry.c new file mode 100644 index 00000000..c90822d7 --- /dev/null +++ b/src/vpp-api/java/jvpp-registry/jvpp_registry.c @@ -0,0 +1,402 @@ +/* + * Copyright (c) 2016 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. + */ +#define _GNU_SOURCE /* for strcasestr(3) */ +#include <vnet/vnet.h> + +#define vl_api_version(n,v) static u32 vpe_api_version = (v); +#include <vpp/api/vpe.api.h> +#undef vl_api_version + + +#include <jni.h> +#include <jvpp-common/jvpp_common.h> +#include "io_fd_vpp_jvpp_VppJNIConnection.h" +#include "io_fd_vpp_jvpp_JVppRegistryImpl.h" + +#include <vpp/api/vpe_msg_enum.h> +#define vl_typedefs /* define message structures */ +#include <vpp/api/vpe_all_api_h.h> +#undef vl_typedefs + +#define vl_endianfun +#include <vpp/api/vpe_all_api_h.h> +#undef vl_endianfun + +/* instantiate all the print functions we know about */ +#define vl_print(handle, ...) +#define vl_printfun +#include <vpp/api/vpe_all_api_h.h> +#undef vl_printfun + +vlib_main_t vlib_global_main; +vlib_main_t **vlib_mains; + +/* + * The Java runtime isn't compile w/ -fstack-protector, + * so we have to supply missing external references for the + * regular vpp libraries. + */ +void __stack_chk_guard(void) __attribute__((weak)); +void __stack_chk_guard(void) { +} + +#define CONTROL_PING_MESSAGE "control_ping" +#define CONTROL_PING_REPLY_MESSAGE "control_ping_reply" + +typedef struct { + /* UThread attachment */ + volatile u32 control_ping_result_ready; + volatile i32 control_ping_retval; + + /* Control ping callback */ + jobject registryObject; + jclass registryClass; + jclass controlPingReplyClass; + jclass callbackExceptionClass; + int control_ping_msg_id; + int control_ping_reply_msg_id; + + /* Thread cleanup */ + pthread_key_t cleanup_rx_thread_key; + + /* Connected indication */ + volatile u8 is_connected; + u32 vpe_pid; +} jvpp_registry_main_t; + +jvpp_registry_main_t jvpp_registry_main __attribute__((aligned (64))); + +void vl_client_add_api_signatures(vl_api_memclnt_create_t *mp) { + /* + * Send the main API signature in slot 0. This bit of code must + * match the checks in ../vpe/api/api.c: vl_msg_api_version_check(). + */ + mp->api_versions[0] = clib_host_to_net_u32(vpe_api_version); +} + +/* cleanup handler for RX thread */ +static_always_inline void cleanup_rx_thread(void *arg) { + jvpp_main_t * jm = &jvpp_main; + jvpp_registry_main_t * rm = &jvpp_registry_main; + + vppjni_lock(jm, 99); + + int getEnvStat = (*jm->jvm)->GetEnv(jm->jvm, (void **) &(jm->jenv), + JNI_VERSION_1_8); + if (getEnvStat == JNI_EVERSION) { + clib_warning("Unsupported JNI version\n"); + rm->control_ping_retval = VNET_API_ERROR_UNSUPPORTED_JNI_VERSION; + goto out; + } else if (getEnvStat != JNI_EDETACHED) { + (*jm->jvm)->DetachCurrentThread(jm->jvm); + } + out: vppjni_unlock(jm); +} + +static void vl_api_control_ping_reply_t_handler( + vl_api_control_ping_reply_t * mp) { + jvpp_main_t * jm = &jvpp_main; + jvpp_registry_main_t * rm = &jvpp_registry_main; + char was_thread_connected = 0; + + // attach to java thread if not attached + int getEnvStat = (*jm->jvm)->GetEnv(jm->jvm, (void **) &(jm->jenv), + JNI_VERSION_1_8); + if (getEnvStat == JNI_EDETACHED) { + if ((*jm->jvm)->AttachCurrentThread(jm->jvm, (void **) &(jm->jenv), + NULL) != 0) { + clib_warning("Failed to attach thread\n"); + rm->control_ping_retval = + VNET_API_ERROR_FAILED_TO_ATTACH_TO_JAVA_THREAD; + goto out; + } + + // workaround as we can't use pthread_cleanup_push + pthread_key_create(&rm->cleanup_rx_thread_key, cleanup_rx_thread); + // destructor is only called if the value of key is non null + pthread_setspecific(rm->cleanup_rx_thread_key, (void *) 1); + was_thread_connected = 1; + } else if (getEnvStat == JNI_EVERSION) { + clib_warning("Unsupported JNI version\n"); + rm->control_ping_retval = VNET_API_ERROR_UNSUPPORTED_JNI_VERSION; + goto out; + } + + if (was_thread_connected == 0) { + JNIEnv *env = jm->jenv; + if (mp->retval < 0) { + call_on_error("controlPing", mp->context, mp->retval, + rm->registryClass, rm->registryObject, + rm->callbackExceptionClass); + } else { + jmethodID constructor = (*env)->GetMethodID(env, + rm->controlPingReplyClass, "<init>", "()V"); + jmethodID callbackMethod = (*env)->GetMethodID(env, + rm->registryClass, "onControlPingReply", + "(Lio/fd/vpp/jvpp/dto/ControlPingReply;)V"); + + jobject dto = (*env)->NewObject(env, rm->controlPingReplyClass, + constructor); + + jfieldID contextFieldId = (*env)->GetFieldID(env, + rm->controlPingReplyClass, "context", "I"); + (*env)->SetIntField(env, dto, contextFieldId, + clib_net_to_host_u32(mp->context)); + + jfieldID clientIndexFieldId = (*env)->GetFieldID(env, + rm->controlPingReplyClass, "clientIndex", "I"); + (*env)->SetIntField(env, dto, clientIndexFieldId, + clib_net_to_host_u32(mp->client_index)); + + jfieldID vpePidFieldId = (*env)->GetFieldID(env, + rm->controlPingReplyClass, "vpePid", "I"); + (*env)->SetIntField(env, dto, vpePidFieldId, + clib_net_to_host_u32(mp->vpe_pid)); + + (*env)->CallVoidMethod(env, rm->registryObject, callbackMethod, + dto); + (*env)->DeleteLocalRef(env, dto); + } + } + + out: rm->vpe_pid = clib_net_to_host_u32(mp->vpe_pid); + rm->control_ping_result_ready = 1; +} + +static int find_ping_id() { + int rv = 0; + jvpp_main_t * jm = &jvpp_main; + jvpp_registry_main_t * rm = &jvpp_registry_main; + api_main_t *am = &api_main; + hash_pair_t *hp; + jm->messages_hash = am->msg_index_by_name_and_crc; + + rm->control_ping_msg_id = -1; + rm->control_ping_reply_msg_id = -1; + + hash_foreach_pair (hp, jm->messages_hash, + ({ + char *key = (char *)hp->key; // key format: name_crc + int msg_name_len = strlen(key) - 9; // ignore crc + if (strlen(CONTROL_PING_MESSAGE) == msg_name_len && + strncmp(CONTROL_PING_MESSAGE, (char *)hp->key, msg_name_len) == 0) { + rm->control_ping_msg_id = (u32)hp->value[0]; + } + if (strlen(CONTROL_PING_REPLY_MESSAGE) == msg_name_len && + strncmp(CONTROL_PING_REPLY_MESSAGE, (char *)hp->key, msg_name_len) == 0) { + rm->control_ping_reply_msg_id = (u32)hp->value[0]; + } + })); + if (rm->control_ping_msg_id == -1) { + clib_warning("failed to find id for %s", CONTROL_PING_MESSAGE); + rv = -1; + } + if (rm->control_ping_reply_msg_id == -1) { + clib_warning("failed to find id for %s", CONTROL_PING_REPLY_MESSAGE); + rv = -1; + } + return rv; +} + +static int send_initial_control_ping() { + f64 timeout; + clib_time_t clib_time; + vl_api_control_ping_t * mp; + jvpp_main_t * jm = &jvpp_main; + jvpp_registry_main_t * rm = &jvpp_registry_main; + + clib_time_init(&clib_time); + + rm->control_ping_result_ready = 0; + mp = vl_msg_api_alloc(sizeof(*mp)); + memset(mp, 0, sizeof(*mp)); + mp->_vl_msg_id = ntohs(rm->control_ping_msg_id); + mp->client_index = jm->my_client_index; + + // send message: + vl_msg_api_send_shmem(jm->vl_input_queue, (u8 *) &mp); + + // wait for results: Current time + 10 seconds is the timeout + timeout = clib_time_now(&clib_time) + 10.0; + int rv = VNET_API_ERROR_RESPONSE_NOT_READY; + while (clib_time_now(&clib_time) < timeout) { + if (rm->control_ping_result_ready == 1) { + rv = rm->control_ping_retval; + break; + } + } + + if (rv != 0) { + vl_msg_api_clean_handlers(rm->control_ping_reply_msg_id); + clib_warning("first control ping failed: %d", rv); + } + return rv; +} + +static int connect_to_vpe(char *shm_prefix, char *name) { + jvpp_main_t * jm = &jvpp_main; + api_main_t * am = &api_main; + jvpp_registry_main_t * rm = &jvpp_registry_main; + + if (vl_client_connect_to_vlib(shm_prefix, name, 32) < 0) + return -1; + jm->my_client_index = am->my_client_index; + + jm->vl_input_queue = am->shmem_hdr->vl_input_queue; + + if (find_ping_id() < 0) + return -1; + + vl_msg_api_set_handlers(rm->control_ping_reply_msg_id, CONTROL_PING_REPLY_MESSAGE, + vl_api_control_ping_reply_t_handler, vl_noop_handler, + vl_api_control_ping_reply_t_endian, + vl_api_control_ping_reply_t_print, + sizeof(vl_api_control_ping_reply_t), 1); + + return send_initial_control_ping(); +} + +JNIEXPORT jobject JNICALL Java_io_fd_vpp_jvpp_VppJNIConnection_clientConnect( + JNIEnv *env, jclass obj, jstring shmPrefix, jstring clientName) { + /* + * TODO introducing memory prefix as variable can be used in hc2vpp + * to be able to run without root privileges + * https://jira.fd.io/browse/HC2VPP-176 + */ + int rv; + const char *client_name; + const char *shm_prefix; + void vl_msg_reply_handler_hookup(void); + jvpp_main_t * jm = &jvpp_main; + jvpp_registry_main_t * rm = &jvpp_registry_main; + + jclass connectionInfoClass = (*env)->FindClass(env, + "io/fd/vpp/jvpp/VppJNIConnection$ConnectionInfo"); + jmethodID connectionInfoConstructor = (*env)->GetMethodID(env, + connectionInfoClass, "<init>", "(JIII)V"); + + if (rm->is_connected) { + return (*env)->NewObject(env, connectionInfoClass, + connectionInfoConstructor, 0, 0, + VNET_API_ERROR_ALREADY_CONNECTED, 0); + } + + client_name = (*env)->GetStringUTFChars(env, clientName, 0); + shm_prefix = (*env)->GetStringUTFChars(env, shmPrefix, 0); + + if (!client_name) { + return (*env)->NewObject(env, connectionInfoClass, + connectionInfoConstructor, 0, 0, VNET_API_ERROR_INVALID_VALUE, 0, shmPrefix); + } + + if (!shm_prefix) { + return (*env)->NewObject(env, connectionInfoClass, + connectionInfoConstructor, 0, 0, VNET_API_ERROR_INVALID_VALUE, 0, shmPrefix); + } + + rv = connect_to_vpe((char *) shm_prefix, (char *) client_name); + + if (rv < 0) + clib_warning("connection failed, rv %d", rv); + + (*env)->ReleaseStringUTFChars(env, clientName, client_name); + (*env)->ReleaseStringUTFChars(env, shmPrefix, shm_prefix); + + return (*env)->NewObject(env, connectionInfoClass, + connectionInfoConstructor, (jlong) pointer_to_uword (jm->vl_input_queue), + (jint) jm->my_client_index, (jint) rv, (jint) rm->vpe_pid); +} + +JNIEXPORT jint JNICALL Java_io_fd_vpp_jvpp_JVppRegistryImpl_controlPing0( + JNIEnv *env, jobject regstryObject) { + jvpp_main_t * jm = &jvpp_main; + vl_api_control_ping_t * mp; + u32 my_context_id = vppjni_get_context_id(&jvpp_main); + jvpp_registry_main_t * rm = &jvpp_registry_main; + + if (rm->registryObject == 0) { + rm->registryObject = (*env)->NewGlobalRef(env, regstryObject); + } + if (rm->registryClass == 0) { + rm->registryClass = (jclass) (*env)->NewGlobalRef(env, + (*env)->GetObjectClass(env, regstryObject)); + } + + mp = vl_msg_api_alloc(sizeof(*mp)); + memset(mp, 0, sizeof(*mp)); + mp->_vl_msg_id = ntohs(rm->control_ping_msg_id); + mp->client_index = jm->my_client_index; + mp->context = clib_host_to_net_u32(my_context_id); + + // send message: + vl_msg_api_send_shmem(jm->vl_input_queue, (u8 *) &mp); + return my_context_id; +} + +JNIEXPORT void JNICALL Java_io_fd_vpp_jvpp_VppJNIConnection_clientDisconnect( + JNIEnv *env, jclass clazz) { + jvpp_registry_main_t * rm = &jvpp_registry_main; + rm->is_connected = 0; // TODO make thread safe + vl_client_disconnect_from_vlib(); + + // cleanup: + if (rm->registryObject) { + (*env)->DeleteGlobalRef(env, rm->registryObject); + rm->registryObject = 0; + } + if (rm->registryClass) { + (*env)->DeleteGlobalRef(env, rm->registryClass); + rm->registryClass = 0; + } +} + +jint JNI_OnLoad(JavaVM *vm, void *reserved) { + jvpp_main_t * jm = &jvpp_main; + jvpp_registry_main_t * rm = &jvpp_registry_main; + JNIEnv* env; + + if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_8) != JNI_OK) { + return JNI_EVERSION; + } + + rm->controlPingReplyClass = (jclass) (*env)->NewGlobalRef(env, + (*env)->FindClass(env, "io/fd/vpp/jvpp/dto/ControlPingReply")); + if ((*env)->ExceptionCheck(env)) { + (*env)->ExceptionDescribe(env); + clib_warning("Failed to cache class references\n"); + return JNI_ERR; + } + + rm->callbackExceptionClass = (jclass) (*env)->NewGlobalRef(env, + (*env)->FindClass(env, "io/fd/vpp/jvpp/VppCallbackException")); + if ((*env)->ExceptionCheck(env)) { + (*env)->ExceptionDescribe(env); + return JNI_ERR; + } + + jm->jvm = vm; + return JNI_VERSION_1_8; +} + +void JNI_OnUnload(JavaVM *vm, void *reserved) { + jvpp_main_t * jm = &jvpp_main; + JNIEnv* env; + if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_8) != JNI_OK) { + return; + } + + jm->jenv = NULL; + jm->jvm = NULL; +} diff --git a/src/vpp-api/java/jvpp/gen/jvpp_gen.py b/src/vpp-api/java/jvpp/gen/jvpp_gen.py new file mode 100755 index 00000000..6648a4f7 --- /dev/null +++ b/src/vpp-api/java/jvpp/gen/jvpp_gen.py @@ -0,0 +1,189 @@ +#!/usr/bin/env python +# +# Copyright (c) 2016 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. +# + +import argparse +import importlib +import sys +import os +import json + +from jvppgen import types_gen +from jvppgen import callback_gen +from jvppgen import notification_gen +from jvppgen import dto_gen +from jvppgen import jvpp_callback_facade_gen +from jvppgen import jvpp_future_facade_gen +from jvppgen import jvpp_impl_gen +from jvppgen import jvpp_c_gen +from jvppgen import util + +blacklist = [ "memclnt.api", "flowprobe.api" ] + +# Invocation: +# ~/Projects/vpp/vpp-api/jvpp/gen$ mkdir -p java/io/fd/vpp/jvpp && cd java/io/fd/vpp/jvpp +# ~/Projects/vpp/vpp-api/jvpp/gen/java/io/fd/vpp/jvpp$ ../../../../jvpp_gen.py -idefs_api_vpp_papi.py +# +# Compilation: +# ~/Projects/vpp/vpp-api/jvpp/gen/java/io/fd/vpp/jvpp$ javac *.java dto/*.java callback/*.java +# +# where +# defs_api_vpp_papi.py - vpe.api in python format (generated by vppapigen) + +parser = argparse.ArgumentParser(description='VPP Java API generator') +parser.add_argument('-i', action="store", dest="inputfiles", nargs='+') +parser.add_argument('--plugin_name', action="store", dest="plugin_name") +parser.add_argument('--root_dir', action="store", dest="root_dir") +args = parser.parse_args() + +sys.path.append(".") +cwd = os.getcwd() + +print "Generating Java API for %s" % args.inputfiles +print "inputfiles %s" % args.inputfiles +plugin_name = args.plugin_name +print "plugin_name %s" % plugin_name + +cfg = {} + +base_package = 'io.fd.vpp.jvpp' +plugin_package = base_package + '.' + plugin_name +root_dir = os.path.abspath(args.root_dir) +print "root_dir %s" % root_dir +work_dir = root_dir + "/target/" + plugin_package.replace(".","/") + +try: + os.makedirs(work_dir) +except OSError: + if not os.path.isdir(work_dir): + raise + +os.chdir(work_dir) + +for inputfile in args.inputfiles: + if any(substring in inputfile for substring in blacklist): + print "WARNING: Imput file %s blacklisted" % inputfile + continue + _cfg = json.load(open(cwd + "/" + inputfile, 'r')) + if 'types' in cfg: + cfg['types'].extend(_cfg['types']) + else: + cfg['types'] = _cfg['types'] + if 'messages' in cfg: + cfg['messages'].extend(_cfg['messages']) + else: + cfg['messages'] = _cfg['messages'] + + +def is_request_field(field_name): + return field_name not in {'_vl_msg_id', 'client_index', 'context'} + + +def is_response_field(field_name): + return field_name not in {'_vl_msg_id'} + + +def get_args(t, filter): + arg_names = [] + arg_types = [] + for i in t: + if is_crc(i): + continue + if not filter(i[1]): + continue + arg_types.append(i[0]) + arg_names.append(i[1]) + return arg_types, arg_names + + +def get_types(t, filter): + types_list = [] + lengths_list = [] + crc = None + for i in t: + if is_crc(i): + crc = ('crc', i['crc'][2:]) + continue + if not filter(i[1]): + continue + if len(i) is 3: # array type + types_list.append(i[0] + '[]') + lengths_list.append((i[2], False)) + elif len(i) is 4: # variable length array type + types_list.append(i[0] + '[]') + lengths_list.append((i[3], True)) + else: # primitive type + types_list.append(i[0]) + lengths_list.append((0, False)) + return types_list, lengths_list, crc + + +def is_crc(arg): + """ Check whether the argument inside message definition is just crc """ + return 'crc' in arg + + +def get_definitions(defs): + # Pass 1 + func_list = [] + func_name = {} + for a in defs: + java_name = util.underscore_to_camelcase(a[0]) + + # For replies include all the arguments except message_id + if util.is_reply(java_name): + types, lengths, crc = get_types(a[1:], is_response_field) + args = get_args(a[1:], is_response_field) + func_name[a[0]] = dict( + [('name', a[0]), ('java_name', java_name), + ('args', args[1]), ('arg_types', args[0]), + ('types', types), ('lengths', lengths), crc]) + # For requests skip message_id, client_id and context + else: + types, lengths, crc = get_types(a[1:], is_request_field) + args = get_args(a[1:], is_request_field) + func_name[a[0]] = dict( + [('name', a[0]), ('java_name', java_name), + ('args', args[1]), ('arg_types', args[0]), + ('types', types), ('lengths', lengths), crc]) + + # Indexed by name + func_list.append(func_name[a[0]]) + return func_list, func_name + + +types_package = 'types' +dto_package = 'dto' +callback_package = 'callback' +notification_package = 'notification' +future_package = 'future' +# TODO find better package name +callback_facade_package = 'callfacade' + +types_list, types_name = get_definitions(cfg['types']) + +types_gen.generate_types(types_list, plugin_package, types_package, args.inputfiles) + +func_list, func_name = get_definitions(cfg['messages']) + +dto_gen.generate_dtos(func_list, base_package, plugin_package, plugin_name.title(), dto_package, args.inputfiles) +jvpp_impl_gen.generate_jvpp(func_list, base_package, plugin_package, plugin_name, dto_package, args.inputfiles) +callback_gen.generate_callbacks(func_list, base_package, plugin_package, plugin_name.title(), callback_package, dto_package, args.inputfiles) +notification_gen.generate_notification_registry(func_list, base_package, plugin_package, plugin_name.title(), notification_package, callback_package, dto_package, args.inputfiles) +jvpp_c_gen.generate_jvpp(func_list, plugin_name, args.inputfiles, root_dir) +jvpp_future_facade_gen.generate_jvpp(func_list, base_package, plugin_package, plugin_name.title(), dto_package, callback_package, notification_package, future_package, args.inputfiles) +jvpp_callback_facade_gen.generate_jvpp(func_list, base_package, plugin_package, plugin_name.title(), dto_package, callback_package, notification_package, callback_facade_package, args.inputfiles) + +print "Java API for %s generated successfully" % args.inputfiles diff --git a/src/vpp-api/java/jvpp/gen/jvppgen/__init__.py b/src/vpp-api/java/jvpp/gen/jvppgen/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/vpp-api/java/jvpp/gen/jvppgen/__init__.py diff --git a/src/vpp-api/java/jvpp/gen/jvppgen/callback_gen.py b/src/vpp-api/java/jvpp/gen/jvppgen/callback_gen.py new file mode 100644 index 00000000..b3024b9c --- /dev/null +++ b/src/vpp-api/java/jvpp/gen/jvppgen/callback_gen.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python +# +# Copyright (c) 2016 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. + +import os +import util +from string import Template + +from util import remove_suffix + +callback_suffix = "Callback" + +callback_template = Template(""" +package $plugin_package.$callback_package; + +/** + * <p>Represents callback for plugin's api file message. + * <br>It was generated by callback_gen.py based on $inputfile preparsed data: + * <pre> +$docs + * </pre> + */ +public interface $cls_name extends $base_package.$callback_package.$callback_type { + + $callback_method + +} +""") + +global_callback_template = Template(""" +package $plugin_package.$callback_package; + +/** + * <p>Global aggregated callback interface. + * <br>It was generated by callback_gen.py based on $inputfile + * <br>(python representation of api file generated by vppapigen). + */ +public interface JVpp${plugin_name}GlobalCallback extends $base_package.$callback_package.ControlPingCallback, $callbacks { +} +""") + + +def generate_callbacks(func_list, base_package, plugin_package, plugin_name, callback_package, dto_package, inputfile): + """ Generates callback interfaces """ + print "Generating Callback interfaces" + + if not os.path.exists(callback_package): + os.mkdir(callback_package) + + callbacks = [] + for func in func_list: + + camel_case_name_with_suffix = util.underscore_to_camelcase_upper(func['name']) + + if util.is_ignored(func['name']) or util.is_control_ping(camel_case_name_with_suffix): + continue + if not util.is_reply(camel_case_name_with_suffix) and not util.is_notification(func['name']): + continue + + if util.is_reply(camel_case_name_with_suffix): + camel_case_name = util.remove_reply_suffix(camel_case_name_with_suffix) + callback_type = "JVppCallback" + else: + camel_case_name_with_suffix = util.add_notification_suffix(camel_case_name_with_suffix) + camel_case_name = camel_case_name_with_suffix + callback_type = "JVppNotificationCallback" + + callbacks.append("{0}.{1}.{2}".format(plugin_package, callback_package, camel_case_name + callback_suffix)) + callback_path = os.path.join(callback_package, camel_case_name + callback_suffix + ".java") + callback_file = open(callback_path, 'w') + + reply_type = "%s.%s.%s" % (plugin_package, dto_package, camel_case_name_with_suffix) + method = "void on{0}({1} reply);".format(camel_case_name_with_suffix, reply_type) + callback_file.write( + callback_template.substitute(inputfile=inputfile, + docs=util.api_message_to_javadoc(func), + cls_name=camel_case_name + callback_suffix, + callback_method=method, + base_package=base_package, + plugin_package=plugin_package, + callback_package=callback_package, + callback_type=callback_type)) + callback_file.flush() + callback_file.close() + + callback_file = open(os.path.join(callback_package, "JVpp%sGlobalCallback.java" % plugin_name), 'w') + callback_file.write(global_callback_template.substitute(inputfile=inputfile, + callbacks=", ".join(callbacks), + base_package=base_package, + plugin_package=plugin_package, + plugin_name=plugin_name, + callback_package=callback_package)) + callback_file.flush() + callback_file.close() diff --git a/src/vpp-api/java/jvpp/gen/jvppgen/dto_gen.py b/src/vpp-api/java/jvpp/gen/jvppgen/dto_gen.py new file mode 100644 index 00000000..e831557c --- /dev/null +++ b/src/vpp-api/java/jvpp/gen/jvppgen/dto_gen.py @@ -0,0 +1,310 @@ +#!/usr/bin/env python +# +# Copyright (c) 2016 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. + +import os +from string import Template + +import util + +dto_template = Template(""" +package $plugin_package.$dto_package; + +/** + * <p>This class represents $description. + * <br>It was generated by dto_gen.py based on $inputfile preparsed data: + * <pre> +$docs + * </pre> + */ +public final class $cls_name implements $base_package.$dto_package.$base_type { + +$fields +$methods +} +""") + +field_template = Template(""" public $type $name;\n""") + +send_template = Template(""" @Override + public int send(final $base_package.JVpp jvpp) throws io.fd.vpp.jvpp.VppInvocationException { + return (($plugin_package.JVpp${plugin_name})jvpp).$method_name($args); + }""") + + +def generate_dtos(func_list, base_package, plugin_package, plugin_name, dto_package, inputfile): + """ Generates dto objects in a dedicated package """ + print "Generating DTOs" + + if not os.path.exists(dto_package): + os.mkdir(dto_package) + + for func in func_list: + camel_case_dto_name = util.underscore_to_camelcase_upper(func['name']) + camel_case_method_name = util.underscore_to_camelcase(func['name']) + dto_path = os.path.join(dto_package, camel_case_dto_name + ".java") + + if util.is_ignored(func['name']) or util.is_control_ping(camel_case_dto_name): + continue + + fields = generate_dto_fields(camel_case_dto_name, func) + methods = generate_dto_base_methods(camel_case_dto_name, func) + base_type = "" + + # Generate request/reply or dump/dumpReply even if structure can be used as notification + if not util.is_just_notification(func["name"]): + if util.is_reply(camel_case_dto_name): + description = "reply DTO" + request_dto_name = get_request_name(camel_case_dto_name, func['name']) + if util.is_details(camel_case_dto_name): + # FIXME assumption that dump calls end with "Dump" suffix. Not enforced in vpe.api + base_type += "JVppReply<%s.%s.%s>" % (plugin_package, dto_package, request_dto_name + "Dump") + generate_dump_reply_dto(request_dto_name, base_package, plugin_package, dto_package, + camel_case_dto_name, camel_case_method_name, func) + else: + base_type += "JVppReply<%s.%s.%s>" % (plugin_package, dto_package, request_dto_name) + else: + args = "" if fields is "" else "this" + methods += send_template.substitute(method_name=camel_case_method_name, + base_package=base_package, + plugin_package=plugin_package, + plugin_name=plugin_name, + args=args) + if util.is_dump(camel_case_dto_name): + base_type += "JVppDump" + description = "dump request DTO" + else: + base_type += "JVppRequest" + description = "request DTO" + + write_dto_file(base_package, plugin_package, base_type, camel_case_dto_name, description, dto_package, + dto_path, fields, func, inputfile, methods) + + # for structures that are also used as notifications, generate dedicated notification DTO + if util.is_notification(func["name"]): + base_type = "JVppNotification" + description = "notification DTO" + camel_case_dto_name = util.add_notification_suffix(camel_case_dto_name) + dto_path = os.path.join(dto_package, camel_case_dto_name + ".java") + methods = generate_dto_base_methods(camel_case_dto_name, func) + write_dto_file(base_package, plugin_package, base_type, camel_case_dto_name, description, dto_package, + dto_path, fields, func, inputfile, methods) + + flush_dump_reply_dtos(inputfile) + + +def generate_dto_base_methods(camel_case_dto_name, func): + methods = generate_dto_hash(func) + methods += generate_dto_equals(camel_case_dto_name, func) + methods += generate_dto_tostring(camel_case_dto_name, func) + return methods + + +def generate_dto_fields(camel_case_dto_name, func): + fields = "" + for t in zip(func['types'], func['args']): + # for retval don't generate dto field in Reply + field_name = util.underscore_to_camelcase(t[1]) + if util.is_reply(camel_case_dto_name) and util.is_retval_field(field_name): + continue + fields += field_template.substitute(type=util.jni_2_java_type_mapping[t[0]], + name=field_name) + return fields + + +tostring_field_template = Template(""" \"$field_name=\" + $field_name + ", " +\n""") +tostring_array_field_template = Template(""" \"$field_name=\" + java.util.Arrays.toString($field_name) + ", " +\n""") +tostring_template = Template(""" @Override + public String toString() { + return "$cls_name{" + +$fields_tostring "}"; + }\n\n""") + + +def generate_dto_tostring(camel_case_dto_name, func): + tostring_fields = "" + for t in zip(func['types'], func['args']): + + field_name = util.underscore_to_camelcase(t[1]) + # for retval don't generate dto field in Reply + if util.is_retval_field(field_name): + continue + + # handle array types + if util.is_array(util.jni_2_java_type_mapping[t[0]]): + tostring_fields += tostring_array_field_template.substitute(field_name=field_name) + else: + tostring_fields += tostring_field_template.substitute(field_name=field_name) + + return tostring_template.substitute(cls_name=camel_case_dto_name, + fields_tostring=tostring_fields[:-8]) + +equals_other_template = Template(""" + final $cls_name other = ($cls_name) o; +\n""") +equals_field_template = Template(""" if (!java.util.Objects.equals(this.$field_name, other.$field_name)) { + return false; + }\n""") +equals_array_field_template = Template(""" if (!java.util.Arrays.equals(this.$field_name, other.$field_name)) { + return false; + }\n""") +equals_template = Template(""" @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } +$comparisons + return true; + }\n\n""") + + +def generate_dto_equals(camel_case_dto_name, func): + equals_fields = "" + for t in zip(func['types'], func['args']): + field_name = util.underscore_to_camelcase(t[1]) + # for retval don't generate dto field in Reply + if util.is_retval_field(field_name): + continue + + # handle array types + if util.is_array(util.jni_2_java_type_mapping[t[0]]): + equals_fields += equals_array_field_template.substitute(field_name=field_name) + else: + equals_fields += equals_field_template.substitute(field_name=field_name) + + if equals_fields != "": + equals_fields = equals_other_template.substitute(cls_name=camel_case_dto_name) + equals_fields + + return equals_template.substitute(comparisons=equals_fields) + + +hash_template = Template(""" @Override + @io.fd.vpp.jvpp.coverity.SuppressFBWarnings("UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD") + public int hashCode() { + return java.util.Objects.hash($fields); + }\n\n""") +hash_single_array_type_template = Template(""" @Override + @io.fd.vpp.jvpp.coverity.SuppressFBWarnings("UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD") + public int hashCode() { + return java.util.Arrays.hashCode($fields); + }\n\n""") + + +def generate_dto_hash(func): + hash_fields = "" + + # Special handling for hashCode in case just a single array field is present. Cannot use Objects.equals since the + # array is mistaken for a varargs parameter. Instead use Arrays.hashCode in such case. + if len(func['args']) == 1: + single_type = func['types'][0] + single_type_name = func['args'][0] + if util.is_array(util.jni_2_java_type_mapping[single_type]): + return hash_single_array_type_template.substitute(fields=util.underscore_to_camelcase(single_type_name)) + + for t in zip(func['types'], func['args']): + field_name = util.underscore_to_camelcase(t[1]) + # for retval don't generate dto field in Reply + if util.is_retval_field(field_name): + continue + + hash_fields += field_name + ", " + + return hash_template.substitute(fields=hash_fields[:-2]) + + +def write_dto_file(base_package, plugin_package, base_type, camel_case_dto_name, description, dto_package, dto_path, + fields, func, inputfile, methods): + dto_file = open(dto_path, 'w') + dto_file.write(dto_template.substitute(inputfile=inputfile, + description=description, + docs=util.api_message_to_javadoc(func), + cls_name=camel_case_dto_name, + fields=fields, + methods=methods, + base_package=base_package, + plugin_package=plugin_package, + base_type=base_type, + dto_package=dto_package)) + dto_file.flush() + dto_file.close() + + +dump_dto_suffix = "ReplyDump" +dump_reply_artificial_dtos = {} + + +# Returns request name or special one from unconventional_naming_rep_req map +def get_request_name(camel_case_dto_name, func_name): + return util.underscore_to_camelcase_upper( + util.unconventional_naming_rep_req[func_name]) if func_name in util.unconventional_naming_rep_req \ + else util.remove_reply_suffix(camel_case_dto_name) + + +def flush_dump_reply_dtos(inputfile): + for dump_reply_artificial_dto in dump_reply_artificial_dtos.values(): + dto_path = os.path.join(dump_reply_artificial_dto['dto_package'], + dump_reply_artificial_dto['cls_name'] + ".java") + dto_file = open(dto_path, 'w') + dto_file.write(dto_template.substitute(inputfile=inputfile, + description="dump reply wrapper", + docs=dump_reply_artificial_dto['docs'], + cls_name=dump_reply_artificial_dto['cls_name'], + fields=dump_reply_artificial_dto['fields'], + methods=dump_reply_artificial_dto['methods'], + plugin_package=dump_reply_artificial_dto['plugin_package'], + base_package=dump_reply_artificial_dto['base_package'], + base_type=dump_reply_artificial_dto['base_type'], + dto_package=dump_reply_artificial_dto['dto_package'])) + dto_file.flush() + dto_file.close() + + +def generate_dump_reply_dto(request_dto_name, base_package, plugin_package, dto_package, camel_case_dto_name, + camel_case_method_name, func): + base_type = "JVppReplyDump<%s.%s.%s, %s.%s.%s>" % ( + plugin_package, dto_package, util.remove_reply_suffix(camel_case_dto_name) + "Dump", + plugin_package, dto_package, camel_case_dto_name) + fields = " public java.util.List<%s> %s = new java.util.ArrayList<>();" % (camel_case_dto_name, camel_case_method_name) + cls_name = camel_case_dto_name + dump_dto_suffix + # using artificial type for fields, just to bypass the is_array check in base methods generators + # the type is not really used + artificial_type = 'u8' + + # In case of already existing artificial reply dump DTO, just update it + # Used for sub-dump dtos + if request_dto_name in dump_reply_artificial_dtos.keys(): + dump_reply_artificial_dtos[request_dto_name]['fields'] += '\n' + fields + dump_reply_artificial_dtos[request_dto_name]['field_names'].append(func['name']) + dump_reply_artificial_dtos[request_dto_name]['field_types'].append(artificial_type) + methods = '\n' + generate_dto_base_methods(dump_reply_artificial_dtos[request_dto_name]['cls_name'], + {'args': dump_reply_artificial_dtos[request_dto_name]['field_names'], + 'types': dump_reply_artificial_dtos[request_dto_name]['field_types']}) + dump_reply_artificial_dtos[request_dto_name]['methods'] = methods + else: + methods = '\n' + generate_dto_base_methods(cls_name, {'args': [func['name']], + 'types': [artificial_type]}) + dump_reply_artificial_dtos[request_dto_name] = ({'docs': util.api_message_to_javadoc(func), + 'cls_name': cls_name, + 'fields': fields, + 'field_names': [func['name']], + 'field_types': [artificial_type], + # strip too many newlines at the end of base method block + 'methods': methods, + 'plugin_package': plugin_package, + 'base_package': base_package, + 'base_type': base_type, + 'dto_package': dto_package}) diff --git a/src/vpp-api/java/jvpp/gen/jvppgen/jni_gen.py b/src/vpp-api/java/jvpp/gen/jvppgen/jni_gen.py new file mode 100644 index 00000000..cb0d66e8 --- /dev/null +++ b/src/vpp-api/java/jvpp/gen/jvppgen/jni_gen.py @@ -0,0 +1,303 @@ +#!/usr/bin/env python +# +# Copyright (c) 2016 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. + +from string import Template + +import util + +variable_length_array_value_template = Template("""mp->${length_var_name}""") +variable_length_array_template = Template("""clib_net_to_host_${length_field_type}(${value})""") + +dto_field_id_template = Template(""" + jfieldID ${field_reference_name}FieldId = (*env)->GetFieldID(env, ${class_ref_name}Class, "${field_name}", "${jni_signature}");""") + +default_dto_field_setter_template = Template(""" + (*env)->Set${jni_setter}(env, ${object_name}, ${field_reference_name}FieldId, mp->${c_name}); +""") + +variable_length_array_value_template = Template("""mp->${length_var_name}""") +variable_length_array_template = Template("""clib_net_to_host_${length_field_type}(${value})""") + +u16_dto_field_setter_template = Template(""" + (*env)->Set${jni_setter}(env, ${object_name}, ${field_reference_name}FieldId, clib_net_to_host_u16(mp->${c_name})); +""") + +u32_dto_field_setter_template = Template(""" + (*env)->Set${jni_setter}(env, ${object_name}, ${field_reference_name}FieldId, clib_net_to_host_u32(mp->${c_name})); +""") + +u64_dto_field_setter_template = Template(""" + (*env)->Set${jni_setter}(env, ${object_name}, ${field_reference_name}FieldId, clib_net_to_host_u64(mp->${c_name})); +""") + +u8_array_dto_field_setter_template = Template(""" + jbyteArray ${field_reference_name} = (*env)->NewByteArray(env, ${field_length}); + (*env)->SetByteArrayRegion(env, ${field_reference_name}, 0, ${field_length}, (const jbyte*)mp->${c_name}); + (*env)->SetObjectField(env, ${object_name}, ${field_reference_name}FieldId, ${field_reference_name}); + (*env)->DeleteLocalRef(env, ${field_reference_name}); +""") + +u16_array_dto_field_setter_template = Template(""" + { + jshortArray ${field_reference_name} = (*env)->NewShortArray(env, ${field_length}); + jshort * ${field_reference_name}ArrayElements = (*env)->GetShortArrayElements(env, ${field_reference_name}, NULL); + unsigned int _i; + for (_i = 0; _i < ${field_length}; _i++) { + ${field_reference_name}ArrayElements[_i] = clib_net_to_host_u16(mp->${c_name}[_i]); + } + + (*env)->ReleaseShortArrayElements(env, ${field_reference_name}, ${field_reference_name}ArrayElements, 0); + (*env)->SetObjectField(env, ${object_name}, ${field_reference_name}FieldId, ${field_reference_name}); + (*env)->DeleteLocalRef(env, ${field_reference_name}); + } +""") + +u32_array_dto_field_setter_template = Template(""" + { + jintArray ${field_reference_name} = (*env)->NewIntArray(env, ${field_length}); + jint * ${field_reference_name}ArrayElements = (*env)->GetIntArrayElements(env, ${field_reference_name}, NULL); + unsigned int _i; + for (_i = 0; _i < ${field_length}; _i++) { + ${field_reference_name}ArrayElements[_i] = clib_net_to_host_u32(mp->${c_name}[_i]); + } + + (*env)->ReleaseIntArrayElements(env, ${field_reference_name}, ${field_reference_name}ArrayElements, 0); + (*env)->SetObjectField(env, ${object_name}, ${field_reference_name}FieldId, ${field_reference_name}); + (*env)->DeleteLocalRef(env, ${field_reference_name}); + } +""") + +# For each u64 array we get its elements. Then we convert values to host byte order. +# All changes to jlong* buffer are written to jlongArray (isCopy is set to NULL) +u64_array_dto_field_setter_template = Template(""" + { + jlongArray ${field_reference_name} = (*env)->NewLongArray(env, ${field_length}); + jlong * ${field_reference_name}ArrayElements = (*env)->GetLongArrayElements(env, ${field_reference_name}, NULL); + unsigned int _i; + for (_i = 0; _i < ${field_length}; _i++) { + ${field_reference_name}ArrayElements[_i] = clib_net_to_host_u64(mp->${c_name}[_i]); + } + + (*env)->ReleaseLongArrayElements(env, ${field_reference_name}, ${field_reference_name}ArrayElements, 0); + (*env)->SetObjectField(env, ${object_name}, ${field_reference_name}FieldId, ${field_reference_name}); + (*env)->DeleteLocalRef(env, ${field_reference_name}); + } +""") + +dto_field_setter_templates = {'u8': default_dto_field_setter_template, + 'u16': u16_dto_field_setter_template, + 'u32': u32_dto_field_setter_template, + 'i32': u32_dto_field_setter_template, + 'u64': u64_dto_field_setter_template, + 'f64': default_dto_field_setter_template, # fixme + 'u8[]': u8_array_dto_field_setter_template, + 'u16[]': u16_array_dto_field_setter_template, + 'u32[]': u32_array_dto_field_setter_template, + 'u64[]': u64_array_dto_field_setter_template + } + + +def jni_reply_handler_for_type(handler_name, ref_name, field_type, c_name, field_reference_name, + field_name, field_length, is_variable_len_array, length_field_type, + object_name="dto"): + """ + Generates jni code that initializes a field of java object (dto or custom type). + To be used in reply message handlers. + :param field_type: type of the field to be initialized (as defined in vpe.api) + :param c_name: name of the message struct member that stores initialization value + :param field_reference_name: name of the field reference in generated code + :param field_name: name of the field (camelcase) + :param field_length: integer or name of variable that stores field length + :param object_name: name of the object to be initialized + """ + + # todo move validation to vppapigen + if field_type.endswith('[]') and field_length == '0': + raise Exception('Variable array \'%s\' defined in \'%s\' ' + 'should have defined length (e.g. \'%s[%s_length]\'' + % (c_name, handler_name, c_name, c_name)) + + if is_variable_len_array: + length_var_name = field_length + field_length = variable_length_array_value_template.substitute(length_var_name=length_var_name) + if length_field_type != 'u8': # we need net to host conversion: + field_length = variable_length_array_template.substitute( + length_field_type=length_field_type, value=field_length) + + # for retval don't generate setters + if util.is_retval_field(c_name): + return "" + + jni_signature = util.jni_2_signature_mapping[field_type] + jni_setter = util.jni_field_accessors[field_type] + + result = dto_field_id_template.substitute( + field_reference_name=field_reference_name, + field_name=field_name, + class_ref_name=ref_name, + jni_signature=jni_signature) + + dto_setter_template = dto_field_setter_templates[field_type] + + result += dto_setter_template.substitute( + jni_signature=jni_signature, + object_name=object_name, + field_reference_name=field_reference_name, + c_name=c_name, + jni_setter=jni_setter, + field_length=field_length) + return result + + +request_field_identifier_template = Template(""" + jfieldID ${field_reference_name}FieldId = (*env)->GetFieldID(env, ${object_name}Class, "${field_name}", "${jni_signature}"); + ${jni_type} ${field_reference_name} = (*env)->Get${jni_getter}(env, ${object_name}, ${field_reference_name}FieldId); + """) + +array_length_enforcement_template = Template(""" + size_t max_size = ${field_length}; + if (cnt > max_size) cnt = max_size;""") + +u8_struct_setter_template = Template(""" + mp->${c_name} = ${field_reference_name};""") + +u16_struct_setter_template = Template(""" + mp->${c_name} = clib_host_to_net_u16(${field_reference_name});""") + +u32_struct_setter_template = Template(""" + mp->${c_name} = clib_host_to_net_u32(${field_reference_name});""") + +i32_struct_setter_template = Template(""" + mp->${c_name} = clib_host_to_net_i32(${field_reference_name});!""") + +u64_struct_setter_template = Template(""" + mp->${c_name} = clib_host_to_net_u64(${field_reference_name});""") + +array_length_enforcement_template = Template(""" + size_t max_size = ${field_length}; + if (cnt > max_size) cnt = max_size;""") + +u8_array_struct_setter_template = Template(""" + if (${field_reference_name}) { + jsize cnt = (*env)->GetArrayLength (env, ${field_reference_name}); + ${field_length_check} + (*env)->GetByteArrayRegion(env, ${field_reference_name}, 0, cnt, (jbyte *)mp->${c_name}); + } +""") + +u16_array_struct_setter_template = Template(""" + if (${field_reference_name}) { + jshort * ${field_reference_name}ArrayElements = (*env)->GetShortArrayElements(env, ${field_reference_name}, NULL); + size_t _i; + jsize cnt = (*env)->GetArrayLength (env, ${field_reference_name}); + ${field_length_check} + for (_i = 0; _i < cnt; _i++) { + mp->${c_name}[_i] = clib_host_to_net_u16(${field_reference_name}ArrayElements[_i]); + } + (*env)->ReleaseShortArrayElements (env, ${field_reference_name}, ${field_reference_name}ArrayElements, 0); + } + """) + +u32_array_struct_setter_template = Template(""" + if (${field_reference_name}) { + jint * ${field_reference_name}ArrayElements = (*env)->GetIntArrayElements(env, ${field_reference_name}, NULL); + size_t _i; + jsize cnt = (*env)->GetArrayLength (env, ${field_reference_name}); + ${field_length_check} + for (_i = 0; _i < cnt; _i++) { + mp->${c_name}[_i] = clib_host_to_net_u32(${field_reference_name}ArrayElements[_i]); + } + (*env)->ReleaseIntArrayElements (env, ${field_reference_name}, ${field_reference_name}ArrayElements, 0); + } + """) + +u64_array_struct_setter_template = Template(""" + if (${field_reference_name}) { + jlong * ${field_reference_name}ArrayElements = (*env)->GetLongArrayElements(env, ${field_reference_name}, NULL); + size_t _i; + jsize cnt = (*env)->GetArrayLength (env, ${field_reference_name}); + ${field_length_check} + for (_i = 0; _i < cnt; _i++) { + mp->${c_name}[_i] = clib_host_to_net_u64(${field_reference_name}ArrayElements[_i]); + } + (*env)->ReleaseLongArrayElements (env, ${field_reference_name}, ${field_reference_name}ArrayElements, 0); + } + """) + +struct_setter_templates = {'u8': u8_struct_setter_template, + 'u16': u16_struct_setter_template, + 'u32': u32_struct_setter_template, + 'i32': u32_struct_setter_template, + 'u64': u64_struct_setter_template, + 'u8[]': u8_array_struct_setter_template, + 'u16[]': u16_array_struct_setter_template, + 'u32[]': u32_array_struct_setter_template, + 'u64[]': u64_array_struct_setter_template + } + + +def jni_request_identifiers_for_type(field_type, field_reference_name, field_name, object_name="request"): + """ + Generates jni code that defines C variable corresponding to field of java object + (dto or custom type). To be used in request message handlers. + :param field_type: type of the field to be initialized (as defined in vpe.api) + :param field_reference_name: name of the field reference in generated code + :param field_name: name of the field (camelcase) + :param object_name: name of the object to be initialized + """ + # field identifiers + jni_type = util.vpp_2_jni_type_mapping[field_type] + jni_signature = util.jni_2_signature_mapping[field_type] + jni_getter = util.jni_field_accessors[field_type] + + # field identifier + return request_field_identifier_template.substitute( + jni_type=jni_type, + field_reference_name=field_reference_name, + field_name=field_name, + jni_signature=jni_signature, + jni_getter=jni_getter, + object_name=object_name) + + +def jni_request_binding_for_type(field_type, c_name, field_reference_name, field_length, is_variable_len_array): + """ + Generates jni code that initializes C structure that corresponds to a field of java object + (dto or custom type). To be used in request message handlers. + :param field_type: type of the field to be initialized (as defined in vpe.api) + :param c_name: name of the message struct member to be initialized + :param field_reference_name: name of the field reference in generated code + :param field_length: integer or name of variable that stores field length + """ + + # field setter + field_length_check = "" + + # check if we are processing variable length array: + if is_variable_len_array: + field_length = util.underscore_to_camelcase(field_length) + + # enforce max length if array has fixed length or uses variable length syntax + if str(field_length) != "0": + field_length_check = array_length_enforcement_template.substitute(field_length=field_length) + + struct_setter_template = struct_setter_templates[field_type] + + msg_initialization = struct_setter_template.substitute( + c_name=c_name, + field_reference_name=field_reference_name, + field_length_check=field_length_check) + + return msg_initialization diff --git a/src/vpp-api/java/jvpp/gen/jvppgen/jvpp_c_gen.py b/src/vpp-api/java/jvpp/gen/jvppgen/jvpp_c_gen.py new file mode 100644 index 00000000..8761eb13 --- /dev/null +++ b/src/vpp-api/java/jvpp/gen/jvppgen/jvpp_c_gen.py @@ -0,0 +1,392 @@ +#!/usr/bin/env python +# +# Copyright (c) 2016 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 +# l +# 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. +# + +import os, util +from string import Template + +import jni_gen + + +def is_manually_generated(f_name, plugin_name): + return f_name in {'control_ping_reply'} + + +class_reference_template = Template("""jclass ${ref_name}Class; +""") + +find_class_invocation_template = Template(""" + ${ref_name}Class = (jclass)(*env)->NewGlobalRef(env, (*env)->FindClass(env, "io/fd/vpp/jvpp/${plugin_name}/dto/${class_name}")); + if ((*env)->ExceptionCheck(env)) { + (*env)->ExceptionDescribe(env); + return JNI_ERR; + }""") + +find_class_template = Template(""" + ${ref_name}Class = (jclass)(*env)->NewGlobalRef(env, (*env)->FindClass(env, "${class_name}")); + if ((*env)->ExceptionCheck(env)) { + (*env)->ExceptionDescribe(env); + return JNI_ERR; + }""") + +delete_class_invocation_template = Template(""" + if (${ref_name}Class) { + (*env)->DeleteGlobalRef(env, ${ref_name}Class); + }""") + +class_cache_template = Template(""" +$class_references +static int cache_class_references(JNIEnv* env) { + $find_class_invocations + return 0; +} + +static void delete_class_references(JNIEnv* env) { + $delete_class_invocations +}""") + + +def generate_class_cache(func_list, plugin_name): + class_references = [] + find_class_invocations = [] + delete_class_invocations = [] + for f in func_list: + c_name = f['name'] + class_name = util.underscore_to_camelcase_upper(c_name) + ref_name = util.underscore_to_camelcase(c_name) + + if util.is_ignored(c_name) or util.is_control_ping(class_name): + continue + + if util.is_reply(class_name): + class_references.append(class_reference_template.substitute( + ref_name=ref_name)) + find_class_invocations.append(find_class_invocation_template.substitute( + plugin_name=plugin_name, + ref_name=ref_name, + class_name=class_name)) + delete_class_invocations.append(delete_class_invocation_template.substitute(ref_name=ref_name)) + elif util.is_notification(c_name): + class_references.append(class_reference_template.substitute( + ref_name=util.add_notification_suffix(ref_name))) + find_class_invocations.append(find_class_invocation_template.substitute( + plugin_name=plugin_name, + ref_name=util.add_notification_suffix(ref_name), + class_name=util.add_notification_suffix(class_name))) + delete_class_invocations.append(delete_class_invocation_template.substitute( + ref_name=util.add_notification_suffix(ref_name))) + + # add exception class to class cache + ref_name = 'callbackException' + class_name = 'io/fd/vpp/jvpp/VppCallbackException' + class_references.append(class_reference_template.substitute( + ref_name=ref_name)) + find_class_invocations.append(find_class_template.substitute( + ref_name=ref_name, + class_name=class_name)) + delete_class_invocations.append(delete_class_invocation_template.substitute(ref_name=ref_name)) + + return class_cache_template.substitute( + class_references="".join(class_references), find_class_invocations="".join(find_class_invocations), + delete_class_invocations="".join(delete_class_invocations)) + + +# TODO: cache method and field identifiers to achieve better performance +# https://jira.fd.io/browse/HONEYCOMB-42 +request_class_template = Template(""" + jclass requestClass = (*env)->FindClass(env, "io/fd/vpp/jvpp/${plugin_name}/dto/${java_name_upper}");""") + +request_field_identifier_template = Template(""" + jfieldID ${field_reference_name}FieldId = (*env)->GetFieldID(env, ${object_name}Class, "${field_name}", "${jni_signature}"); + ${jni_type} ${field_reference_name} = (*env)->Get${jni_getter}(env, ${object_name}, ${field_reference_name}FieldId); + """) + +jni_msg_size_template = Template(""" + ${array_length}*sizeof(${element_type})""") + +jni_impl_template = Template(""" +/** + * JNI binding for sending ${c_name} message. + * Generated based on $inputfile preparsed data: +$api_data + */ +JNIEXPORT jint JNICALL Java_io_fd_vpp_jvpp_${plugin_name}_JVpp${java_plugin_name}Impl_${field_name}0 +(JNIEnv * env, jclass clazz$args) { + ${plugin_name}_main_t *plugin_main = &${plugin_name}_main; + vl_api_${c_name}_t * mp; + u32 my_context_id = vppjni_get_context_id (&jvpp_main); + $request_class + + $jni_identifiers + + // create message: + mp = vl_msg_api_alloc(${msg_size}); + memset (mp, 0, ${msg_size}); + mp->_vl_msg_id = ntohs (get_message_id(env, "${c_name}_${crc}")); + mp->client_index = plugin_main->my_client_index; + mp->context = clib_host_to_net_u32 (my_context_id); + + $msg_initialization + + // send message: + vl_msg_api_send_shmem (plugin_main->vl_input_queue, (u8 *)&mp); + if ((*env)->ExceptionCheck(env)) { + return JNI_ERR; + } + return my_context_id; +}""") + +def generate_jni_impl(func_list, plugin_name, inputfile): + jni_impl = [] + for f in func_list: + f_name = f['name'] + camel_case_function_name = util.underscore_to_camelcase(f_name) + if is_manually_generated(f_name, plugin_name) or util.is_reply(camel_case_function_name) \ + or util.is_ignored(f_name) or util.is_just_notification(f_name): + continue + + arguments = '' + request_class = '' + jni_identifiers = '' + msg_initialization = '' + f_name_uppercase = f_name.upper() + msg_size = 'sizeof(*mp)' + + if f['args']: + arguments = ', jobject request' + camel_case_function_name_upper = util.underscore_to_camelcase_upper(f_name) + + request_class = request_class_template.substitute( + java_name_upper=camel_case_function_name_upper, + plugin_name=plugin_name) + + for t in zip(f['types'], f['args'], f['lengths'], f['arg_types']): + field_name = util.underscore_to_camelcase(t[1]) + is_variable_len_array = t[2][1] + if is_variable_len_array: + msg_size += jni_msg_size_template.substitute(array_length=util.underscore_to_camelcase(t[2][0]), + element_type=t[3]) + jni_identifiers += jni_gen.jni_request_identifiers_for_type(field_type=t[0], + field_reference_name=field_name, + field_name=field_name) + msg_initialization += jni_gen.jni_request_binding_for_type(field_type=t[0], c_name=t[1], + field_reference_name=field_name, + field_length=t[2][0], + is_variable_len_array=is_variable_len_array) + + jni_impl.append(jni_impl_template.substitute( + inputfile=inputfile, + api_data=util.api_message_to_javadoc(f), + field_reference_name=camel_case_function_name, + field_name=camel_case_function_name, + c_name_uppercase=f_name_uppercase, + c_name=f_name, + crc=f['crc'], + plugin_name=plugin_name, + java_plugin_name=plugin_name.title(), + request_class=request_class, + jni_identifiers=jni_identifiers, + msg_size=msg_size, + msg_initialization=msg_initialization, + args=arguments)) + + return "\n".join(jni_impl) + +# code fragment for checking result of the operation before sending request reply +callback_err_handler_template = Template(""" + // for negative result don't send callback message but send error callback + if (mp->retval<0) { + call_on_error("${handler_name}", mp->context, mp->retval, plugin_main->callbackClass, plugin_main->callbackObject, callbackExceptionClass); + return; + } + if (mp->retval == VNET_API_ERROR_IN_PROGRESS) { + clib_warning("Result in progress"); + return; + } +""") + +msg_handler_template = Template(""" +/** + * Handler for ${handler_name} message. + * Generated based on $inputfile preparsed data: +$api_data + */ +static void vl_api_${handler_name}_t_handler (vl_api_${handler_name}_t * mp) +{ + ${plugin_name}_main_t *plugin_main = &${plugin_name}_main; + JNIEnv *env = jvpp_main.jenv; + jthrowable exc; + $err_handler + + jmethodID constructor = (*env)->GetMethodID(env, ${class_ref_name}Class, "<init>", "()V"); + + // User does not have to provide callbacks for all VPP messages. + // We are ignoring messages that are not supported by user. + (*env)->ExceptionClear(env); // just in case exception occurred in different place and was not properly cleared + jmethodID callbackMethod = (*env)->GetMethodID(env, plugin_main->callbackClass, "on${dto_name}", "(Lio/fd/vpp/jvpp/${plugin_name}/dto/${dto_name};)V"); + exc = (*env)->ExceptionOccurred(env); + if (exc) { + clib_warning("Unable to extract on${dto_name} method reference from ${plugin_name} plugin's callbackClass. Ignoring message.\\n"); + (*env)->ExceptionDescribe(env); + (*env)->ExceptionClear(env); + return; + } + + jobject dto = (*env)->NewObject(env, ${class_ref_name}Class, constructor); + $dto_setters + + (*env)->CallVoidMethod(env, plugin_main->callbackObject, callbackMethod, dto); + // free DTO as per http://stackoverflow.com/questions/1340938/memory-leak-when-calling-java-code-from-c-using-jni + (*env)->DeleteLocalRef(env, dto); +}""") + + +def generate_msg_handlers(func_list, plugin_name, inputfile): + handlers = [] + for f in func_list: + handler_name = f['name'] + dto_name = util.underscore_to_camelcase_upper(handler_name) + ref_name = util.underscore_to_camelcase(handler_name) + + if is_manually_generated(handler_name, plugin_name) or util.is_ignored(handler_name): + continue + + if not util.is_reply(dto_name) and not util.is_notification(handler_name): + continue + + if util.is_notification(handler_name): + dto_name = util.add_notification_suffix(dto_name) + ref_name = util.add_notification_suffix(ref_name) + + dto_setters = '' + err_handler = '' + # dto setters + for t in zip(f['types'], f['args'], f['lengths']): + c_name = t[1] + java_name = util.underscore_to_camelcase(c_name) + field_length = t[2][0] + is_variable_len_array = t[2][1] + length_field_type = None + if is_variable_len_array: + length_field_type = f['types'][f['args'].index(field_length)] + dto_setters += jni_gen.jni_reply_handler_for_type(handler_name=handler_name, ref_name=ref_name, + field_type=t[0], c_name=t[1], + field_reference_name=java_name, + field_name=java_name, field_length=field_length, + is_variable_len_array=is_variable_len_array, + length_field_type=length_field_type) + + # for retval don't generate setters and generate retval check + if util.is_retval_field(c_name): + err_handler = callback_err_handler_template.substitute( + handler_name=handler_name + ) + continue + + handlers.append(msg_handler_template.substitute( + inputfile=inputfile, + api_data=util.api_message_to_javadoc(f), + handler_name=handler_name, + plugin_name=plugin_name, + dto_name=dto_name, + class_ref_name=ref_name, + dto_setters=dto_setters, + err_handler=err_handler)) + + return "\n".join(handlers) + + +handler_registration_template = Template("""_(${name}_${crc}, ${name}) \\ +""") + + +def generate_handler_registration(func_list): + handler_registration = ["#define foreach_api_reply_handler \\\n"] + for f in func_list: + name = f['name'] + camelcase_name = util.underscore_to_camelcase(f['name']) + + if (not util.is_reply(camelcase_name) and not util.is_notification(name)) or util.is_ignored(name) \ + or util.is_control_ping(camelcase_name): + continue + + handler_registration.append(handler_registration_template.substitute( + name=name, + crc=f['crc'])) + + return "".join(handler_registration) + + +api_verification_template = Template("""_(${name}_${crc}) \\ +""") + + +def generate_api_verification(func_list): + api_verification = ["#define foreach_supported_api_message \\\n"] + for f in func_list: + name = f['name'] + + if util.is_ignored(name): + continue + + api_verification.append(api_verification_template.substitute( + name=name, + crc=f['crc'])) + + return "".join(api_verification) + + +jvpp_c_template = Template("""/** + * This file contains JNI bindings for jvpp Java API. + * It was generated by jvpp_c_gen.py based on $inputfile + * (python representation of api file generated by vppapigen). + */ + +// JAVA class reference cache +$class_cache + +// List of supported API messages used for verification +$api_verification + +// JNI bindings +$jni_implementations + +// Message handlers +$msg_handlers + +// Registration of message handlers in vlib +$handler_registration +""") + +def generate_jvpp(func_list, plugin_name, inputfile, path): + """ Generates jvpp C file """ + print "Generating jvpp C" + + class_cache = generate_class_cache(func_list, plugin_name) + jni_impl = generate_jni_impl(func_list, plugin_name, inputfile) + msg_handlers = generate_msg_handlers(func_list, plugin_name, inputfile) + handler_registration = generate_handler_registration(func_list) + api_verification = generate_api_verification(func_list) + + jvpp_c_file = open("%s/jvpp_%s_gen.h" % (path, plugin_name), 'w') + jvpp_c_file.write(jvpp_c_template.substitute( + inputfile=inputfile, + class_cache=class_cache, + api_verification=api_verification, + jni_implementations=jni_impl, + msg_handlers=msg_handlers, + handler_registration=handler_registration)) + jvpp_c_file.flush() + jvpp_c_file.close() + diff --git a/src/vpp-api/java/jvpp/gen/jvppgen/jvpp_callback_facade_gen.py b/src/vpp-api/java/jvpp/gen/jvppgen/jvpp_callback_facade_gen.py new file mode 100644 index 00000000..9aaa4c64 --- /dev/null +++ b/src/vpp-api/java/jvpp/gen/jvppgen/jvpp_callback_facade_gen.py @@ -0,0 +1,326 @@ +#!/usr/bin/env python +# +# Copyright (c) 2016 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. + +import os, util +from string import Template + +import callback_gen +import dto_gen + +jvpp_ifc_template = Template(""" +package $plugin_package.$callback_facade_package; + +/** + * <p>Callback Java API representation of $plugin_package plugin. + * <br>It was generated by jvpp_callback_facade_gen.py based on $inputfile + * <br>(python representation of api file generated by vppapigen). + */ +public interface CallbackJVpp${plugin_name} extends $base_package.$notification_package.NotificationRegistryProvider, java.lang.AutoCloseable { + + // TODO add send + +$methods +} +""") + +jvpp_impl_template = Template(""" +package $plugin_package.$callback_facade_package; + +/** + * <p>Default implementation of Callback${plugin_name}JVpp interface. + * <br>It was generated by jvpp_callback_facade_gen.py based on $inputfile + * <br>(python representation of api file generated by vppapigen). + */ +public final class CallbackJVpp${plugin_name}Facade implements CallbackJVpp${plugin_name} { + + private final $plugin_package.JVpp${plugin_name} jvpp; + private final java.util.Map<Integer, $base_package.$callback_package.JVppCallback> callbacks; + private final $plugin_package.$notification_package.${plugin_name}NotificationRegistryImpl notificationRegistry = new $plugin_package.$notification_package.${plugin_name}NotificationRegistryImpl(); + /** + * <p>Create CallbackJVpp${plugin_name}Facade object for provided JVpp instance. + * Constructor internally creates CallbackJVppFacadeCallback class for processing callbacks + * and then connects to provided JVpp instance + * + * @param jvpp provided $base_package.JVpp instance + * + * @throws java.io.IOException in case instance cannot connect to JVPP + */ + public CallbackJVpp${plugin_name}Facade(final $base_package.JVppRegistry registry, final $plugin_package.JVpp${plugin_name} jvpp) throws java.io.IOException { + this.jvpp = java.util.Objects.requireNonNull(jvpp,"jvpp is null"); + this.callbacks = new java.util.HashMap<>(); + java.util.Objects.requireNonNull(registry, "JVppRegistry should not be null"); + registry.register(jvpp, new CallbackJVpp${plugin_name}FacadeCallback(this.callbacks, notificationRegistry)); + } + + @Override + public $plugin_package.$notification_package.${plugin_name}NotificationRegistry getNotificationRegistry() { + return notificationRegistry; + } + + @Override + public void close() throws Exception { + jvpp.close(); + } + + // TODO add send() + +$methods +} +""") + +method_template = Template( + """ void $name($plugin_package.$dto_package.$request request, $plugin_package.$callback_package.$callback callback) throws $base_package.VppInvocationException;""") + +method_impl_template = Template(""" public final void $name($plugin_package.$dto_package.$request request, $plugin_package.$callback_package.$callback callback) throws $base_package.VppInvocationException { + synchronized (callbacks) { + callbacks.put(jvpp.$name(request), callback); + } + } +""") + +no_arg_method_template = Template(""" void $name($plugin_package.$callback_package.$callback callback) throws $base_package.VppInvocationException;""") +no_arg_method_impl_template = Template(""" public final void $name($plugin_package.$callback_package.$callback callback) throws $base_package.VppInvocationException { + synchronized (callbacks) { + callbacks.put(jvpp.$name(), callback); + } + } +""") + + +def generate_jvpp(func_list, base_package, plugin_package, plugin_name, dto_package, callback_package, notification_package, callback_facade_package, inputfile): + """ Generates callback facade """ + print "Generating JVpp callback facade" + + if os.path.exists(callback_facade_package): + util.remove_folder(callback_facade_package) + + os.mkdir(callback_facade_package) + + methods = [] + methods_impl = [] + for func in func_list: + + if util.is_notification(func['name']) or util.is_ignored(func['name']): + continue + + camel_case_name = util.underscore_to_camelcase(func['name']) + camel_case_name_upper = util.underscore_to_camelcase_upper(func['name']) + if util.is_reply(camel_case_name) or util.is_control_ping(camel_case_name): + continue + + # Strip suffix for dump calls + callback_type = get_request_name(camel_case_name_upper, func['name']) + callback_gen.callback_suffix + + if len(func['args']) == 0: + methods.append(no_arg_method_template.substitute(name=camel_case_name, + base_package=base_package, + plugin_package=plugin_package, + dto_package=dto_package, + callback_package=callback_package, + callback=callback_type)) + methods_impl.append(no_arg_method_impl_template.substitute(name=camel_case_name, + base_package=base_package, + plugin_package=plugin_package, + dto_package=dto_package, + callback_package=callback_package, + callback=callback_type)) + else: + methods.append(method_template.substitute(name=camel_case_name, + request=camel_case_name_upper, + base_package=base_package, + plugin_package=plugin_package, + dto_package=dto_package, + callback_package=callback_package, + callback=callback_type)) + methods_impl.append(method_impl_template.substitute(name=camel_case_name, + request=camel_case_name_upper, + base_package=base_package, + plugin_package=plugin_package, + dto_package=dto_package, + callback_package=callback_package, + callback=callback_type)) + + join = os.path.join(callback_facade_package, "CallbackJVpp%s.java" % plugin_name) + jvpp_file = open(join, 'w') + jvpp_file.write( + jvpp_ifc_template.substitute(inputfile=inputfile, + methods="\n".join(methods), + base_package=base_package, + plugin_package=plugin_package, + plugin_name=plugin_name, + dto_package=dto_package, + notification_package=notification_package, + callback_facade_package=callback_facade_package)) + jvpp_file.flush() + jvpp_file.close() + + jvpp_file = open(os.path.join(callback_facade_package, "CallbackJVpp%sFacade.java" % plugin_name), 'w') + jvpp_file.write(jvpp_impl_template.substitute(inputfile=inputfile, + methods="\n".join(methods_impl), + base_package=base_package, + plugin_package=plugin_package, + plugin_name=plugin_name, + dto_package=dto_package, + notification_package=notification_package, + callback_package=callback_package, + callback_facade_package=callback_facade_package)) + jvpp_file.flush() + jvpp_file.close() + + generate_callback(func_list, base_package, plugin_package, plugin_name, dto_package, callback_package, notification_package, callback_facade_package, inputfile) + + +jvpp_facade_callback_template = Template(""" +package $plugin_package.$callback_facade_package; + +/** + * <p>Implementation of JVppGlobalCallback interface for Java Callback API. + * <br>It was generated by jvpp_callback_facade_gen.py based on $inputfile + * <br>(python representation of api file generated by vppapigen). + */ +public final class CallbackJVpp${plugin_name}FacadeCallback implements $plugin_package.$callback_package.JVpp${plugin_name}GlobalCallback { + + private final java.util.Map<Integer, $base_package.$callback_package.JVppCallback> requests; + private final $plugin_package.$notification_package.Global${plugin_name}NotificationCallback notificationCallback; + private static final java.util.logging.Logger LOG = java.util.logging.Logger.getLogger(CallbackJVpp${plugin_name}FacadeCallback.class.getName()); + + public CallbackJVpp${plugin_name}FacadeCallback(final java.util.Map<Integer, $base_package.$callback_package.JVppCallback> requestMap, + final $plugin_package.$notification_package.Global${plugin_name}NotificationCallback notificationCallback) { + this.requests = requestMap; + this.notificationCallback = notificationCallback; + } + + @Override + public void onError($base_package.VppCallbackException reply) { + + $base_package.$callback_package.JVppCallback failedCall; + synchronized(requests) { + failedCall = requests.remove(reply.getCtxId()); + } + + if(failedCall != null) { + try { + failedCall.onError(reply); + } catch(RuntimeException ex) { + ex.addSuppressed(reply); + LOG.log(java.util.logging.Level.WARNING, String.format("Callback: %s failed while handling exception: %s", failedCall, reply), ex); + } + } + } + + @Override + @SuppressWarnings("unchecked") + public void onControlPingReply(final $base_package.$dto_package.ControlPingReply reply) { + + $base_package.$callback_package.ControlPingCallback callback; + final int replyId = reply.context; + synchronized(requests) { + callback = ($base_package.$callback_package.ControlPingCallback) requests.remove(replyId); + } + + if(callback != null) { + callback.onControlPingReply(reply); + } + } + +$methods +} +""") + +jvpp_facade_callback_method_template = Template(""" + @Override + @SuppressWarnings("unchecked") + public void on$callback_dto(final $plugin_package.$dto_package.$callback_dto reply) { + + $plugin_package.$callback_package.$callback callback; + final int replyId = reply.context; + synchronized(requests) { + callback = ($plugin_package.$callback_package.$callback) requests.remove(replyId); + } + + if(callback != null) { + callback.on$callback_dto(reply); + } + } +""") + +jvpp_facade_callback_notification_method_template = Template(""" + @Override + @SuppressWarnings("unchecked") + public void on$callback_dto($plugin_package.$dto_package.$callback_dto notification) { + notificationCallback.on$callback_dto(notification); + } +""") + + +def generate_callback(func_list, base_package, plugin_package, plugin_name, dto_package, callback_package, notification_package, callback_facade_package, inputfile): + callbacks = [] + for func in func_list: + + camel_case_name_with_suffix = util.underscore_to_camelcase_upper(func['name']) + + if util.is_ignored(func['name']) or util.is_control_ping(camel_case_name_with_suffix): + continue + + if util.is_reply(camel_case_name_with_suffix): + callbacks.append(jvpp_facade_callback_method_template.substitute(plugin_package=plugin_package, + dto_package=dto_package, + callback_package=callback_package, + callback=util.remove_reply_suffix(camel_case_name_with_suffix) + callback_gen.callback_suffix, + callback_dto=camel_case_name_with_suffix)) + + if util.is_notification(func["name"]): + with_notification_suffix = util.add_notification_suffix(camel_case_name_with_suffix) + callbacks.append(jvpp_facade_callback_notification_method_template.substitute(plugin_package=plugin_package, + dto_package=dto_package, + callback_package=callback_package, + callback=with_notification_suffix + callback_gen.callback_suffix, + callback_dto=with_notification_suffix)) + + jvpp_file = open(os.path.join(callback_facade_package, "CallbackJVpp%sFacadeCallback.java" % plugin_name), 'w') + jvpp_file.write(jvpp_facade_callback_template.substitute(inputfile=inputfile, + base_package=base_package, + plugin_package=plugin_package, + plugin_name=plugin_name, + dto_package=dto_package, + notification_package=notification_package, + callback_package=callback_package, + methods="".join(callbacks), + callback_facade_package=callback_facade_package)) + jvpp_file.flush() + jvpp_file.close() + + +# Returns request name or special one from unconventional_naming_rep_req map +def get_request_name(camel_case_dto_name, func_name): + if func_name in reverse_dict(util.unconventional_naming_rep_req): + request_name = util.underscore_to_camelcase_upper(reverse_dict(util.unconventional_naming_rep_req)[func_name]) + else: + request_name = camel_case_dto_name + return remove_suffix(request_name) + + +def reverse_dict(map): + return dict((v, k) for k, v in map.iteritems()) + + +def remove_suffix(name): + if util.is_reply(name): + return util.remove_reply_suffix(name) + else: + if util.is_dump(name): + return util.remove_suffix(name, util.dump_suffix) + else: + return name diff --git a/src/vpp-api/java/jvpp/gen/jvppgen/jvpp_future_facade_gen.py b/src/vpp-api/java/jvpp/gen/jvppgen/jvpp_future_facade_gen.py new file mode 100644 index 00000000..07947e30 --- /dev/null +++ b/src/vpp-api/java/jvpp/gen/jvppgen/jvpp_future_facade_gen.py @@ -0,0 +1,331 @@ +#!/usr/bin/env python +# +# Copyright (c) 2016 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. + +import os +from string import Template + +import dto_gen +import util + +jvpp_facade_callback_template = Template(""" +package $plugin_package.$future_package; + +/** + * <p>Async facade callback setting values to future objects + * <br>It was generated by jvpp_future_facade_gen.py based on $inputfile + * <br>(python representation of api file generated by vppapigen). + */ +public final class FutureJVpp${plugin_name}FacadeCallback implements $plugin_package.$callback_package.JVpp${plugin_name}GlobalCallback { + + private final java.util.Map<java.lang.Integer, java.util.concurrent.CompletableFuture<? extends $base_package.$dto_package.JVppReply<?>>> requests; + private final $plugin_package.$notification_package.Global${plugin_name}NotificationCallback notificationCallback; + + public FutureJVpp${plugin_name}FacadeCallback( + final java.util.Map<java.lang.Integer, java.util.concurrent.CompletableFuture<? extends $base_package.$dto_package.JVppReply<?>>> requestMap, + final $plugin_package.$notification_package.Global${plugin_name}NotificationCallback notificationCallback) { + this.requests = requestMap; + this.notificationCallback = notificationCallback; + } + + @Override + @SuppressWarnings("unchecked") + public void onError($base_package.VppCallbackException reply) { + final java.util.concurrent.CompletableFuture<$base_package.$dto_package.JVppReply<?>> completableFuture; + + synchronized(requests) { + completableFuture = (java.util.concurrent.CompletableFuture<$base_package.$dto_package.JVppReply<?>>) requests.get(reply.getCtxId()); + } + + if(completableFuture != null) { + completableFuture.completeExceptionally(reply); + + synchronized(requests) { + requests.remove(reply.getCtxId()); + } + } + } + + @Override + @SuppressWarnings("unchecked") + public void onControlPingReply(final $base_package.$dto_package.ControlPingReply reply) { + final java.util.concurrent.CompletableFuture<$base_package.$dto_package.JVppReply<?>> completableFuture; + + final int replyId = reply.context; + synchronized(requests) { + completableFuture = (java.util.concurrent.CompletableFuture<$base_package.$dto_package.JVppReply<?>>) requests.get(replyId); + } + + if(completableFuture != null) { + // Finish dump call + if (completableFuture instanceof $base_package.$future_package.AbstractFutureJVppInvoker.CompletableDumpFuture) { + completableFuture.complete((($base_package.$future_package.AbstractFutureJVppInvoker.CompletableDumpFuture) completableFuture).getReplyDump()); + // Remove future mapped to dump call context id + synchronized(requests) { + requests.remove((($base_package.$future_package.AbstractFutureJVppInvoker.CompletableDumpFuture) completableFuture).getContextId()); + } + } else { + completableFuture.complete(reply); + } + synchronized(requests) { + requests.remove(replyId); + } + } + } + +$methods +} +""") + +jvpp_facade_callback_method_template = Template(""" + @Override + @SuppressWarnings("unchecked") + public void on$callback_dto(final $plugin_package.$dto_package.$callback_dto reply) { + final java.util.concurrent.CompletableFuture<$base_package.$dto_package.JVppReply<?>> completableFuture; + final int replyId = reply.context; + synchronized(requests) { + completableFuture = (java.util.concurrent.CompletableFuture<$base_package.$dto_package.JVppReply<?>>) requests.get(replyId); + } + + if(completableFuture != null) { + completableFuture.complete(reply); + + synchronized(requests) { + requests.remove(replyId); + } + } + } +""") + +jvpp_facade_callback_notification_method_template = Template(""" + @Override + public void on$callback_dto($plugin_package.$dto_package.$callback_dto notification) { + notificationCallback.on$callback_dto(notification); + } +""") + +jvpp_facade_details_callback_method_template = Template(""" + @Override + @SuppressWarnings("unchecked") + public void on$callback_dto(final $plugin_package.$dto_package.$callback_dto reply) { + final $base_package.$future_package.AbstractFutureJVppInvoker.CompletableDumpFuture<$plugin_package.$dto_package.$callback_dto_reply_dump> completableFuture; + final int replyId = reply.context; + synchronized(requests) { + completableFuture = ($base_package.$future_package.AbstractFutureJVppInvoker.CompletableDumpFuture<$plugin_package.$dto_package.$callback_dto_reply_dump>) requests.get(replyId); + } + + if(completableFuture != null) { + completableFuture.getReplyDump().$callback_dto_field.add(reply); + } + } +""") + + +def generate_jvpp(func_list, base_package, plugin_package, plugin_name, dto_package, callback_package, notification_package, future_facade_package, inputfile): + """ Generates JVpp interface and JNI implementation """ + print "Generating JVpp future facade" + + if not os.path.exists(future_facade_package): + os.mkdir(future_facade_package) + + methods = [] + methods_impl = [] + callbacks = [] + for func in func_list: + camel_case_name_with_suffix = util.underscore_to_camelcase_upper(func['name']) + + if util.is_ignored(func['name']) or util.is_control_ping(camel_case_name_with_suffix): + continue + + if not util.is_reply(camel_case_name_with_suffix) and not util.is_notification(func['name']): + continue + + camel_case_method_name = util.underscore_to_camelcase(func['name']) + + if not util.is_notification(func["name"]): + camel_case_request_method_name = util.remove_reply_suffix(util.underscore_to_camelcase(func['name'])) + if util.is_details(camel_case_name_with_suffix): + camel_case_reply_name = get_standard_dump_reply_name(util.underscore_to_camelcase_upper(func['name']), + func['name']) + callbacks.append(jvpp_facade_details_callback_method_template.substitute(base_package=base_package, + plugin_package=plugin_package, + dto_package=dto_package, + callback_dto=camel_case_name_with_suffix, + callback_dto_field=camel_case_method_name, + callback_dto_reply_dump=camel_case_reply_name + dto_gen.dump_dto_suffix, + future_package=future_facade_package)) + + methods.append(future_jvpp_method_template.substitute(plugin_package=plugin_package, + dto_package=dto_package, + method_name=camel_case_request_method_name + + util.underscore_to_camelcase_upper(util.dump_suffix), + reply_name=camel_case_reply_name + dto_gen.dump_dto_suffix, + request_name=util.remove_reply_suffix(camel_case_reply_name) + + util.underscore_to_camelcase_upper(util.dump_suffix))) + methods_impl.append(future_jvpp_dump_method_impl_template.substitute(plugin_package=plugin_package, + dto_package=dto_package, + method_name=camel_case_request_method_name + + util.underscore_to_camelcase_upper(util.dump_suffix), + reply_name=camel_case_reply_name + dto_gen.dump_dto_suffix, + request_name=util.remove_reply_suffix(camel_case_reply_name) + + util.underscore_to_camelcase_upper(util.dump_suffix))) + else: + request_name = util.underscore_to_camelcase_upper(util.unconventional_naming_rep_req[func['name']]) \ + if func['name'] in util.unconventional_naming_rep_req else util.remove_reply_suffix(camel_case_name_with_suffix) + + methods.append(future_jvpp_method_template.substitute(plugin_package=plugin_package, + dto_package=dto_package, + method_name=camel_case_request_method_name, + reply_name=camel_case_name_with_suffix, + request_name=request_name)) + methods_impl.append(future_jvpp_method_impl_template.substitute(plugin_package=plugin_package, + dto_package=dto_package, + method_name=camel_case_request_method_name, + reply_name=camel_case_name_with_suffix, + request_name=request_name)) + + callbacks.append(jvpp_facade_callback_method_template.substitute(base_package=base_package, + plugin_package=plugin_package, + dto_package=dto_package, + callback_dto=camel_case_name_with_suffix)) + + if util.is_notification(func["name"]): + callbacks.append(jvpp_facade_callback_notification_method_template.substitute(plugin_package=plugin_package, + dto_package=dto_package, + callback_dto=util.add_notification_suffix(camel_case_name_with_suffix))) + + jvpp_file = open(os.path.join(future_facade_package, "FutureJVpp%sFacadeCallback.java" % plugin_name), 'w') + jvpp_file.write(jvpp_facade_callback_template.substitute(inputfile=inputfile, + base_package=base_package, + plugin_package=plugin_package, + plugin_name=plugin_name, + dto_package=dto_package, + notification_package=notification_package, + callback_package=callback_package, + methods="".join(callbacks), + future_package=future_facade_package)) + jvpp_file.flush() + jvpp_file.close() + + jvpp_file = open(os.path.join(future_facade_package, "FutureJVpp%s.java" % plugin_name), 'w') + jvpp_file.write(future_jvpp_template.substitute(inputfile=inputfile, + base_package=base_package, + plugin_package=plugin_package, + plugin_name=plugin_name, + notification_package=notification_package, + methods="".join(methods), + future_package=future_facade_package)) + jvpp_file.flush() + jvpp_file.close() + + jvpp_file = open(os.path.join(future_facade_package, "FutureJVpp%sFacade.java" % plugin_name), 'w') + jvpp_file.write(future_jvpp_facade_template.substitute(inputfile=inputfile, + base_package=base_package, + plugin_package=plugin_package, + plugin_name=plugin_name, + dto_package=dto_package, + notification_package=notification_package, + methods="".join(methods_impl), + future_package=future_facade_package)) + jvpp_file.flush() + jvpp_file.close() + + +future_jvpp_template = Template(''' +package $plugin_package.$future_package; + +/** + * <p>Async facade extension adding specific methods for each request invocation + * <br>It was generated by jvpp_future_facade_gen.py based on $inputfile + * <br>(python representation of api file generated by vppapigen). + */ +public interface FutureJVpp${plugin_name} extends $base_package.$future_package.FutureJVppInvoker { +$methods + + @Override + public $plugin_package.$notification_package.${plugin_name}NotificationRegistry getNotificationRegistry(); + +} +''') + +future_jvpp_method_template = Template(''' + java.util.concurrent.CompletionStage<$plugin_package.$dto_package.$reply_name> $method_name($plugin_package.$dto_package.$request_name request); +''') + + +future_jvpp_facade_template = Template(''' +package $plugin_package.$future_package; + +/** + * <p>Implementation of FutureJVpp based on AbstractFutureJVppInvoker + * <br>It was generated by jvpp_future_facade_gen.py based on $inputfile + * <br>(python representation of api file generated by vppapigen). + */ +public class FutureJVpp${plugin_name}Facade extends $base_package.$future_package.AbstractFutureJVppInvoker implements FutureJVpp${plugin_name} { + + private final $plugin_package.$notification_package.${plugin_name}NotificationRegistryImpl notificationRegistry = new $plugin_package.$notification_package.${plugin_name}NotificationRegistryImpl(); + + /** + * <p>Create FutureJVpp${plugin_name}Facade object for provided JVpp instance. + * Constructor internally creates FutureJVppFacadeCallback class for processing callbacks + * and then connects to provided JVpp instance + * + * @param jvpp provided $base_package.JVpp instance + * + * @throws java.io.IOException in case instance cannot connect to JVPP + */ + public FutureJVpp${plugin_name}Facade(final $base_package.JVppRegistry registry, final $base_package.JVpp jvpp) throws java.io.IOException { + super(jvpp, registry, new java.util.HashMap<>()); + java.util.Objects.requireNonNull(registry, "JVppRegistry should not be null"); + registry.register(jvpp, new FutureJVpp${plugin_name}FacadeCallback(getRequests(), notificationRegistry)); + } + + @Override + public $plugin_package.$notification_package.${plugin_name}NotificationRegistry getNotificationRegistry() { + return notificationRegistry; + } + +$methods +} +''') + +future_jvpp_method_impl_template = Template(''' + @Override + public java.util.concurrent.CompletionStage<$plugin_package.$dto_package.$reply_name> $method_name($plugin_package.$dto_package.$request_name request) { + return send(request); + } +''') + +future_jvpp_dump_method_impl_template = Template(''' + @Override + public java.util.concurrent.CompletionStage<$plugin_package.$dto_package.$reply_name> $method_name($plugin_package.$dto_package.$request_name request) { + return send(request, new $plugin_package.$dto_package.$reply_name()); + } +''') + + +# Returns request name or special one from unconventional_naming_rep_req map +def get_standard_dump_reply_name(camel_case_dto_name, func_name): + # FIXME this is a hotfix for sub-details callbacks + # FIXME also for L2FibTableEntry + # It's all because unclear mapping between + # request -> reply, + # dump -> reply, details, + # notification_start -> reply, notifications + + # vpe.api needs to be "standardized" so we can parse the information and create maps before generating java code + suffix = func_name.split("_")[-1] + return util.underscore_to_camelcase_upper( + util.unconventional_naming_rep_req[func_name]) + util.underscore_to_camelcase_upper(suffix) if func_name in util.unconventional_naming_rep_req \ + else camel_case_dto_name diff --git a/src/vpp-api/java/jvpp/gen/jvppgen/jvpp_impl_gen.py b/src/vpp-api/java/jvpp/gen/jvppgen/jvpp_impl_gen.py new file mode 100644 index 00000000..7bf91138 --- /dev/null +++ b/src/vpp-api/java/jvpp/gen/jvppgen/jvpp_impl_gen.py @@ -0,0 +1,219 @@ +#!/usr/bin/env python +# +# Copyright (c) 2016 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. + +import os, util +from string import Template + +jvpp_ifc_template = Template(""" +package $plugin_package; + +/** + * <p>Java representation of plugin's api file. + * <br>It was generated by jvpp_impl_gen.py based on $inputfile + * <br>(python representation of api file generated by vppapigen). + */ +public interface JVpp${plugin_name} extends $base_package.JVpp { + + /** + * Generic dispatch method for sending requests to VPP + * + * @throws io.fd.vpp.jvpp.VppInvocationException if send request had failed + */ + int send($base_package.$dto_package.JVppRequest request) throws io.fd.vpp.jvpp.VppInvocationException; + +$methods +} +""") + +jvpp_impl_template = Template(""" +package $plugin_package; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.PosixFilePermission; +import java.nio.file.attribute.PosixFilePermissions; +import java.util.Set; +import java.util.logging.Logger; +import $base_package.callback.JVppCallback; +import $base_package.VppConnection; +import $base_package.JVppRegistry; + +/** + * <p>Default implementation of JVpp interface. + * <br>It was generated by jvpp_impl_gen.py based on $inputfile + * <br>(python representation of api file generated by vppapigen). + */ +public final class JVpp${plugin_name}Impl implements $plugin_package.JVpp${plugin_name} { + + private final static Logger LOG = Logger.getLogger(JVpp${plugin_name}Impl.class.getName()); + private static final String LIBNAME = "libjvpp_${plugin_name_underscore}.so"; + + // FIXME using NativeLibraryLoader makes load fail could not find (WantInterfaceEventsReply). + static { + try { + loadLibrary(); + } catch (Exception e) { + LOG.severe("Can't find jvpp jni library: " + LIBNAME); + throw new ExceptionInInitializerError(e); + } + } + + private static void loadStream(final InputStream is) throws IOException { + final Set<PosixFilePermission> perms = PosixFilePermissions.fromString("rwxr-x---"); + final Path p = Files.createTempFile(LIBNAME, null, PosixFilePermissions.asFileAttribute(perms)); + try { + Files.copy(is, p, StandardCopyOption.REPLACE_EXISTING); + + try { + Runtime.getRuntime().load(p.toString()); + } catch (UnsatisfiedLinkError e) { + throw new IOException("Failed to load library " + p, e); + } + } finally { + try { + Files.deleteIfExists(p); + } catch (IOException e) { + } + } + } + + private static void loadLibrary() throws IOException { + try (final InputStream is = JVpp${plugin_name}Impl.class.getResourceAsStream('/' + LIBNAME)) { + if (is == null) { + throw new IOException("Failed to open library resource " + LIBNAME); + } + loadStream(is); + } + } + + private VppConnection connection; + private JVppRegistry registry; + + private static native void init0(final JVppCallback callback, final long queueAddress, final int clientIndex); + @Override + public void init(final JVppRegistry registry, final JVppCallback callback, final long queueAddress, final int clientIndex) { + this.registry = java.util.Objects.requireNonNull(registry, "registry should not be null"); + this.connection = java.util.Objects.requireNonNull(registry.getConnection(), "connection should not be null"); + connection.checkActive(); + init0(callback, queueAddress, clientIndex); + } + + private static native void close0(); + @Override + public void close() { + close0(); + } + + @Override + public int send($base_package.$dto_package.JVppRequest request) throws io.fd.vpp.jvpp.VppInvocationException { + return request.send(this); + } + + @Override + public final int controlPing(final io.fd.vpp.jvpp.dto.ControlPing controlPing) throws io.fd.vpp.jvpp.VppInvocationException { + return registry.controlPing(JVpp${plugin_name}Impl.class); + } + +$methods +} +""") + +method_template = Template(""" int $name($plugin_package.$dto_package.$request request) throws io.fd.vpp.jvpp.VppInvocationException;""") +method_native_template = Template( + """ private static native int ${name}0($plugin_package.$dto_package.$request request);""") +method_impl_template = Template(""" public final int $name($plugin_package.$dto_package.$request request) throws io.fd.vpp.jvpp.VppInvocationException { + java.util.Objects.requireNonNull(request,"Null request object"); + connection.checkActive(); + int result=${name}0(request); + if(result<0){ + throw new io.fd.vpp.jvpp.VppInvocationException("${name}",result); + } + return result; + } +""") + +no_arg_method_template = Template(""" int $name() throws io.fd.vpp.jvpp.VppInvocationException;""") +no_arg_method_native_template = Template(""" private static native int ${name}0() throws io.fd.vpp.jvpp.VppInvocationException;""") +no_arg_method_impl_template = Template(""" public final int $name() throws io.fd.vpp.jvpp.VppInvocationException { + connection.checkActive(); + int result=${name}0(); + if(result<0){ + throw new io.fd.vpp.jvpp.VppInvocationException("${name}",result); + } + return result; + } +""") + + +def generate_jvpp(func_list, base_package, plugin_package, plugin_name_underscore, dto_package, inputfile): + """ Generates JVpp interface and JNI implementation """ + print "Generating JVpp" + plugin_name = util.underscore_to_camelcase_upper(plugin_name_underscore) + + methods = [] + methods_impl = [] + for func in func_list: + + # Skip structures that are used only as notifications + if util.is_just_notification(func['name']) or util.is_ignored(func['name']): + continue + + camel_case_name = util.underscore_to_camelcase(func['name']) + camel_case_name_upper = util.underscore_to_camelcase_upper(func['name']) + if util.is_reply(camel_case_name): + continue + + if len(func['args']) == 0: + methods.append(no_arg_method_template.substitute(name=camel_case_name)) + methods_impl.append(no_arg_method_native_template.substitute(name=camel_case_name)) + methods_impl.append(no_arg_method_impl_template.substitute(name=camel_case_name)) + else: + methods.append(method_template.substitute(name=camel_case_name, + request=camel_case_name_upper, + plugin_package=plugin_package, + dto_package=dto_package)) + methods_impl.append(method_native_template.substitute(name=camel_case_name, + request=camel_case_name_upper, + plugin_package=plugin_package, + dto_package=dto_package)) + methods_impl.append(method_impl_template.substitute(name=camel_case_name, + request=camel_case_name_upper, + plugin_package=plugin_package, + dto_package=dto_package)) + + jvpp_file = open("JVpp%s.java" % plugin_name, 'w') + jvpp_file.write( + jvpp_ifc_template.substitute(inputfile=inputfile, + methods="\n".join(methods), + base_package=base_package, + plugin_package=plugin_package, + plugin_name=plugin_name, + dto_package=dto_package)) + jvpp_file.flush() + jvpp_file.close() + + jvpp_file = open("JVpp%sImpl.java" % plugin_name, 'w') + jvpp_file.write(jvpp_impl_template.substitute(inputfile=inputfile, + methods="\n".join(methods_impl), + base_package=base_package, + plugin_package=plugin_package, + plugin_name=plugin_name, + plugin_name_underscore=plugin_name_underscore, + dto_package=dto_package)) + jvpp_file.flush() + jvpp_file.close() diff --git a/src/vpp-api/java/jvpp/gen/jvppgen/notification_gen.py b/src/vpp-api/java/jvpp/gen/jvppgen/notification_gen.py new file mode 100644 index 00000000..94302d56 --- /dev/null +++ b/src/vpp-api/java/jvpp/gen/jvppgen/notification_gen.py @@ -0,0 +1,199 @@ +#!/usr/bin/env python +# +# Copyright (c) 2016 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. + +import os + +import callback_gen +import util +from string import Template + +notification_registry_template = Template(""" +package $plugin_package.$notification_package; + +/** + * <p>Registry for notification callbacks defined in ${plugin_name}. + * <br>It was generated by notification_gen.py based on $inputfile + * <br>(python representation of api file generated by vppapigen). + */ +public interface ${plugin_name}NotificationRegistry extends $base_package.$notification_package.NotificationRegistry { + + $register_callback_methods + + @Override + void close(); +} +""") + +global_notification_callback_template = Template(""" +package $plugin_package.$notification_package; + +/** + * <p>Aggregated callback interface for notifications only. + * <br>It was generated by notification_gen.py based on $inputfile + * <br>(python representation of api file generated by vppapigen). + */ +public interface Global${plugin_name}NotificationCallback$callbacks { + +} +""") + +notification_registry_impl_template = Template(""" +package $plugin_package.$notification_package; + +/** + * <p>Notification registry delegating notification processing to registered callbacks. + * <br>It was generated by notification_gen.py based on $inputfile + * <br>(python representation of api file generated by vppapigen). + */ +public final class ${plugin_name}NotificationRegistryImpl implements ${plugin_name}NotificationRegistry, Global${plugin_name}NotificationCallback { + + // TODO add a special NotificationCallback interface and only allow those to be registered + private final java.util.concurrent.ConcurrentMap<Class<? extends $base_package.$dto_package.JVppNotification>, $base_package.$callback_package.JVppNotificationCallback> registeredCallbacks = + new java.util.concurrent.ConcurrentHashMap<>(); + + $register_callback_methods + $handler_methods + + @Override + public void close() { + registeredCallbacks.clear(); + } +} +""") + +register_callback_impl_template = Template(""" + public java.lang.AutoCloseable register$callback(final $plugin_package.$callback_package.$callback callback){ + if(null != registeredCallbacks.putIfAbsent($plugin_package.$dto_package.$notification.class, callback)){ + throw new IllegalArgumentException("Callback for " + $plugin_package.$dto_package.$notification.class + + "notification already registered"); + } + return () -> registeredCallbacks.remove($plugin_package.$dto_package.$notification.class); + } +""") + +handler_impl_template = Template(""" + @Override + public void on$notification( + final $plugin_package.$dto_package.$notification notification) { + final $base_package.$callback_package.JVppNotificationCallback jVppNotificationCallback = registeredCallbacks.get($plugin_package.$dto_package.$notification.class); + if (null != jVppNotificationCallback) { + (($plugin_package.$callback_package.$callback) registeredCallbacks + .get($plugin_package.$dto_package.$notification.class)) + .on$notification(notification); + } + } +""") + +notification_provider_template = Template(""" +package $plugin_package.$notification_package; + + /** + * Provides ${plugin_name}NotificationRegistry. + * <br>The file was generated by notification_gen.py based on $inputfile + * <br>(python representation of api file generated by vppapigen). + */ +public interface ${plugin_name}NotificationRegistryProvider extends $base_package.$notification_package.NotificationRegistryProvider { + + @Override + public ${plugin_name}NotificationRegistry getNotificationRegistry(); +} +""") + + +def generate_notification_registry(func_list, base_package, plugin_package, plugin_name, notification_package, callback_package, dto_package, inputfile): + """ Generates notification registry interface and implementation """ + print "Generating Notification interfaces and implementation" + + if not os.path.exists(notification_package): + os.mkdir(notification_package) + + callbacks = [] + register_callback_methods = [] + register_callback_methods_impl = [] + handler_methods = [] + for func in func_list: + + if not util.is_notification(func['name']): + continue + + camel_case_name_with_suffix = util.underscore_to_camelcase_upper(func['name']) + notification_dto = util.add_notification_suffix(camel_case_name_with_suffix) + callback_ifc = notification_dto + callback_gen.callback_suffix + fully_qualified_callback_ifc = "{0}.{1}.{2}".format(plugin_package, callback_package, callback_ifc) + callbacks.append(fully_qualified_callback_ifc) + + # TODO create NotificationListenerRegistration and return that instead of AutoCloseable to better indicate + # that the registration should be closed + register_callback_methods.append("java.lang.AutoCloseable register{0}({1} callback);" + .format(callback_ifc, fully_qualified_callback_ifc)) + register_callback_methods_impl.append(register_callback_impl_template.substitute(plugin_package=plugin_package, + callback_package=callback_package, + dto_package=dto_package, + notification=notification_dto, + callback=callback_ifc)) + handler_methods.append(handler_impl_template.substitute(base_package=base_package, + plugin_package=plugin_package, + callback_package=callback_package, + dto_package=dto_package, + notification=notification_dto, + callback=callback_ifc)) + + + callback_file = open(os.path.join(notification_package, "%sNotificationRegistry.java" % plugin_name), 'w') + callback_file.write(notification_registry_template.substitute(inputfile=inputfile, + register_callback_methods="\n ".join(register_callback_methods), + base_package=base_package, + plugin_package=plugin_package, + plugin_name=plugin_name, + notification_package=notification_package)) + callback_file.flush() + callback_file.close() + + callback_file = open(os.path.join(notification_package, "Global%sNotificationCallback.java" % plugin_name), 'w') + + global_notification_callback_callbacks = "" + if (callbacks): + global_notification_callback_callbacks = " extends " + ", ".join(callbacks) + + callback_file.write(global_notification_callback_template.substitute(inputfile=inputfile, + callbacks=global_notification_callback_callbacks, + plugin_package=plugin_package, + plugin_name=plugin_name, + notification_package=notification_package)) + callback_file.flush() + callback_file.close() + + callback_file = open(os.path.join(notification_package, "%sNotificationRegistryImpl.java" % plugin_name), 'w') + callback_file.write(notification_registry_impl_template.substitute(inputfile=inputfile, + callback_package=callback_package, + dto_package=dto_package, + register_callback_methods="".join(register_callback_methods_impl), + handler_methods="".join(handler_methods), + base_package=base_package, + plugin_package=plugin_package, + plugin_name=plugin_name, + notification_package=notification_package)) + callback_file.flush() + callback_file.close() + + callback_file = open(os.path.join(notification_package, "%sNotificationRegistryProvider.java" % plugin_name), 'w') + callback_file.write(notification_provider_template.substitute(inputfile=inputfile, + base_package=base_package, + plugin_package=plugin_package, + plugin_name=plugin_name, + notification_package=notification_package)) + callback_file.flush() + callback_file.close() + diff --git a/src/vpp-api/java/jvpp/gen/jvppgen/types_gen.py b/src/vpp-api/java/jvpp/gen/jvppgen/types_gen.py new file mode 100644 index 00000000..858ea8ba --- /dev/null +++ b/src/vpp-api/java/jvpp/gen/jvppgen/types_gen.py @@ -0,0 +1,232 @@ +#!/usr/bin/env python +# +# Copyright (c) 2016 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. + +import os +from string import Template + +import util +import jni_gen +import dto_gen + +type_template = Template(""" +package $plugin_package.$type_package; + +/** + * <p>This class represents $c_type_name type definition. + * <br>It was generated by types_gen.py based on $inputfile preparsed data: + * <pre> +$docs + * </pre> + */ +public final class $java_type_name { +$fields +$methods +} +""") + +field_template = Template(""" public $type $name;\n""") + + +def generate_type_fields(type_definition): + """ + Generates fields for class representing typeonly definition + :param type_definition: python representation of typeonly definition + :return: string representing class fields + """ + fields = "" + for t in zip(type_definition['types'], type_definition['args']): + field_name = util.underscore_to_camelcase(t[1]) + fields += field_template.substitute(type=util.jni_2_java_type_mapping[t[0]], + name=field_name) + return fields + +object_struct_setter_template = Template(""" + { + jclass ${field_reference_name}Class = (*env)->FindClass(env, "${class_FQN}"); + memset (&(mp->${c_name}), 0, sizeof (mp->${c_name})); + ${struct_initialization} + } +""") + +object_array_struct_setter_template = Template(""" + { + jclass ${field_reference_name}ArrayElementClass = (*env)->FindClass(env, "${class_FQN}"); + if (${field_reference_name}) { + size_t _i; + jsize cnt = (*env)->GetArrayLength (env, ${field_reference_name}); + ${field_length_check} + for (_i = 0; _i < cnt; _i++) { + jobject ${field_reference_name}ArrayElement = (*env)->GetObjectArrayElement(env, ${field_reference_name}, _i); + memset (&(mp->${c_name}[_i]), 0, sizeof (mp->${c_name}[_i])); + ${struct_initialization} + } + } + } +""") + +object_dto_field_setter_template = Template(""" + { + jclass ${field_reference_name}Class = (*env)->FindClass(env, "${class_FQN}"); + jmethodID ${field_reference_name}Constructor = (*env)->GetMethodID(env, ${field_reference_name}Class, "<init>", "()V"); + jobject ${field_reference_name} = (*env)->NewObject(env, ${field_reference_name}Class, ${field_reference_name}Constructor); + ${type_initialization} + (*env)->SetObjectField(env, dto, ${field_reference_name}FieldId, ${field_reference_name}); + (*env)->DeleteLocalRef(env, ${field_reference_name}); + } +""") + +object_array_dto_field_setter_template = Template(""" + { + jclass ${field_reference_name}Class = (*env)->FindClass(env, "${class_FQN}"); + jobjectArray ${field_reference_name} = (*env)->NewObjectArray(env, ${field_length}, ${field_reference_name}Class, 0); + jmethodID ${field_reference_name}Constructor = (*env)->GetMethodID(env, ${field_reference_name}Class, "<init>", "()V"); + unsigned int _i; + for (_i = 0; _i < ${field_length}; _i++) { + jobject ${field_reference_name}ArrayElement = (*env)->NewObject(env, ${field_reference_name}Class, ${field_reference_name}Constructor); + ${type_initialization} + (*env)->SetObjectArrayElement(env, ${field_reference_name}, _i, ${field_reference_name}ArrayElement); + (*env)->DeleteLocalRef(env, ${field_reference_name}ArrayElement); + } + (*env)->SetObjectField(env, dto, ${field_reference_name}FieldId, ${field_reference_name}); + (*env)->DeleteLocalRef(env, ${field_reference_name}); + } +""") + + +def generate_struct_initialization(type_def, c_name_prefix, object_name, indent): + struct_initialization = "" + # field identifiers + for t in zip(type_def['types'], type_def['args'], type_def['lengths']): + field_reference_name = "${c_name}" + util.underscore_to_camelcase_upper(t[1]) + field_name = util.underscore_to_camelcase(t[1]) + struct_initialization += jni_gen.jni_request_identifiers_for_type(field_type=t[0], + field_reference_name=field_reference_name, + field_name=field_name, + object_name=object_name) + struct_initialization += jni_gen.jni_request_binding_for_type(field_type=t[0], c_name=c_name_prefix + t[1], + field_reference_name=field_reference_name, + field_length=t[2][0], + is_variable_len_array=t[2][1]) + return indent + struct_initialization.replace('\n', '\n' + indent) + + +def generate_type_setter(handler_name, type_def, c_name_prefix, object_name, indent): + type_initialization = "" + for t in zip(type_def['types'], type_def['args'], type_def['lengths']): + field_length = t[2][0] + is_variable_len_array = t[2][1] + length_field_type = None + if is_variable_len_array: + length_field_type = type_def['types'][type_def['args'].index(field_length)] + type_initialization += jni_gen.jni_reply_handler_for_type(handler_name=handler_name, + ref_name="${field_reference_name}", + field_type=t[0], c_name=c_name_prefix + t[1], + field_reference_name="${c_name}" + util.underscore_to_camelcase_upper(t[1]), + field_name=util.underscore_to_camelcase(t[1]), + field_length=field_length, + is_variable_len_array=is_variable_len_array, + length_field_type=length_field_type, + object_name=object_name) + return indent + type_initialization.replace('\n', '\n' + indent) + + +def generate_types(types_list, plugin_package, types_package, inputfile): + """ + Generates Java representation of custom types defined in api file. + """ + + # + if not types_list: + print "Skipping custom types generation (%s does not define custom types)." % inputfile + return + + print "Generating custom types" + + if not os.path.exists(types_package): + os.mkdir(types_package) + + for type in types_list: + c_type_name = type['name'] + java_type_name = util.underscore_to_camelcase_upper(type['name']) + dto_path = os.path.join(types_package, java_type_name + ".java") + + fields = generate_type_fields(type) + + dto_file = open(dto_path, 'w') + dto_file.write(type_template.substitute(plugin_package=plugin_package, + type_package=types_package, + c_type_name=c_type_name, + inputfile=inputfile, + docs=util.api_message_to_javadoc(type), + java_type_name=java_type_name, + fields=fields, + methods=dto_gen.generate_dto_base_methods(java_type_name, type) + )) + + # update type mappings: + # todo fix vpe.api to use type_name instead of vl_api_type_name_t + type_name = "vl_api_" + c_type_name + "_t" + java_fqn = "%s.%s.%s" % (plugin_package, types_package, java_type_name) + util.vpp_2_jni_type_mapping[type_name] = "jobject" + util.vpp_2_jni_type_mapping[type_name + "[]"] = "jobjectArray" + util.jni_2_java_type_mapping[type_name] = java_fqn + util.jni_2_java_type_mapping[type_name + "[]"] = java_fqn + "[]" + jni_name = java_fqn.replace('.', "/") + jni_signature = "L" + jni_name + ";" + util.jni_2_signature_mapping[type_name] = "L" + jni_name + ";" + util.jni_2_signature_mapping[type_name + "[]"] = "[" + jni_signature + util.jni_field_accessors[type_name] = "ObjectField" + util.jni_field_accessors[type_name + "[]"] = "ObjectField" + + jni_gen.struct_setter_templates[type_name] = Template( + object_struct_setter_template.substitute( + c_name="${c_name}", + field_reference_name="${field_reference_name}", + class_FQN=jni_name, + struct_initialization=generate_struct_initialization(type, "${c_name}.", + "${field_reference_name}", ' ' * 4)) + ) + + jni_gen.struct_setter_templates[type_name+ "[]"] = Template( + object_array_struct_setter_template.substitute( + c_name="${c_name}", + field_reference_name="${field_reference_name}", + field_length_check="${field_length_check}", + class_FQN=jni_name, + struct_initialization=generate_struct_initialization(type, "${c_name}[_i].", + "${field_reference_name}ArrayElement", ' ' * 8)) + ) + + jni_gen.dto_field_setter_templates[type_name] = Template( + object_dto_field_setter_template.substitute( + field_reference_name="${field_reference_name}", + field_length="${field_length}", + class_FQN=jni_name, + type_initialization=generate_type_setter(c_type_name, type, "${c_name}.", + "${field_reference_name}", ' ' * 4)) + ) + + jni_gen.dto_field_setter_templates[type_name + "[]"] = Template( + object_array_dto_field_setter_template.substitute( + field_reference_name="${field_reference_name}", + field_length="${field_length}", + class_FQN=jni_name, + type_initialization=generate_type_setter(c_type_name, type, "${c_name}[_i].", + "${field_reference_name}ArrayElement", ' ' * 8)) + ) + + dto_file.flush() + dto_file.close() + diff --git a/src/vpp-api/java/jvpp/gen/jvppgen/util.py b/src/vpp-api/java/jvpp/gen/jvppgen/util.py new file mode 100644 index 00000000..42394419 --- /dev/null +++ b/src/vpp-api/java/jvpp/gen/jvppgen/util.py @@ -0,0 +1,212 @@ +#!/usr/bin/env python +# +# Copyright (c) 2016 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. + +import os, pprint +from os import removedirs + + +def underscore_to_camelcase(name): + name = name.title().replace("_", "") + return name[0].lower() + name[1:] + + +def underscore_to_camelcase_upper(name): + name = name.title().replace("_", "") + return name[0].upper() + name[1:] + + +def remove_folder(folder): + """ Remove folder with all its files """ + for root, dirs, files in os.walk(folder, topdown=False): + for name in files: + os.remove(os.path.join(root, name)) + removedirs(folder) + + +reply_suffixes = ("reply", "details", "l2fibtableentry") + + +def is_reply(name): + return name.lower().endswith(reply_suffixes) + + +def is_details(name): + return name.lower().endswith(reply_suffixes[1]) or name.lower().endswith(reply_suffixes[2]) + + +def is_retval_field(name): + return name == 'retval' + +dump_suffix = "dump" + + +def is_dump(name): + return name.lower().endswith(dump_suffix) + + +def get_reply_suffix(name): + for reply_suffix in reply_suffixes: + if name.lower().endswith(reply_suffix): + if reply_suffix == reply_suffixes[2]: + # FIXME workaround for l2_fib_table_entry + return 'entry' + else: + return reply_suffix + +# Mapping according to: +# http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/types.html +# +# Unsigned types are converted to signed java types that have the same size. +# It is the API user responsibility to interpret them correctly. +jni_2_java_type_mapping = {'u8': 'byte', + 'u8[]': 'byte[]', + 'i8': 'byte', + 'i8[]': 'byte[]', + 'u16': 'short', + 'u16[]': 'short[]', + 'i16': 'short', + 'i16[]': 'short[]', + 'u32': 'int', + 'u32[]': 'int[]', + 'i32': 'int', + 'i32[]': 'int[]', + 'u64': 'long', + 'u64[]': 'long[]', + 'i64': 'long', + 'i64[]': 'long[]', + 'f64': 'double', + 'f64[]': 'double[]' + } + +vpp_2_jni_type_mapping = {'u8': 'jbyte', + 'u8[]': 'jbyteArray', + 'i8': 'jbyte', + 'u8[]': 'jbyteArray', + 'u16': 'jshort', + 'u16[]': 'jshortArray', + 'i16': 'jshort', + 'i16[]': 'jshortArray', + 'u32': 'jint', + 'u32[]': 'jintArray', + 'i32': 'jint', + 'i32[]': 'jintArray', + 'u64': 'jlong', + 'u64[]': 'jlongArray', + 'i64': 'jlong', + 'i64[]': 'jlongArray', + 'f64': 'jdouble', + 'f64[]': 'jdoubleArray' + } + +# https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/types.html#type_signatures +jni_2_signature_mapping = {'u8': 'B', + 'u8[]': '[B', + 'i8': 'B', + 'i8[]': '[B', + 'u16': 'S', + 'u16[]': '[S', + 'i16': 'S', + 'i16[]': '[S', + 'u32': 'I', + 'u32[]': '[I', + 'i32': 'I', + 'i32[]': '[I', + 'u64': 'J', + 'u64[]': '[J', + 'i64': 'J', + 'i64[]': '[J', + 'f64': 'D', + 'f64[]': '[D' + } + +# https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#Get_type_Field_routines +jni_field_accessors = {'u8': 'ByteField', + 'u8[]': 'ObjectField', + 'i8': 'ByteField', + 'i8[]': 'ObjectField', + 'u16': 'ShortField', + 'u16[]': 'ObjectField', + 'i16': 'ShortField', + 'i16[]': 'ObjectField', + 'u32': 'IntField', + 'u32[]': 'ObjectField', + 'i32': 'IntField', + 'i32[]': 'ObjectField', + 'u64': 'LongField', + 'u64[]': 'ObjectField', + 'i64': 'LongField', + 'i64[]': 'ObjectField', + 'f64': 'DoubleField', + 'f64[]': 'ObjectField' + } + + +# vpe.api calls that do not follow naming conventions and have to be handled exceptionally when finding reply -> request mapping +# FIXME in vpe.api +unconventional_naming_rep_req = { + } + +# +# FIXME no convention in the naming of events (notifications) in vpe.api +notifications_message_suffixes = ("event", "counters") + +# messages that must be ignored. These messages are INSUFFICIENTLY marked as disabled in vpe.api +# FIXME +ignored_messages = [] + + +def is_notification(name): + """ Returns true if the structure is a notification regardless of its no other use """ + return is_just_notification(name) + + +def is_just_notification(name): + """ Returns true if the structure is just a notification and has no other use """ + return name.lower().endswith(notifications_message_suffixes) + + +def is_ignored(param): + return param.lower() in ignored_messages + + +def remove_reply_suffix(camel_case_name_with_suffix): + return remove_suffix(camel_case_name_with_suffix, get_reply_suffix(camel_case_name_with_suffix)) + + +def remove_suffix(camel_case_name_with_suffix, suffix): + suffix_length = len(suffix) + return camel_case_name_with_suffix[:-suffix_length] if suffix_length != 0 else camel_case_name_with_suffix + + +def is_control_ping(camel_case_name_with_suffix): + return camel_case_name_with_suffix.lower().startswith("controlping"); + + +def api_message_to_javadoc(api_message): + """ Converts vpe.api message description to javadoc """ + str = pprint.pformat(api_message, indent=4, width=120, depth=None) + return " * " + str.replace("\n", "\n * ") + + +notification_dto_suffix = "Notification" + + +def add_notification_suffix(camel_case_dto_name): + camel_case_dto_name += notification_dto_suffix + return camel_case_dto_name + + +def is_array(java_type_as_string): + return java_type_as_string.endswith("[]") diff --git a/src/vpp-api/lua/README.md b/src/vpp-api/lua/README.md new file mode 100644 index 00000000..4ecdb34d --- /dev/null +++ b/src/vpp-api/lua/README.md @@ -0,0 +1,50 @@ +This is the experimental version of Lua API, aimed for the luajit use. + +Please take a look and send the feedback to ayourtch@gmail.com. + +To run the examples here: + +1) install luajit - "sudo apt-get install luajit" on ubuntu + +2) "make build-vpp-api" in the top VPP directory + +3) "make run" in a separate terminal window + This ensures you have an instance of VPP running + +4) sudo luajit examples/example-cli.lua + +This will result in something like this: + +Version: +00000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ +00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ + +{ [1] = { ["luaapi_message_name"] = show_version_reply,["program"] = vpe,["version"] = ,["build_date"] = Fri Nov 25 10:58:48 UTC 2016,["retval"] = 0,["build_directory"] = /home/ubuntu/vpp,["_vl_msg_id"] = 170,["context"] = 0,} ,} +--- +{ [1] = { ["luaapi_message_name"] = cli_inband_reply,["_vl_msg_id"] = 94,["length"] = 66,["reply"] = vpp v built by ubuntu on vpp-toys at Fri Nov 25 10:58:48 UTC 2016 +,["retval"] = 0,["context"] = 0,} ,} +--- + +5) You can also run the performance test bench: + +$ sudo luajit bench.lua +10001 iterations, average speed 5624LL per second +10001 iterations, average speed 6650LL per second +10001 iterations, average speed 6053LL per second +10001 iterations, average speed 7056LL per second +10001 iterations, average speed 6388LL per second +10001 iterations, average speed 5849LL per second +10001 iterations, average speed 6321LL per second +10001 iterations, average speed 6368LL per second +10001 iterations, average speed 5958LL per second +10001 iterations, average speed 6482LL per second +Average tps across the tests: 6274LL + +Note: the above is run in an lxd container running inside 2-core +xhyve VM on a Macbook Pro, so I would not take the performance numbers for granted :) + +The "examples" directory contains a few naive examples, as well as a couple of more +advanced ones - a tab-completing CLI for VPP that can call both the APIs and CLI, +and also a small test utility which I use for automating some small tests using +VPP. + diff --git a/src/vpp-api/lua/bench.lua b/src/vpp-api/lua/bench.lua new file mode 100644 index 00000000..c7231b90 --- /dev/null +++ b/src/vpp-api/lua/bench.lua @@ -0,0 +1,70 @@ +--[[ +/* + * Copyright (c) 2016 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. + */ +]] + +local vpp = require "vpp-lapi" + +local ffi = require "ffi" + +ffi.cdef([[ + struct timespec { + long tv_sec; /* seconds */ + long tv_nsec; /* nanoseconds */ + }; + + int clock_gettime(int clk_id, struct timespec *tp); +]]) + + +local time_cache = ffi.new("struct timespec[1]") +local time_cache_1 = time_cache[0] +function get_ns() + ffi.C.clock_gettime(0, time_cache) + return time_cache_1.tv_nsec + 1000000000 * time_cache_1.tv_sec +end + +function do_bench() + local cycle_start = get_ns() + local n_iterations = 10000 + local count = 1 + for i = 1,n_iterations do + -- print(i) + vpp:api_call("show_version") + count = count + 1 + -- print(i, "done") + end + cycle_end = get_ns() + local tps = n_iterations*1000000000LL/(cycle_end - cycle_start) + print (tostring(count) .. " iterations, average speed " .. tostring(tps) .. " per second") + return tps +end + +root_dir = "/home/ubuntu/vpp" +pneum_path = root_dir .. "/build-root/install-vpp_debug-native/vpp-api/lib64/libpneum.so" +vpp:init({ pneum_path = pneum_path }) +vpp:json_api(root_dir .. "/build-root/install-vpp_debug-native/vpp/vpp-api/vpe.api.json") + +vpp:connect("lua-bench") +local n_tests = 10 +local tps_acc = 0LL +for i=1,n_tests do + tps_acc = tps_acc + do_bench() +end +print("Average tps across the tests: " .. tostring(tps_acc/n_tests)) + +vpp:disconnect() + + diff --git a/src/vpp-api/lua/examples/cli/README.md b/src/vpp-api/lua/examples/cli/README.md new file mode 100644 index 00000000..3a5f8ee9 --- /dev/null +++ b/src/vpp-api/lua/examples/cli/README.md @@ -0,0 +1,5 @@ +This is a small experiment to have a wrapper CLI which can call both API functions as well as debug CLI. + +To facilitate tab completion and help, the API call names are broken up with spaces replacing the underscores. + + diff --git a/src/vpp-api/lua/examples/cli/lua-cli.lua b/src/vpp-api/lua/examples/cli/lua-cli.lua new file mode 100644 index 00000000..4a27af53 --- /dev/null +++ b/src/vpp-api/lua/examples/cli/lua-cli.lua @@ -0,0 +1,747 @@ +--[[ +/* + * Copyright (c) 2016 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. + */ +]] + +-- Experimental prototype CLI using API to VPP, with tab completion +-- +-- Written by Andrew Yourtchenko (ayourtch@cisco.com) 2010,2016 +-- + +vpp = require "vpp-lapi" + + +local dotdotdot = "..." + +-- First the "readline" routine + +readln = { +split = function(str, pat) + local t = {} -- NOTE: use {n = 0} in Lua-5.0 + local fpat = "(.-)" .. pat + local last_end = 1 + if str then + local s, e, cap = str:find(fpat, 1) + while s do + if s ~= 1 or cap ~= "" then + table.insert(t,cap) + end + last_end = e+1 + s, e, cap = str:find(fpat, last_end) + end + if last_end <= #str then + cap = str:sub(last_end) + table.insert(t, cap) + end + end + return t +end, + +reader = function() + local rl = {} + + rl.init = function() + os.execute("stty -icanon min 1 -echo") + rl.rawmode = true + end + + rl.done = function() + os.execute("stty icanon echo") + rl.rawmode = false + end + + rl.prompt = ">" + rl.history = { "" } + rl.history_index = 1 + rl.history_length = 1 + + rl.hide_cmd = function() + local bs = string.char(8) .. " " .. string.char(8) + for i = 1, #rl.command do + io.stdout:write(bs) + end + end + + rl.show_cmd = function() + if rl.command then + io.stdout:write(rl.command) + end + end + + rl.store_history = function(cmd) + if cmd == "" then + return + end + rl.history[rl.history_length] = cmd + rl.history_length = rl.history_length + 1 + rl.history_index = rl.history_length + rl.history[rl.history_length] = "" + end + + rl.readln = function() + local done = false + local need_prompt = true + rl.command = "" + + if not rl.rawmode then + rl.init() + end + + while not done do + if need_prompt then + io.stdout:write(rl.prompt) + io.stdout:write(rl.command) + need_prompt = false + end + + local ch = io.stdin:read(1) + if ch:byte(1) == 27 then + -- CONTROL + local ch2 = io.stdin:read(1) + -- arrows + if ch2:byte(1) == 91 then + local ch3 = io.stdin:read(1) + local b = ch3:byte(1) + if b == 65 then + ch = "UP" + elseif b == 66 then + ch = "DOWN" + elseif b == 67 then + ch = "RIGHT" + elseif b == 68 then + ch = "LEFT" + end + -- print("Byte: " .. ch3:byte(1)) + -- if ch3:byte(1) + end + end + + if ch == "?" then + io.stdout:write(ch) + io.stdout:write("\n") + if rl.help then + rl.help(rl) + end + need_prompt = true + elseif ch == "\t" then + if rl.tab_complete then + rl.tab_complete(rl) + end + io.stdout:write("\n") + need_prompt = true + elseif ch == "\n" then + io.stdout:write(ch) + done = true + elseif ch == "\004" then + io.stdout:write("\n") + rl.command = nil + done = true + elseif ch == string.char(127) then + if rl.command ~= "" then + io.stdout:write(string.char(8) .. " " .. string.char(8)) + rl.command = string.sub(rl.command, 1, -2) + end + elseif #ch > 1 then + -- control char + if ch == "UP" then + rl.hide_cmd() + if rl.history_index == #rl.history then + rl.history[rl.history_index] = rl.command + end + if rl.history_index > 1 then + rl.history_index = rl.history_index - 1 + rl.command = rl.history[rl.history_index] + end + rl.show_cmd() + elseif ch == "DOWN" then + rl.hide_cmd() + if rl.history_index < rl.history_length then + rl.history_index = rl.history_index + 1 + rl.command = rl.history[rl.history_index] + end + rl.show_cmd() + end + else + io.stdout:write(ch) + rl.command = rl.command .. ch + end + end + if rl.command then + rl.store_history(rl.command) + end + return rl.command + end + return rl +end + +} + +--[[ + +r = reader() + +local done = false + +while not done do + local cmd = r.readln() + print("Command: " .. tostring(cmd)) + if not cmd or cmd == "quit" then + done = true + end +end + +r.done() + +]] + +--------- MDS show tech parser + +local print_section = nil +local list_sections = false + +local curr_section = "---" +local curr_parser = nil + +-- by default operate in batch mode +local batch_mode = true + +local db = {} +local device = {} +device.output = {} +local seen_section = {} + +function start_collection(name) + device = {} + seen_section = {} +end + +function print_error(errmsg) + print("@#$:" .. errmsg) +end + +function keys(tbl) + local t = {} + for k, v in pairs(tbl) do + table.insert(t, k) + end + return t +end + +function tset (parent, ...) + + -- print ('set', ...) + + local len = select ('#', ...) + local key, value = select (len-1, ...) + local cutpoint, cutkey + + for i=1,len-2 do + + local key = select (i, ...) + local child = parent[key] + + if value == nil then + if child == nil then return + elseif next (child, next (child)) then cutpoint = nil cutkey = nil + elseif cutpoint == nil then cutpoint = parent cutkey = key end + + elseif child == nil then child = {} parent[key] = child end + + parent = child + end + + if value == nil and cutpoint then cutpoint[cutkey] = nil + else parent[key] = value return value end + end + + +function tget (parent, ...) + local len = select ('#', ...) + for i=1,len do + parent = parent[select (i, ...)] + if parent == nil then break end + end + return parent + end + + +local pager_lines = 23 +local pager_printed = 0 +local pager_skipping = false +local pager_filter_pipe = nil + +function pager_reset() + pager_printed = 0 + pager_skipping = false + if pager_filter_pipe then + pager_filter_pipe:close() + pager_filter_pipe = nil + end +end + + +function print_more() + io.stdout:write(" --More-- ") +end + +function print_nomore() + local bs = string.char(8) + local bs10 = bs .. bs .. bs .. bs .. bs .. bs .. bs .. bs .. bs .. bs + io.stdout:write(bs10 .. " " .. bs10) +end + +function print_line(txt) + if pager_filter_pipe then + pager_filter_pipe:write(txt .. "\n") + return + end + if pager_printed >= pager_lines then + print_more() + local ch = io.stdin:read(1) + if ch == " " then + pager_printed = 0 + elseif ch == "\n" then + pager_printed = pager_printed - 1 + elseif ch == "q" then + pager_printed = 0 + pager_skipping = true + end + print_nomore() + end + if not pager_skipping then + print(txt) + pager_printed = pager_printed + 1 + else + -- skip printing + end +end + +function paged_write(text) + local t = readln.split(text, "[\n]") + if string.sub(text, -1) == "\n" then + table.insert(t, "") + end + for i, v in ipairs(t) do + if i < #t then + print_line(v) + else + if pager_filter_pipe then + pager_filter_pipe:write(v) + else + io.stdout:write(v) + end + end + end +end + + + + + +function get_choices(tbl, key) + local res = {} + for k, v in pairs(tbl) do + if string.sub(k, 1, #key) == key then + table.insert(res, k) + elseif 0 < #key and dotdotdot == k then + table.insert(res, k) + end + end + return res +end + +function get_exact_choice(choices, val) + local exact_idx = nil + local substr_idx = nil + local substr_seen = false + + if #choices == 1 then + if choices[1] == dotdotdot then + return 1 + elseif string.sub(choices[1], 1, #val) == val then + return 1 + else + return nil + end + else + for i, v in ipairs(choices) do + if v == val then + exact_idx = i + substr_seen = true + elseif choices[i] ~= dotdotdot and string.sub(choices[i], 1, #val) == val then + if substr_seen then + substr_idx = nil + else + substr_idx = i + substr_seen = true + end + elseif choices[i] == dotdotdot then + if substr_seen then + substr_idx = nil + else + substr_idx = i + substr_seen = true + end + end + end + end + return exact_idx or substr_idx +end + +function device_cli_help(rl) + local key = readln.split(rl.command, "[ ]+") + local tree = rl.tree + local keylen = #key + local fullcmd = "" + local error = false + local terse = true + + if ((#rl.command >= 1) and (string.sub(rl.command, -1) == " ")) or (#rl.command == 0) then + table.insert(key, "") + terse = false + end + + for i, v in ipairs(key) do + local choices = get_choices(tree, v) + local idx = get_exact_choice(choices, v) + if idx then + local choice = choices[idx] + tree = tree[choice] + fullcmd = fullcmd .. choice .. " " + else + if i < #key then + error = true + end + end + + if i == #key and not error then + for j, w in ipairs(choices) do + if terse then + paged_write(w .. "\t") + else + paged_write(" " .. w .. "\n") + end + end + paged_write("\n") + if terse then + paged_write(" \n") + end + end + end + pager_reset() +end + +function device_cli_tab_complete(rl) + local key = readln.split(rl.command, "[ ]+") + local tree = rl.tree + local keylen = #key + local fullcmd = "" + local error = false + + for i, v in ipairs(key) do + local choices = get_choices(tree, v) + local idx = get_exact_choice(choices, v) + if idx and choices[idx] ~= dotdotdot then + local choice = choices[idx] + tree = tree[choice] + -- print("level " .. i .. " '" .. choice .. "'") + fullcmd = fullcmd .. choice .. " " + else + -- print("level " .. i .. " : " .. table.concat(choices, " ") .. " ") + error = true + end + end + if not error then + rl.command = fullcmd + else + -- print("\n\nerror\n") + end + pager_reset() +end + +function device_cli_exec(rl) + + local cmd_nopipe = rl.command + local cmd_pipe = nil + + local pipe1, pipe2 = string.find(rl.command, "[|]") + if pipe1 then + cmd_nopipe = string.sub(rl.command, 1, pipe1-1) + cmd_pipe = string.sub(rl.command, pipe2+1, -1) + end + + local key = readln.split(cmd_nopipe .. " <cr>", "[ ]+") + local tree = rl.tree + local keylen = #key + local fullcmd = "" + local error = false + local func = nil + + if cmd_pipe then + pager_filter_pipe = io.popen(cmd_pipe, "w") + end + + + rl.choices = {} + + for i, v in ipairs(key) do + local choices = get_choices(tree, v) + local idx = get_exact_choice(choices, v) + if idx then + local choice = choices[idx] + if i == #key then + func = tree[choice] + else + if choice == dotdotdot then + -- keep the tree the same, update the choice value to match the input string + choices[idx] = v + choice = v + else + tree = tree[choice] + end + end + -- print("level " .. i .. " '" .. choice .. "'") + table.insert(rl.choices, choice) + else + -- print("level " .. i .. " : " .. table.concat(choices, " ") .. " ") + error = true + return nil + end + end + return func +end + +function populate_tree(commands) + local tree = {} + + for k, v in pairs(commands) do + local key = readln.split(k .. " <cr>", "[ ]+") + local xtree = tree + for i, kk in ipairs(key) do + if i == 1 and kk == "sh" then + kk = "show" + end + if i == #key then + if type(v) == "function" then + xtree[kk] = v + else + xtree[kk] = function(rl) paged_write(table.concat(v, "\n") .. "\n") end + end + else + if not xtree[kk] then + xtree[kk] = {} + end + xtree = xtree[kk] + end + end + end + return tree +end + +function trim (s) + return (string.gsub(s, "^%s*(.-)%s*$", "%1")) +end + + +function init_vpp(vpp) + local root_dir = "/home/ubuntu/vpp" + local pneum_path = root_dir .. "/build-root/install-vpp_debug-native/vpp-api/lib64/libpneum.so" + + vpp:init({ pneum_path = pneum_path }) + + vpp:init({ pneum_path = pneum_path }) + vpp:json_api(root_dir .. "/build-root/install-vpp_debug-native/vpp/vpp-api/vpe.api.json") + + + + vpp:connect("lua_cli") +end + +function run_cli(vpp, cli) + local reply = vpp:api_call("cli_inband", { cmd = cli }) + if reply and #reply == 1 then + local rep = reply[1] + if 0 == rep.retval then + return rep.reply + else + return "XXXXXLUACLI: API RETVAL ERROR : " .. tostring(rep.retval) + end + else + return "XXXXXLUACLI ERROR, RAW REPLY: " .. vpp.dump(reply) + end +end + + +function toprintablestring(s) + if type(s) == "string" then + return "\n"..vpp.hex_dump(s) + else + return tostring(s) + end +end + +function interactive_cli(r) + while not done do + pager_reset() + local cmd = r.readln() + if not cmd then + done = true + elseif cmd == "quit" or cmd == "exit" then + done = true + else + local func = device_cli_exec(r) + if func then + func(r) + else + if trim(cmd) == "" then + else + for i = 1, #r.prompt do + paged_write(" ") + end + paged_write("^\n% Invalid input detected at '^' marker.\n\n") + end + end + end + end +end + +device = {} +device.output = {} + +init_vpp(vpp) +cmds_str = run_cli(vpp, "?") +vpp_cmds = readln.split(cmds_str, "\n") +vpp_clis = {} + +for linenum, line in ipairs(vpp_cmds) do + local m,h = string.match(line, "^ (.-) (.*)$") + if m and #m > 0 then + table.insert(vpp_clis, m) + device.output["vpp debug cli " .. m] = function(rl) + -- print("ARBITRARY CLI" .. vpp.dump(rl.choices)) + print("LUACLI command: " .. table.concat(rl.choices, " ")) + local sub = {} + -- + for i=4, #rl.choices -1 do + table.insert(sub, rl.choices[i]) + end + local cli = table.concat(sub, " ") + print("Running CLI: " .. tostring(cli)) + paged_write(run_cli(vpp, cli)) + end + device.output["vpp debug cli " .. m .. " " .. dotdotdot] = function(rl) + print("ARGH") + end + + local ret = run_cli(vpp, "help " .. m) + device.output["help vpp debug cli " .. m] = { ret } + end +end + +for linenum, line in ipairs(vpp_clis) do + -- print(line, ret) +end + +for msgnum, msgname in pairs(vpp.msg_number_to_name) do + local cli, numspaces = string.gsub(msgname, "_", " ") + device.output["call " .. cli .. " " .. dotdotdot] = function(rl) + print("ARGH") + end + device.output["call " .. cli] = function(rl) + print("LUACLI command: " .. table.concat(rl.choices, " ")) + print("Running API: " .. msgname) -- vpp.dump(rl.choices)) + local out = {} + local args = {} + local ntaken = 0 + local argname = "" + for i=(1+1+numspaces+1), #rl.choices-1 do + -- print(i, rl.choices[i]) + if ntaken > 0 then + ntaken = ntaken -1 + else + local fieldname = rl.choices[i] + local field = vpp.msg_name_to_fields[msgname][fieldname] + if field then + local s = rl.choices[i+1] + s=s:gsub("\\x(%x%x)",function (x) return string.char(tonumber(x,16)) end) + args[fieldname] = s + ntaken = 1 + end + end + end + -- print("ARGS: ", vpp.dump(args)) + local ret = vpp:api_call(msgname, args) + for i, reply in ipairs(ret) do + table.insert(out, "=================== Entry #" .. tostring(i)) + for k, v in pairs(reply) do + table.insert(out, " " .. tostring(k) .. " : " .. toprintablestring(v)) + end + end + -- paged_write(vpp.dump(ret) .. "\n\n") + paged_write(table.concat(out, "\n").."\n\n") + end + device.output["call " .. cli .. " help"] = function(rl) + local out = {} + for k, v in pairs(vpp.msg_name_to_fields[msgname]) do + table.insert(out, tostring(k) .. " : " .. v["ctype"] .. " ; " .. tostring(vpp.dump(v)) ) + end + -- paged_write(vpp.dump(vpp.msg_name_to_fields[msgname]) .. "\n\n") + paged_write(table.concat(out, "\n").."\n\n") + end +-- vpp.msg_name_to_number = {} +end + + + +local r = readln.reader() +local done = false + +r.prompt = "VPP(luaCLI)#" + +r.help = device_cli_help +r.tab_complete = device_cli_tab_complete +print("===== CLI view, use ^D to end =====") + +r.tree = populate_tree(device.output) +-- readln.pretty("xxxx", r.tree) + + +for idx, an_arg in ipairs(arg) do + local fname = an_arg + if fname == "-i" then + pager_lines = 23 + interactive_cli(r) + else + pager_lines = 100000000 + for line in io.lines(fname) do + r.command = line + local func = device_cli_exec(r) + if func then + func(r) + end + end + end +end + +if #arg == 0 then + print("You should specify '-i' as an argument for the interactive session,") + print("but with no other sources of commands, we start interactive session now anyway") + interactive_cli(r) +end + +vpp:disconnect() +r.done() + + diff --git a/src/vpp-api/lua/examples/example-acl-plugin.lua b/src/vpp-api/lua/examples/example-acl-plugin.lua new file mode 100644 index 00000000..ca01f18d --- /dev/null +++ b/src/vpp-api/lua/examples/example-acl-plugin.lua @@ -0,0 +1,110 @@ +--[[ +/* + * Copyright (c) 2016 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. + */ +]] + + +vpp = require "vpp-lapi" + +root_dir = "/home/ubuntu/vpp" +pneum_path = root_dir .. "/build-root/install-vpp_debug-native/vpp-api/lib64/libpneum.so" + +vpp:init({ pneum_path = pneum_path }) + +vpp:consume_api(root_dir .. "/build-root/install-vpp_debug-native/vlib-api/vlibmemory/memclnt.api") +vpp:consume_api(root_dir .. "/build-root/install-vpp_debug-native/vpp/vpp-api/vpe.api") +vpp:connect("aytest") +vpp:consume_api(root_dir .. "/plugins/acl-plugin/acl/acl.api", "acl") + +-- api calls +reply = vpp:api_call("show_version") +print("Version: ", reply[1].version) +print(vpp.hex_dump(reply[1].version)) +print(vpp.dump(reply)) +print("---") + +reply = vpp:api_call("acl_del", { context = 42, acl_index = 230 }) +print(vpp.dump(reply)) +print("---") + +reply = vpp:api_call("acl_del", { context = 42, acl_index = 8 }) +print(vpp.dump(reply)) +print("---") + +reply = vpp:api_call("acl_del", { context = 42, acl_index = 15 }) +print(vpp.dump(reply)) +print("---") + +reply = vpp:api_call("acl_add", { context = 42, count = 2, r = { { is_permit = 1, is_ipv6 = 1 }, { is_permit = 0, is_ipv6 = 1 } } }) +print(vpp.dump(reply)) +print("---") +interface_acl_in = reply[1].acl_index + +reply = vpp:api_call("acl_add", { context = 42, count = 3, r = { { is_permit = 1, is_ipv6 = 1 }, { is_permit = 0, is_ipv6 = 1 }, { is_permit = 1, is_ipv6 = 0 } } }) +print(vpp.dump(reply)) +print("---") +interface_acl_out = reply[1].acl_index + + +reply = vpp:api_call("acl_interface_add_del", { context = 42, sw_if_index = 0, is_add = 1, is_input = 1, acl_index = interface_acl_in }) +print(vpp.dump(reply)) +print("---") +reply = vpp:api_call("acl_interface_add_del", { context = 42, sw_if_index = 0, is_add = 1, is_input = 1, acl_index = interface_acl_in }) +print(vpp.dump(reply)) +print("---") + +reply = vpp:api_call("acl_interface_add_del", { context = 42, sw_if_index = 0, is_add = 1, is_input = 0, acl_index = interface_acl_out }) +print(vpp.dump(reply)) +print("---") +reply = vpp:api_call("acl_interface_add_del", { context = 42, sw_if_index = 0, is_add = 1, is_input = 0, acl_index = interface_acl_out }) +print(vpp.dump(reply)) +print("---") + +reply = vpp:api_call("acl_add", { context = 42, count = 0 }) +print(vpp.dump(reply)) +print("---") + +acl_index_to_delete = reply[1].acl_index +print("Deleting " .. tostring(acl_index_to_delete)) +reply = vpp:api_call("acl_del", { context = 42, acl_index = acl_index_to_delete }) +print(vpp.dump(reply)) +print("---") + +reply = vpp:api_call("acl_dump", { context = 42, sw_if_index = 0}) +for ri, rv in ipairs(reply) do + print("Reply message #" .. tostring(ri)) + print(vpp.dump(rv)) + for ai, av in ipairs(rv.r) do + print("ACL rule #" .. tostring(ai) .. " : " .. vpp.dump(av)) + end + +end +print("---") + +reply = vpp:api_call("acl_del", { context = 42, acl_index = interface_acl_out }) +print(vpp.dump(reply)) +print("---") +reply = vpp:api_call("acl_del", { context = 42, acl_index = interface_acl_in }) +print(vpp.dump(reply)) +print("---") + +reply = vpp:api_call("acl_dump", { context = 42, sw_if_index = 0}) +print(vpp.dump(reply)) +print("---") + + +vpp:disconnect() + + diff --git a/src/vpp-api/lua/examples/example-classifier.lua b/src/vpp-api/lua/examples/example-classifier.lua new file mode 100644 index 00000000..b1270757 --- /dev/null +++ b/src/vpp-api/lua/examples/example-classifier.lua @@ -0,0 +1,51 @@ +--[[ +/* + * Copyright (c) 2016 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. + */ +]] + + +local vpp = require "vpp-lapi" +local bit = require("bit") + +root_dir = "/home/ubuntu/vpp" +pneum_path = root_dir .. "/build-root/install-vpp_debug-native/vpp-api/lib64/libpneum.so" + + +vpp:init({ pneum_path = pneum_path }) + +vpp:json_api(root_dir .. "/build-root/install-vpp_debug-native/vpp/vpp-api/vpe.api.json") + +vpp:connect("aytest") + +-- api calls + +print("Calling API to add a new classifier table") +reply = vpp:api_call("classify_add_del_table", { + context = 43, + memory_size = bit.lshift(2, 20), + client_index = 42, + is_add = 1, + nbuckets = 32, + skip_n_vectors = 0, + match_n_vectors = 1, + mask = "\255\255\255\255\255\255\255\255" .. "\255\255\255\255\255\255\255\255" +}) +print(vpp.dump(reply)) +print("---") + + +vpp:disconnect() + + diff --git a/src/vpp-api/lua/examples/example-cli.lua b/src/vpp-api/lua/examples/example-cli.lua new file mode 100644 index 00000000..85425caf --- /dev/null +++ b/src/vpp-api/lua/examples/example-cli.lua @@ -0,0 +1,44 @@ +--[[ +/* + * Copyright (c) 2016 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. + */ +]] + +vpp = require "vpp-lapi" + +root_dir = "/home/ubuntu/vpp" +pneum_path = root_dir .. "/build-root/install-vpp_debug-native/vpp-api/lib64/libpneum.so" + +vpp:init({ pneum_path = pneum_path }) + +vpp:json_api(root_dir .. "/build-root/install-vpp_debug-native/vpp/vpp-api/vpe.api.json") + +vpp:connect("aytest") + +-- api calls +reply = vpp:api_call("show_version") +print("Version: ", reply[1].version) +print(vpp.hex_dump(reply[1].version)) +print(vpp.dump(reply)) +print("---") + + +reply = vpp:api_call("cli_inband", { cmd = "show vers" }) +print(vpp.dump(reply)) +print("---") + + +vpp:disconnect() + + diff --git a/src/vpp-api/lua/examples/lute/README.md b/src/vpp-api/lua/examples/lute/README.md new file mode 100644 index 00000000..8d37250a --- /dev/null +++ b/src/vpp-api/lua/examples/lute/README.md @@ -0,0 +1,66 @@ +LUTE: Lua Unit Test Environment + +This is a small helper utility to automate some simple tests +that one might need to do. + +Think of it as a hybrid of a screen and expect who +also took some habits from HTML inline code. + +It is quite probably useless for building anything serious, +but practice shows it is quite efficient at allowing +convenient temporary quick tests, and for something +that was written over a course of a couple of evenings it +is quite a nice little helper tool. + +It allows do launch and drive multiple shell sessions, +and by virtue of having been written in Lua, it of course +also allows to add the business logic using the Lua code. + +If you launch the lute without parameters, it gives you +the interactive shell to execute the commands in. + +If you launch it with an argument, it will attempt to +read and execute the commands from the file. + +Commands: + +shell FOO + + spawn a shell in a new PTY under the label FOO. + +run FOO bar + + Send "bar" keystrokes followed by "ENTER" to the session FOO + + Special case: "break" word on its own gets translated into ^C being sent. + +cd FOO + + "change domain" into session FOO. All subsequent inputs will go, + line-buffered, into the session FOO. To jump back up, use ^D (Control-D), + or within the file, use ^D^D^D (caret D caret D caret D on its own line) + +expect FOO blablabla + + Pause further interpretation of the batch mode until you see "blablabla" + in the output of session FOO, or until timeout happens. + +sleep N + + Sleep an integer N seconds, if you are in batch mode. + +echo blabla + + Echo the remainder of the line to standard output. + +For Lua code, there is a pre-existing pseudo-session called "lua", +which accepts "run lua" command which does what you would expect +(evaluate the rest of the string in Lua context - being the same +as lute itself). Also you can do "cd lua" and get into a +multiline-enabled interpreter shell. + +This way for the VPP case you can automate some of the things in your routine +that you would have to have done manually, and test drive API as well +as use the realistic native OS components to create the environment around it. + + diff --git a/src/vpp-api/lua/examples/lute/lute.lua b/src/vpp-api/lua/examples/lute/lute.lua new file mode 100644 index 00000000..89b9924b --- /dev/null +++ b/src/vpp-api/lua/examples/lute/lute.lua @@ -0,0 +1,777 @@ +--[[ +version = 1 +/* + * Copyright (c) 2016 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. + */ +]] + +-- LUTE: Lua Unit Test Environment +-- AKA what happens when screen tries to marry with lua and expect, +-- but escapes mid-ceremony. +-- +-- comments: @ayourtch + +ffi = require("ffi") + +vpp = {} +function vpp.dump(o) + if type(o) == 'table' then + local s = '{ ' + for k,v in pairs(o) do + if type(k) ~= 'number' then k = '"'..k..'"' end + s = s .. '['..k..'] = ' .. vpp.dump(v) .. ',' + end + return s .. '} ' + else + return tostring(o) + end +end + + +ffi.cdef([[ + +int posix_openpt(int flags); +int grantpt(int fd); +int unlockpt(int fd); +char *ptsname(int fd); + +typedef long pid_t; +typedef long ssize_t; +typedef long size_t; +typedef int nfds_t; +typedef long time_t; +typedef long suseconds_t; + +pid_t fork(void); +pid_t setsid(void); + +int close(int fd); +int open(char *pathname, int flags); + +int dup2(int oldfd, int newfd); + +ssize_t read(int fd, void *buf, size_t count); +ssize_t write(int fd, const void *buf, size_t count); + +struct pollfd { + int fd; /* file descriptor */ + short events; /* requested events */ + short revents; /* returned events */ + }; + +int poll(struct pollfd *fds, nfds_t nfds, int timeout); + +struct timeval { + time_t tv_sec; /* seconds */ + suseconds_t tv_usec; /* microseconds */ + }; + +int gettimeofday(struct timeval *tv, struct timezone *tz); + +int inet_pton(int af, const char *src, void *dst); + +]]) + +ffi.cdef([[ +void *memset(void *s, int c, size_t n); +void *memcpy(void *dest, void *src, size_t n); +void *memmove(void *dest, const void *src, size_t n); +void *memmem(const void *haystack, size_t haystacklen, + const void *needle, size_t needlelen); +]]) + + + +local O_RDWR = 2 + + +function os_time() + local tv = ffi.new("struct timeval[1]") + local ret = ffi.C.gettimeofday(tv, nil) + return tonumber(tv[0].tv_sec) + (tonumber(tv[0].tv_usec)/1000000.0) +end + +function sleep(n) + local when_wakeup = os_time() + n + while os_time() <= when_wakeup do + ffi.C.poll(nil, 0, 10) + end +end + + +function c_str(text_in) + local text = text_in + local c_str = ffi.new("char[?]", #text+1) + ffi.copy(c_str, text) + return c_str +end + +function ip46(addr_text) + local out = ffi.new("char [200]") + local AF_INET6 = 10 + local AF_INET = 2 + local is_ip6 = ffi.C.inet_pton(AF_INET6, c_str(addr_text), out) + if is_ip6 == 1 then + return ffi.string(out, 16), true + end + local is_ip4 = ffi.C.inet_pton(AF_INET, c_str(addr_text), out) + if is_ip4 then + return (string.rep("4", 12).. ffi.string(out, 4)), false + end +end + +function pty_master_open() + local fd = ffi.C.posix_openpt(O_RDWR) + ffi.C.grantpt(fd) + ffi.C.unlockpt(fd) + local p = ffi.C.ptsname(fd) + print("PTS:" .. ffi.string(p)) + return fd, ffi.string(p) +end + +function pty_run(cmd) + local master_fd, pts_name = pty_master_open() + local child_pid = ffi.C.fork() + if (child_pid == -1) then + print("Error fork()ing") + return -1 + end + + if child_pid ~= 0 then + -- print("Parent") + return master_fd, child_pid + end + + -- print("Child") + if (ffi.C.setsid() == -1) then + print("Child error setsid") + os.exit(-1) + end + + ffi.C.close(master_fd) + + local slave_fd = ffi.C.open(c_str(pts_name), O_RDWR) + if slave_fd == -1 then + print("Child can not open slave fd") + os.exit(-2) + end + + ffi.C.dup2(slave_fd, 0) + ffi.C.dup2(slave_fd, 1) + ffi.C.dup2(slave_fd, 2) + os.execute(cmd) +end + +function readch() + local buf = ffi.new("char[1]") + local nread= ffi.C.read(0, buf, 1) + -- print("\nREADCH : " .. string.char(buf[0])) + return string.char(buf[0]) +end + +function stdout_write(str) + ffi.C.write(1, c_str(str), #str) +end + + +readln = { +split = function(str, pat) + local t = {} -- NOTE: use {n = 0} in Lua-5.0 + local fpat = "(.-)" .. pat + local last_end = 1 + if str then + local s, e, cap = str:find(fpat, 1) + while s do + if s ~= 1 or cap ~= "" then + table.insert(t,cap) + end + last_end = e+1 + s, e, cap = str:find(fpat, last_end) + end + if last_end <= #str then + cap = str:sub(last_end) + table.insert(t, cap) + end + end + return t +end, + +reader = function() + local rl = {} + + rl.init = function() + os.execute("stty -icanon min 1 -echo") + rl.rawmode = true + end + + rl.done = function() + os.execute("stty icanon echo") + rl.rawmode = false + end + + rl.prompt = ">" + rl.history = { "" } + rl.history_index = 1 + rl.history_length = 1 + + rl.hide_cmd = function() + local bs = string.char(8) .. " " .. string.char(8) + for i = 1, #rl.command do + stdout_write(bs) + end + end + + rl.show_cmd = function() + if rl.command then + stdout_write(rl.command) + end + end + + rl.store_history = function(cmd) + if cmd == "" then + return + end + rl.history[rl.history_length] = cmd + rl.history_length = rl.history_length + 1 + rl.history_index = rl.history_length + rl.history[rl.history_length] = "" + end + + rl.readln = function(stdin_select_fn, batch_cmd, batch_when, batch_expect) + local done = false + local need_prompt = true + rl.command = "" + + if not rl.rawmode then + rl.init() + end + + while not done do + local indent_value = #rl.prompt + #rl.command + if need_prompt then + stdout_write(rl.prompt) + stdout_write(rl.command) + need_prompt = false + end + if type(stdin_select_fn) == "function" then + while not stdin_select_fn(indent_value, batch_cmd, batch_when, batch_expect) do + stdout_write(rl.prompt) + stdout_write(rl.command) + indent_value = #rl.prompt + #rl.command + end + if batch_cmd and ((os_time() > batch_when) or (batch_expect and expect_success(batch_expect, buf, 0))) then + stdout_write("\n" .. rl.prompt .. batch_cmd .. "\n") + if batch_expect then + expect_done(batch_expect) + end + return batch_cmd, batch_expect + end + end + local ch = readch() + if ch:byte(1) == 27 then + -- CONTROL + local ch2 = readch() + -- arrows + if ch2:byte(1) == 91 then + local ch3 = readch() + local b = ch3:byte(1) + if b == 65 then + ch = "UP" + elseif b == 66 then + ch = "DOWN" + elseif b == 67 then + ch = "RIGHT" + elseif b == 68 then + ch = "LEFT" + end + -- print("Byte: " .. ch3:byte(1)) + -- if ch3:byte(1) + end + end + + if ch == "?" then + stdout_write(ch) + stdout_write("\n") + if rl.help then + rl.help(rl) + end + need_prompt = true + elseif ch == "\t" then + if rl.tab_complete then + rl.tab_complete(rl) + end + stdout_write("\n") + need_prompt = true + elseif ch == "\n" then + stdout_write(ch) + done = true + elseif ch == "\004" then + stdout_write("\n") + rl.command = nil + done = true + elseif ch == string.char(127) then + if rl.command ~= "" then + stdout_write(string.char(8) .. " " .. string.char(8)) + rl.command = string.sub(rl.command, 1, -2) + end + elseif #ch > 1 then + -- control char + if ch == "UP" then + rl.hide_cmd() + if rl.history_index == #rl.history then + rl.history[rl.history_index] = rl.command + end + if rl.history_index > 1 then + rl.history_index = rl.history_index - 1 + rl.command = rl.history[rl.history_index] + end + rl.show_cmd() + elseif ch == "DOWN" then + rl.hide_cmd() + if rl.history_index < rl.history_length then + rl.history_index = rl.history_index + 1 + rl.command = rl.history[rl.history_index] + end + rl.show_cmd() + end + else + stdout_write(ch) + rl.command = rl.command .. ch + end + end + if rl.command then + rl.store_history(rl.command) + end + return rl.command + end + return rl +end + +} + +local select_fds = {} +local sessions = {} + +local line_erased = false + +function erase_line(indent) + if not line_erased then + line_erased = true + stdout_write(string.rep(string.char(8), indent)..string.rep(" ", indent)..string.rep(string.char(8), indent)) + end +end + +function do_select_stdin(indent, batch_cmd, batch_when, batch_expect) + while true do + local nfds = 1+#select_fds + local pfds = ffi.new("struct pollfd[?]", nfds) + pfds[0].fd = 0; + pfds[0].events = 1; + pfds[0].revents = 0; + for i = 1,#select_fds do + pfds[i].fd = select_fds[i].fd + pfds[i].events = 1 + pfds[i].revents = 0 + end + if batch_cmd and ((os_time() > batch_when) or (batch_expect and expect_success(batch_expect, buf, 0))) then + return true + end + while ffi.C.poll(pfds, nfds, 10) == 0 do + if batch_cmd and ((os_time() > batch_when) or (batch_expect and expect_success(batch_expect, buf, 0))) then + return true + end + if line_erased then + line_erased = false + return false + end + end + if pfds[0].revents == 1 then + return true + end + for i = 1,#select_fds do + if(pfds[i].revents > 0) then + if pfds[i].fd ~= select_fds[i].fd then + print("File descriptors unequal", pfds[i].fd, select_fds[i].fd) + end + select_fds[i].cb(select_fds[i], pfds[i].revents, indent) + end + end + end +end + +local buf = ffi.new("char [32768]") + +function session_stdout_write(prefix, data) + data = prefix .. data:gsub("\n", "\n"..prefix):gsub("\n"..prefix.."$", "\n") + + stdout_write(data) +end + +function expect_success(sok, buf, nread) + local expect_buf_sz = ffi.sizeof(sok.expect_buf) - 128 + local expect_buf_avail = expect_buf_sz - sok.expect_buf_idx + -- print("EXPECT_SUCCESS: nread ".. tostring(nread).. " expect_buf_idx: " .. tostring(sok.expect_buf_idx) .. " expect_buf_avail: " .. tostring(expect_buf_avail) ) + if expect_buf_avail < 0 then + print "EXPECT BUFFER OVERRUN ALREADY" + os.exit(1) + end + if expect_buf_avail < nread then + if (nread >= ffi.sizeof(sok.expect_buf)) then + print("Read too large of a chunk to fit into expect buffer") + return nil + end + local delta = nread - expect_buf_avail + + ffi.C.memmove(sok.expect_buf, sok.expect_buf + delta, expect_buf_sz - delta) + sok.expect_buf_idx = sok.expect_buf_idx - delta + expect_buf_avail = nread + end + if sok.expect_buf_idx + nread > expect_buf_sz then + print("ERROR, I have just overrun the buffer !") + os.exit(1) + end + ffi.C.memcpy(sok.expect_buf + sok.expect_buf_idx, buf, nread) + sok.expect_buf_idx = sok.expect_buf_idx + nread + if sok.expect_str == nil then + return true + end + local match_p = ffi.C.memmem(sok.expect_buf, sok.expect_buf_idx, sok.expect_str, sok.expect_str_len) + if match_p ~= nil then + return true + end + return false +end + +function expect_done(sok) + local expect_buf_sz = ffi.sizeof(sok.expect_buf) - 128 + if not sok.expect_str then + return false + end + local match_p = ffi.C.memmem(sok.expect_buf, sok.expect_buf_idx, sok.expect_str, sok.expect_str_len) + if match_p ~= nil then + if sok.expect_cb then + sok.expect_cb(sok) + end + local match_idx = ffi.cast("char *", match_p) - ffi.cast("char *", sok.expect_buf) + ffi.C.memmove(sok.expect_buf, ffi.cast("char *", match_p) + sok.expect_str_len, expect_buf_sz - match_idx - sok.expect_str_len) + sok.expect_buf_idx = match_idx + sok.expect_str_len + sok.expect_success = true + + sok.expect_str = nil + sok.expect_str_len = 0 + return true + end +end + +function slave_events(sok, revents, indent) + local fd = sok.fd + local nread = ffi.C.read(fd, buf, ffi.sizeof(buf)-128) + local idx = nread - 1 + while idx >= 0 and buf[idx] ~= 10 do + idx = idx - 1 + end + if idx >= 0 then + erase_line(indent) + session_stdout_write(sok.prefix, sok.buf .. ffi.string(buf, idx+1)) + sok.buf = "" + end + sok.buf = sok.buf .. ffi.string(buf+idx+1, nread-idx-1) + -- print("\nRead: " .. tostring(nread)) + -- stdout_write(ffi.string(buf, nread)) + if expect_success(sok, buf, nread) then + return true + end + return false +end + + +function start_session(name) + local mfd, cpid = pty_run("/bin/bash") + local sok = { ["fd"] = mfd, ["cb"] = slave_events, ["buf"] = "", ["prefix"] = name .. ":", ["expect_buf"] = ffi.new("char [165536]"), ["expect_buf_idx"] = 0, ["expect_str"] = nil } + table.insert(select_fds, sok) + sessions[name] = sok +end + +function command_transform(exe) + if exe == "break" then + exe = string.char(3) + end + return exe +end + +function session_write(a_session, a_str) + if has_session(a_session) then + return tonumber(ffi.C.write(sessions[a_session].fd, c_str(a_str), #a_str)) + else + return 0 + end +end + +function session_exec(a_session, a_cmd) + local exe = command_transform(a_cmd) .. "\n" + session_write(a_session, exe) +end + +function session_cmd(ui, a_session, a_cmd) + if not has_session(a_session) then + stdout_write("ERR: No such session '" .. tostring(a_session) .. "'\n") + return nil + end + if a_session == "lua" then + local func, msg = loadstring(ui.lua_acc .. a_cmd) + -- stdout_write("LOADSTR: " .. vpp.dump({ ret, msg }) .. "\n") + if not func and string.match(msg, "<eof>") then + if a_session ~= ui.in_session then + stdout_write("ERR LOADSTR: " .. tostring(msg) .. "\n") + return nil + end + ui.lua_acc = ui.lua_acc .. a_cmd .. "\n" + return true + end + ui.lua_acc = "" + local ret, msg = pcall(func) + if ret then + return true + else + stdout_write("ERR: " .. msg .. "\n") + return nil + end + else + session_exec(a_session, a_cmd) + if ui.session_cmd_delay then + return { "delay", ui.session_cmd_delay } + end + return true + end +end + +function has_session(a_session) + if a_session == "lua" then + return true + end + return (sessions[a_session] ~= nil) +end + +function command_match(list, input, output) + for i, v in ipairs(list) do + local m = {} + m[1], m[2], m[3], m[4], m[5], m[6], m[7], m[8], m[9] = string.match(input, v[1]) + -- print("MATCH: ", vpp.dump(m)) + if m[1] then + output["result"] = m + output["result_index"] = i + return m + end + end + return nil +end + +function cmd_spawn_shell(ui, a_arg) + start_session(a_arg[1]) + return true +end + +function cmd_run_cmd(ui, a_arg) + local a_sess = a_arg[1] + local a_cmd = a_arg[2] + return session_cmd(ui, a_sess, a_cmd) +end + +function cmd_cd(ui, a_arg) + local a_sess = a_arg[1] + if has_session(a_sess) then + ui.in_session = a_sess + return true + else + stdout_write("ERR: Unknown session '".. tostring(a_sess) .. "'\n") + return nil + end +end + +function cmd_sleep(ui, a_arg) + return { "delay", tonumber(a_arg[1]) } +end + +function cmd_expect(ui, a_arg) + local a_sess = a_arg[1] + local a_expect = a_arg[2] + local sok = sessions[a_sess] + if not sok then + stdout_write("ERR: unknown session '" .. tostring(a_sess) .. "'\n") + return nil + end + sok.expect_str = c_str(a_expect) + sok.expect_str_len = #a_expect + return { "expect", a_sess } +end + +function cmd_info(ui, a_arg) + local a_sess = a_arg[1] + local sok = sessions[a_sess] + if not sok then + stdout_write("ERR: unknown session '" .. tostring(a_sess) .. "'\n") + return nil + end + print("Info for session " .. tostring(a_sess) .. "\n") + print("Expect buffer index: " .. tostring(sok.expect_buf_idx)) + print("Expect buffer: '" .. tostring(ffi.string(sok.expect_buf, sok.expect_buf_idx)) .. "'\n") + if sok.expect_str then + print("Expect string: '" .. tostring(ffi.string(sok.expect_str, sok.expect_str_len)) .. "'\n") + else + print("Expect string not set\n") + end +end + +function cmd_echo(ui, a_arg) + local a_data = a_arg[1] + print("ECHO: " .. tostring(a_data)) +end + +main_command_table = { + { "^shell ([a-zA-Z0-9_]+)$", cmd_spawn_shell }, + { "^run ([a-zA-Z0-9_]+) (.+)$", cmd_run_cmd }, + { "^cd ([a-zA-Z0-9_]+)$", cmd_cd }, + { "^sleep ([0-9]+)$", cmd_sleep }, + { "^expect ([a-zA-Z0-9_]+) (.-)$", cmd_expect }, + { "^info ([a-zA-Z0-9_]+)$", cmd_info }, + { "^echo (.-)$", cmd_echo } +} + + + +function ui_set_prompt(ui) + if ui.in_session then + if ui.in_session == "lua" then + if #ui.lua_acc > 0 then + ui.r.prompt = ui.in_session .. ">>" + else + ui.r.prompt = ui.in_session .. ">" + end + else + ui.r.prompt = ui.in_session .. "> " + end + else + ui.r.prompt = "> " + end + return ui.r.prompt +end + +function ui_run_command(ui, cmd) + -- stdout_write("Command: " .. tostring(cmd) .. "\n") + local ret = false + if ui.in_session then + if cmd then + if cmd == "^D^D^D" then + ui.in_session = nil + ret = true + else + ret = session_cmd(ui, ui.in_session, cmd) + end + else + ui.in_session = nil + ret = true + end + else + if cmd then + local out = {} + if cmd == "" then + ret = true + end + if command_match(main_command_table, cmd, out) then + local i = out.result_index + local m = out.result + if main_command_table[i][2] then + ret = main_command_table[i][2](ui, m) + end + end + end + if not cmd or cmd == "quit" then + return "quit" + end + end + return ret +end + +local ui = {} +ui.in_session = nil +ui.r = readln.reader() +ui.lua_acc = "" +ui.session_cmd_delay = 0.3 + +local lines = "" + +local done = false +-- a helper function which always returns nil +local no_next_line = function() return nil end + +-- a function which returns the next batch line +local next_line = no_next_line + +local batchfile = arg[1] + +if batchfile then + local f = io.lines(batchfile) + next_line = function() + local line = f() + if line then + return line + else + next_line = no_next_line + session_stdout_write(batchfile .. ":", "End of batch\n") + return nil + end + end +end + + +local batch_when = 0 +local batch_expect = nil +while not done do + local prompt = ui_set_prompt(ui) + local batch_cmd = next_line() + local cmd, expect_sok = ui.r.readln(do_select_stdin, batch_cmd, batch_when, batch_expect) + if expect_sok and not expect_success(expect_sok, buf, 0) then + if not cmd_ret and next_line ~= no_next_line then + print("ERR: expect timeout\n") + next_line = no_next_line + end + else + local cmd_ret = ui_run_command(ui, cmd) + if not cmd_ret and next_line ~= no_next_line then + print("ERR: Error during batch execution\n") + next_line = no_next_line + end + + if cmd_ret == "quit" then + done = true + end + batch_expect = nil + batch_when = 0 + if type(cmd_ret) == "table" then + if cmd_ret[1] == "delay" then + batch_when = os_time() + tonumber(cmd_ret[2]) + end + if cmd_ret[1] == "expect" then + batch_expect = sessions[cmd_ret[2]] + batch_when = os_time() + 15 + end + end + end +end +ui.r.done() + +os.exit(1) + + + diff --git a/src/vpp-api/lua/examples/lute/script-inout-acl-noacl.lute b/src/vpp-api/lua/examples/lute/script-inout-acl-noacl.lute new file mode 100644 index 00000000..a24d04bf --- /dev/null +++ b/src/vpp-api/lua/examples/lute/script-inout-acl-noacl.lute @@ -0,0 +1,329 @@ +shell vppbuild +run vppbuild stty -echo +run vppbuild sudo -u ubuntu -i bash -c "(cd vpp && make plugins && echo ALLGOOD)" +expect vppbuild ALLGOOD + +shell s0 +shell s1 +shell s2 + + +cd s1 +unshare -n /bin/bash +/sbin/ifconfig -a +^D^D^D + +cd s2 +unshare -n /bin/bash +/sbin/ifconfig -a +^D^D^D + + +cd lua + +function session_get_bash_pid(s) + if not has_session(s) then + return nil + end + local fname = "/tmp/lute-"..s.."-pid.txt" + + session_exec(s, "echo $$ >" .. fname) + -- it's a dirty hack but it's quick + sleep(0.5) + local pid = io.lines(fname)() + print("Got pid for " .. s .. " : " .. tostring(pid)) + return(tonumber(pid)) +end + +function session_connect_with(s0, s1) + -- local pid0 = tostring(session_get_bash_pid(s0)) + local pid1 = tostring(session_get_bash_pid(s1)) + local eth_options = { "rx", "tx", "sg", "tso", "ufo", "gso", "gro", "lro", "rxvlan", "txvlan", "rxhash" } + local this_end = s0 .. "_" .. s1 + local other_end = s1 .. "_" .. s0 + session_exec(s0, "ip link add name " .. this_end .. " type veth peer name " .. other_end) + session_exec(s0, "ip link set dev " .. this_end .. " up promisc on") + for i, option in ipairs(eth_options) do + session_exec(s0, "/sbin/ethtool --offload " .. this_end .. " " .. option .. " off") + session_exec(s0, "/sbin/ethtool --offload " .. other_end .. " " .. option .. " off") + end + session_exec(s0, "ip link set dev " .. other_end .. " up promisc on netns /proc/" .. pid1 .. "/ns/net") + sleep(0.5) +end + +^D^D^D +run lua session_connect_with("s0", "s1") +run lua session_connect_with("s0", "s2") + +cd s1 +ip -6 addr add dev s1_s0 2001:db8:1::1/64 +ip -4 addr add dev s1_s0 192.0.2.1/24 +ip link set dev s1_s0 up promisc on +^D^D^D + +cd s2 +ip -6 addr add dev s2_s0 2001:db8:1::2/64 +ip -6 addr add dev s2_s0 2001:db8:1::3/64 +ip -6 addr add dev s2_s0 2001:db8:1::4/64 +ip -4 addr add dev s2_s0 192.0.2.2/24 +ip -4 addr add dev s2_s0:1 192.0.2.3/24 +ip -4 addr add dev s2_s0:2 192.0.2.4/24 +ip link set dev s2_s0 up promisc on +^D^D^D + +run s1 ip addr +run s2 ip addr +shell VPP +cd VPP +cd /home/ubuntu/vpp +make debug +r +^D^D^D +expect VPP DBGvpp# + +cd lua +-- Initialization of the Lua environment for talking to VPP +vpp = require("vpp-lapi") +root_dir = "/home/ubuntu/vpp" +pneum_path = root_dir .. "/build-root/install-vpp_debug-native/vpp-api/lib64/libpneum.so" +vpp:init({ pneum_path = pneum_path }) +vpp:consume_api(root_dir .. "/build-root/install-vpp_debug-native/vlib-api/vlibmemory/memclnt.api") +vpp:consume_api(root_dir .. "/build-root/install-vpp_debug-native/vpp/vpp-api/vpe.api") +vpp:connect("aytest") +vpp:consume_api(root_dir .. "/plugins/acl-plugin/acl/acl.api", "acl") + +^D^D^D + +cd lua + +reply = vpp:api_call("af_packet_create", { host_if_name = "s0_s1", hw_addr = "AAAAAA" }) +vpp_if_to_s1 = reply[1].sw_if_index + +reply = vpp:api_call("af_packet_create", { host_if_name = "s0_s2", hw_addr = "AAAAAA" }) +vpp_if_to_s2 = reply[1].sw_if_index + +ifaces = { vpp_if_to_s1, vpp_if_to_s2 } + +reply = vpp:api_call("sw_interface_set_flags", { sw_if_index = vpp_if_to_s1, admin_up_down = 1, link_up_down = 1 }) +print(vpp.dump(reply)) +reply = vpp:api_call("sw_interface_set_flags", { sw_if_index = vpp_if_to_s2, admin_up_down = 1, link_up_down = 1 }) +print(vpp.dump(reply)) + +bd_id = 42 + +reply = vpp:api_call("bridge_domain_add_del", { bd_id = bd_id, flood = 1, uu_flood = 1, forward = 1, learn = 1, arp_term = 0, is_add = 1 }) +print(vpp.dump(reply)) + +for i, v in ipairs(ifaces) do + reply = vpp:api_call("sw_interface_set_l2_bridge", { rx_sw_if_index = v, bd_id = bd_id, shg = 0, bvi = 0, enable = 1 } ) + print(vpp.dump(reply)) +end + +^D^D^D + +run s1 ping -c 3 192.0.2.2 +expect s1 packet loss +run s1 ping -c 3 192.0.2.3 +expect s1 packet loss +run s1 ping -c 3 192.0.2.4 +expect s1 packet loss +run s1 ping6 -c 3 2001:db8:1::2 +expect s1 packet loss +run s1 ping6 -c 3 2001:db8:1::3 +expect s1 packet loss +run s1 ping6 -c 3 2001:db8:1::4 +expect s1 packet loss + + +cd lua +--- ACL testing + +--[[ temporary comment out + +reply = vpp:api_call("acl_del", { context = 42, acl_index = 230 }) +print(vpp.dump(reply)) +print("---") + +reply = vpp:api_call("acl_del", { context = 42, acl_index = 8 }) +print(vpp.dump(reply)) +print("---") + +reply = vpp:api_call("acl_del", { context = 42, acl_index = 15 }) +print(vpp.dump(reply)) +print("---") + +reply = vpp:api_call("acl_add_replace", { context = 42, count = 2, r = { { is_permit = 1, is_ipv6 = 1 }, { is_permit = 0, is_ipv6 = 1 } } }) +print(vpp.dump(reply)) +print("---") +interface_acl_in = reply[1].acl_index + +reply = vpp:api_call("acl_add_replace", { context = 42, count = 3, r = { { is_permit = 1, is_ipv6 = 1 }, { is_permit = 0, is_ipv6 = 1 }, { is_permit = 1, is_ipv6 = 0 } } }) +print(vpp.dump(reply)) +print("---") +interface_acl_out = reply[1].acl_index + + +reply = vpp:api_call("acl_interface_add_del", { context = 42, sw_if_index = 0, is_add = 1, is_input = 1, acl_index = interface_acl_in }) +print(vpp.dump(reply)) +print("---") + +reply = vpp:api_call("acl_interface_add_del", { context = 42, sw_if_index = 0, is_add = 1, is_input = 1, acl_index = interface_acl_in }) +print(vpp.dump(reply)) +print("---") + +reply = vpp:api_call("acl_interface_add_del", { context = 42, sw_if_index = 0, is_add = 1, is_input = 0, acl_index = interface_acl_out }) +print(vpp.dump(reply)) +print("---") + +reply = vpp:api_call("acl_interface_add_del", { context = 42, sw_if_index = 0, is_add = 1, is_input = 0, acl_index = interface_acl_out }) +print(vpp.dump(reply)) +print("---") + +reply = vpp:api_call("acl_add_replace", { context = 42, count = 0 }) +print(vpp.dump(reply)) +print("---") + +acl_index_to_delete = reply[1].acl_index +print("Deleting " .. tostring(acl_index_to_delete)) +reply = vpp:api_call("acl_del", { context = 42, acl_index = acl_index_to_delete }) +print(vpp.dump(reply)) +print("---") + +reply = vpp:api_call("acl_dump", { context = 42, sw_if_index = 0}) +for ri, rv in ipairs(reply) do + print("Reply message #" .. tostring(ri)) + print(vpp.dump(rv)) + for ai, av in ipairs(rv.r) do + print("ACL rule #" .. tostring(ai) .. " : " .. vpp.dump(av)) + end + +end +print("---") + +reply = vpp:api_call("acl_del", { context = 42, acl_index = interface_acl_out }) +print(vpp.dump(reply)) +print("---") +reply = vpp:api_call("acl_del", { context = 42, acl_index = interface_acl_in }) +print(vpp.dump(reply)) +print("---") + +reply = vpp:api_call("acl_dump", { context = 42, sw_if_index = 0}) +print(vpp.dump(reply)) +print("---") + +reply = vpp:api_call("acl_dump", { context = 42, sw_if_index = 4294967295 }) +print(vpp.dump(reply)) +print("---") + + +]] -- end of comment out + +---- Should be nothing ^^ +r = { + { is_permit = 1, is_ipv6 = 1, dst_ip_addr = ip46("2001:db8:1::2"), dst_ip_prefix_len = 128 }, + { is_permit = 0, is_ipv6 = 1, dst_ip_addr = ip46("2001:db8:1::3"), dst_ip_prefix_len = 128 }, + { is_permit = 1, is_ipv6 = 1, dst_ip_addr = ip46("2001:db8::"), dst_ip_prefix_len = 32 }, + { is_permit = 1, is_ipv6 = 0, dst_ip_addr = ip46("192.0.2.2"), dst_ip_prefix_len = 32}, + { is_permit = 0, is_ipv6 = 0, dst_ip_addr = ip46("192.0.2.3"), dst_ip_prefix_len = 32 }, +} + +reply = vpp:api_call("acl_add_replace", { context = 42, count = 5, r = r }) +print(vpp.dump(reply)) +print("---") +interface_acl_in = reply[1].acl_index + +reply = vpp:api_call("acl_add_replace", { context = 42, count = 3, r = { { is_permit = 1, is_ipv6 = 1 }, { is_permit = 0, is_ipv6 = 1 }, { is_permit = 1, is_ipv6 = 0 } } }) +print(vpp.dump(reply)) +print("---") +interface_acl_out = reply[1].acl_in + +reply = vpp:api_call("acl_interface_add_del", { context = 42, sw_if_index = vpp_if_to_s1, is_add = 1, is_input = 1, acl_index = interface_acl_in }) +print(vpp.dump(reply)) +print("---") + +--reply = vpp:api_call("acl_interface_add_del", { context = 42, sw_if_index = vpp_if_to_s2, is_add = 1, is_input = 0, acl_index = interface_acl_out }) +-- print(vpp.dump(reply)) +--print("---") + +^D^D^D + +run VPP clear trace +run VPP trace add af-packet-input 100 +run s1 ping6 -c 3 2001:db8:1::2 +expect s1 packet loss +run VPP show trace +expect VPP match: inacl 0 rule 0 + +run VPP clear trace +run VPP trace add af-packet-input 100 +run s1 ping6 -c 3 2001:db8:1::3 +expect s1 packet loss +run VPP show trace +expect VPP match: inacl 0 rule 1 + +run VPP clear trace +run VPP trace add af-packet-input 100 +run s1 ping6 -c 3 2001:db8:1::4 +expect s1 packet loss +run VPP show trace +expect VPP match: inacl 0 rule 2 + +run VPP clear trace +run VPP trace add af-packet-input 100 +run s1 ping -c 3 192.0.2.2 +expect s1 packet loss +run VPP show trace +expect VPP match: inacl 0 rule 3 + +run VPP clear trace +run VPP trace add af-packet-input 100 +run s1 ping -c 3 192.0.2.3 +expect s1 packet loss +run VPP show trace +expect VPP match: inacl 0 rule 4 + + +cd lua + +--- TEST OUTBOUND ACL + +r1 = { + { is_permit = 1, is_ipv6 = 1, src_ip_addr = ip46("2001:db8:1::1"), src_ip_prefix_len = 128, dst_ip_addr = ip46("2001:db8:1::2"), dst_ip_prefix_len = 128 }, + { is_permit = 0, is_ipv6 = 1, src_ip_addr = ip46("2001:db8:1::1"), src_ip_prefix_len = 128, dst_ip_addr = ip46("2001:db8:1::4"), dst_ip_prefix_len = 128 } +} + +reply = vpp:api_call("acl_add_replace", { context = 42, count = 3, r = r1 }) +print(vpp.dump(reply)) +print("---") +interface_acl_out = reply[1].acl_index + +reply = vpp:api_call("acl_interface_add_del", { context = 42, sw_if_index = vpp_if_to_s2, is_add = 1, is_input = 0, acl_index = interface_acl_out }) +print(vpp.dump(reply)) +print("---") + + +^D^D^D + +run VPP clear trace +run VPP trace add af-packet-input 100 +run s1 ping6 -c 3 2001:db8:1::2 +expect s1 packet loss +run VPP show trace +expect VPP match: outacl 2 rule 0 + +run VPP clear trace +run VPP trace add af-packet-input 100 +run s1 ping6 -c 3 2001:db8:1::3 +expect s1 packet loss +run VPP show trace +expect VPP match: inacl 0 rule 1 + +run VPP clear trace +run VPP trace add af-packet-input 100 +run s1 ping6 -c 3 2001:db8:1::4 +expect s1 packet loss +run VPP show trace +expect VPP match: outacl 2 rule 1 + +run lua print("ALL GOOD!") + diff --git a/src/vpp-api/lua/examples/lute/script-inout-acl-old.lute b/src/vpp-api/lua/examples/lute/script-inout-acl-old.lute new file mode 100644 index 00000000..9edebf02 --- /dev/null +++ b/src/vpp-api/lua/examples/lute/script-inout-acl-old.lute @@ -0,0 +1,329 @@ +shell vppbuild +run vppbuild stty -echo +run vppbuild sudo -u ubuntu -i bash -c "(cd vpp && make plugins && echo ALLGOOD)" +expect vppbuild ALLGOOD + +shell s0 +shell s1 +shell s2 + + +cd s1 +unshare -n /bin/bash +/sbin/ifconfig -a +^D^D^D + +cd s2 +unshare -n /bin/bash +/sbin/ifconfig -a +^D^D^D + + +cd lua + +function session_get_bash_pid(s) + if not has_session(s) then + return nil + end + local fname = "/tmp/lute-"..s.."-pid.txt" + + session_exec(s, "echo $$ >" .. fname) + -- it's a dirty hack but it's quick + sleep(0.5) + local pid = io.lines(fname)() + print("Got pid for " .. s .. " : " .. tostring(pid)) + return(tonumber(pid)) +end + +function session_connect_with(s0, s1) + -- local pid0 = tostring(session_get_bash_pid(s0)) + local pid1 = tostring(session_get_bash_pid(s1)) + local eth_options = { "rx", "tx", "sg", "tso", "ufo", "gso", "gro", "lro", "rxvlan", "txvlan", "rxhash" } + local this_end = s0 .. "_" .. s1 + local other_end = s1 .. "_" .. s0 + session_exec(s0, "ip link add name " .. this_end .. " type veth peer name " .. other_end) + session_exec(s0, "ip link set dev " .. this_end .. " up promisc on") + for i, option in ipairs(eth_options) do + session_exec(s0, "/sbin/ethtool --offload " .. this_end .. " " .. option .. " off") + session_exec(s0, "/sbin/ethtool --offload " .. other_end .. " " .. option .. " off") + end + session_exec(s0, "ip link set dev " .. other_end .. " up promisc on netns /proc/" .. pid1 .. "/ns/net") + sleep(0.5) +end + +^D^D^D +run lua session_connect_with("s0", "s1") +run lua session_connect_with("s0", "s2") + +cd s1 +ip -6 addr add dev s1_s0 2001:db8:1::1/64 +ip -4 addr add dev s1_s0 192.0.2.1/24 +ip link set dev s1_s0 up promisc on +^D^D^D + +cd s2 +ip -6 addr add dev s2_s0 2001:db8:1::2/64 +ip -6 addr add dev s2_s0 2001:db8:1::3/64 +ip -6 addr add dev s2_s0 2001:db8:1::4/64 +ip -4 addr add dev s2_s0 192.0.2.2/24 +ip -4 addr add dev s2_s0:1 192.0.2.3/24 +ip -4 addr add dev s2_s0:2 192.0.2.4/24 +ip link set dev s2_s0 up promisc on +^D^D^D + +run s1 ip addr +run s2 ip addr +shell VPP +cd VPP +cd /home/ubuntu/vpp +make debug +r +^D^D^D +expect VPP DBGvpp# + +cd lua +-- Initialization of the Lua environment for talking to VPP +vpp = require("vpp-lapi") +root_dir = "/home/ubuntu/vpp" +pneum_path = root_dir .. "/build-root/install-vpp_debug-native/vpp-api/lib64/libpneum.so" +vpp:init({ pneum_path = pneum_path }) +vpp:consume_api(root_dir .. "/build-root/install-vpp_debug-native/vlib-api/vlibmemory/memclnt.api") +vpp:consume_api(root_dir .. "/build-root/install-vpp_debug-native/vpp/vpp-api/vpe.api") +vpp:connect("aytest") +vpp:consume_api(root_dir .. "/plugins/acl-plugin/acl/acl.api", "acl") + +^D^D^D + +cd lua + +reply = vpp:api_call("af_packet_create", { host_if_name = "s0_s1", hw_addr = "AAAAAA" }) +vpp_if_to_s1 = reply[1].sw_if_index + +reply = vpp:api_call("af_packet_create", { host_if_name = "s0_s2", hw_addr = "AAAAAA" }) +vpp_if_to_s2 = reply[1].sw_if_index + +ifaces = { vpp_if_to_s1, vpp_if_to_s2 } + +reply = vpp:api_call("sw_interface_set_flags", { sw_if_index = vpp_if_to_s1, admin_up_down = 1, link_up_down = 1 }) +print(vpp.dump(reply)) +reply = vpp:api_call("sw_interface_set_flags", { sw_if_index = vpp_if_to_s2, admin_up_down = 1, link_up_down = 1 }) +print(vpp.dump(reply)) + +bd_id = 42 + +reply = vpp:api_call("bridge_domain_add_del", { bd_id = bd_id, flood = 1, uu_flood = 1, forward = 1, learn = 1, arp_term = 0, is_add = 1 }) +print(vpp.dump(reply)) + +for i, v in ipairs(ifaces) do + reply = vpp:api_call("sw_interface_set_l2_bridge", { rx_sw_if_index = v, bd_id = bd_id, shg = 0, bvi = 0, enable = 1 } ) + print(vpp.dump(reply)) +end + +^D^D^D + +run s1 ping -c 3 192.0.2.2 +expect s1 packet loss +run s1 ping -c 3 192.0.2.3 +expect s1 packet loss +run s1 ping -c 3 192.0.2.4 +expect s1 packet loss +run s1 ping6 -c 3 2001:db8:1::2 +expect s1 packet loss +run s1 ping6 -c 3 2001:db8:1::3 +expect s1 packet loss +run s1 ping6 -c 3 2001:db8:1::4 +expect s1 packet loss + + +cd lua +--- ACL testing + +--[[ temporary comment out + +reply = vpp:api_call("acl_del", { context = 42, acl_index = 230 }) +print(vpp.dump(reply)) +print("---") + +reply = vpp:api_call("acl_del", { context = 42, acl_index = 8 }) +print(vpp.dump(reply)) +print("---") + +reply = vpp:api_call("acl_del", { context = 42, acl_index = 15 }) +print(vpp.dump(reply)) +print("---") + +reply = vpp:api_call("acl_add", { context = 42, count = 2, r = { { is_permit = 1, is_ipv6 = 1 }, { is_permit = 0, is_ipv6 = 1 } } }) +print(vpp.dump(reply)) +print("---") +interface_acl_in = reply[1].acl_index + +reply = vpp:api_call("acl_add", { context = 42, count = 3, r = { { is_permit = 1, is_ipv6 = 1 }, { is_permit = 0, is_ipv6 = 1 }, { is_permit = 1, is_ipv6 = 0 } } }) +print(vpp.dump(reply)) +print("---") +interface_acl_out = reply[1].acl_index + + +reply = vpp:api_call("acl_interface_add_del", { context = 42, sw_if_index = 0, is_add = 1, is_input = 1, acl_index = interface_acl_in }) +print(vpp.dump(reply)) +print("---") + +reply = vpp:api_call("acl_interface_add_del", { context = 42, sw_if_index = 0, is_add = 1, is_input = 1, acl_index = interface_acl_in }) +print(vpp.dump(reply)) +print("---") + +reply = vpp:api_call("acl_interface_add_del", { context = 42, sw_if_index = 0, is_add = 1, is_input = 0, acl_index = interface_acl_out }) +print(vpp.dump(reply)) +print("---") + +reply = vpp:api_call("acl_interface_add_del", { context = 42, sw_if_index = 0, is_add = 1, is_input = 0, acl_index = interface_acl_out }) +print(vpp.dump(reply)) +print("---") + +reply = vpp:api_call("acl_add", { context = 42, count = 0 }) +print(vpp.dump(reply)) +print("---") + +acl_index_to_delete = reply[1].acl_index +print("Deleting " .. tostring(acl_index_to_delete)) +reply = vpp:api_call("acl_del", { context = 42, acl_index = acl_index_to_delete }) +print(vpp.dump(reply)) +print("---") + +reply = vpp:api_call("acl_dump", { context = 42, sw_if_index = 0}) +for ri, rv in ipairs(reply) do + print("Reply message #" .. tostring(ri)) + print(vpp.dump(rv)) + for ai, av in ipairs(rv.r) do + print("ACL rule #" .. tostring(ai) .. " : " .. vpp.dump(av)) + end + +end +print("---") + +reply = vpp:api_call("acl_del", { context = 42, acl_index = interface_acl_out }) +print(vpp.dump(reply)) +print("---") +reply = vpp:api_call("acl_del", { context = 42, acl_index = interface_acl_in }) +print(vpp.dump(reply)) +print("---") + +reply = vpp:api_call("acl_dump", { context = 42, sw_if_index = 0}) +print(vpp.dump(reply)) +print("---") + +reply = vpp:api_call("acl_dump", { context = 42, sw_if_index = 4294967295 }) +print(vpp.dump(reply)) +print("---") + + +]] -- end of comment out + +---- Should be nothing ^^ +r = { + { is_permit = 1, is_ipv6 = 1, dst_ip_addr = ip46("2001:db8:1::2"), dst_ip_prefix_len = 128 }, + { is_permit = 0, is_ipv6 = 1, dst_ip_addr = ip46("2001:db8:1::3"), dst_ip_prefix_len = 128 }, + { is_permit = 1, is_ipv6 = 1, dst_ip_addr = ip46("2001:db8::"), dst_ip_prefix_len = 32 }, + { is_permit = 1, is_ipv6 = 0, dst_ip_addr = ip46("192.0.2.2"), dst_ip_prefix_len = 32}, + { is_permit = 0, is_ipv6 = 0, dst_ip_addr = ip46("192.0.2.3"), dst_ip_prefix_len = 32 }, +} + +reply = vpp:api_call("acl_add", { context = 42, count = 5, r = r }) +print(vpp.dump(reply)) +print("---") +interface_acl_in = reply[1].acl_index + +reply = vpp:api_call("acl_add", { context = 42, count = 3, r = { { is_permit = 1, is_ipv6 = 1 }, { is_permit = 0, is_ipv6 = 1 }, { is_permit = 1, is_ipv6 = 0 } } }) +print(vpp.dump(reply)) +print("---") +interface_acl_out = reply[1].acl_in + +reply = vpp:api_call("acl_interface_add_del", { context = 42, sw_if_index = vpp_if_to_s1, is_add = 1, is_input = 1, acl_index = interface_acl_in }) +print(vpp.dump(reply)) +print("---") + +--reply = vpp:api_call("acl_interface_add_del", { context = 42, sw_if_index = vpp_if_to_s2, is_add = 1, is_input = 0, acl_index = interface_acl_out }) +-- print(vpp.dump(reply)) +--print("---") + +^D^D^D + +run VPP clear trace +run VPP trace add af-packet-input 100 +run s1 ping6 -c 3 2001:db8:1::2 +expect s1 packet loss +run VPP show trace +expect VPP match: inacl 0 rule 0 + +run VPP clear trace +run VPP trace add af-packet-input 100 +run s1 ping6 -c 3 2001:db8:1::3 +expect s1 packet loss +run VPP show trace +expect VPP match: inacl 0 rule 1 + +run VPP clear trace +run VPP trace add af-packet-input 100 +run s1 ping6 -c 3 2001:db8:1::4 +expect s1 packet loss +run VPP show trace +expect VPP match: inacl 0 rule 2 + +run VPP clear trace +run VPP trace add af-packet-input 100 +run s1 ping -c 3 192.0.2.2 +expect s1 packet loss +run VPP show trace +expect VPP match: inacl 0 rule 3 + +run VPP clear trace +run VPP trace add af-packet-input 100 +run s1 ping -c 3 192.0.2.3 +expect s1 packet loss +run VPP show trace +expect VPP match: inacl 0 rule 4 + + +cd lua + +--- TEST OUTBOUND ACL + +r1 = { + { is_permit = 1, is_ipv6 = 1, src_ip_addr = ip46("2001:db8:1::1"), src_ip_prefix_len = 128, dst_ip_addr = ip46("2001:db8:1::2"), dst_ip_prefix_len = 128 }, + { is_permit = 0, is_ipv6 = 1, src_ip_addr = ip46("2001:db8:1::1"), src_ip_prefix_len = 128, dst_ip_addr = ip46("2001:db8:1::4"), dst_ip_prefix_len = 128 } +} + +reply = vpp:api_call("acl_add", { context = 42, count = 3, r = r1 }) +print(vpp.dump(reply)) +print("---") +interface_acl_out = reply[1].acl_index + +reply = vpp:api_call("acl_interface_add_del", { context = 42, sw_if_index = vpp_if_to_s2, is_add = 1, is_input = 0, acl_index = interface_acl_out }) +print(vpp.dump(reply)) +print("---") + + +^D^D^D + +run VPP clear trace +run VPP trace add af-packet-input 100 +run s1 ping6 -c 3 2001:db8:1::2 +expect s1 packet loss +run VPP show trace +expect VPP match: outacl 2 rule 0 + +run VPP clear trace +run VPP trace add af-packet-input 100 +run s1 ping6 -c 3 2001:db8:1::3 +expect s1 packet loss +run VPP show trace +expect VPP match: inacl 0 rule 1 + +run VPP clear trace +run VPP trace add af-packet-input 100 +run s1 ping6 -c 3 2001:db8:1::4 +expect s1 packet loss +run VPP show trace +expect VPP match: outacl 2 rule 1 + +run lua print("ALL GOOD!") + diff --git a/src/vpp-api/lua/examples/lute/script-inout-acl.lute b/src/vpp-api/lua/examples/lute/script-inout-acl.lute new file mode 100644 index 00000000..d7e7423c --- /dev/null +++ b/src/vpp-api/lua/examples/lute/script-inout-acl.lute @@ -0,0 +1,329 @@ +shell vppbuild +run vppbuild stty -echo +run vppbuild sudo -u ubuntu -i bash -c "(cd vpp && make plugins && echo ALLGOOD)" +expect vppbuild ALLGOOD + +shell s0 +shell s1 +shell s2 + + +cd s1 +unshare -n /bin/bash +/sbin/ifconfig -a +^D^D^D + +cd s2 +unshare -n /bin/bash +/sbin/ifconfig -a +^D^D^D + + +cd lua + +function session_get_bash_pid(s) + if not has_session(s) then + return nil + end + local fname = "/tmp/lute-"..s.."-pid.txt" + + session_exec(s, "echo $$ >" .. fname) + -- it's a dirty hack but it's quick + sleep(0.5) + local pid = io.lines(fname)() + print("Got pid for " .. s .. " : " .. tostring(pid)) + return(tonumber(pid)) +end + +function session_connect_with(s0, s1) + -- local pid0 = tostring(session_get_bash_pid(s0)) + local pid1 = tostring(session_get_bash_pid(s1)) + local eth_options = { "rx", "tx", "sg", "tso", "ufo", "gso", "gro", "lro", "rxvlan", "txvlan", "rxhash" } + local this_end = s0 .. "_" .. s1 + local other_end = s1 .. "_" .. s0 + session_exec(s0, "ip link add name " .. this_end .. " type veth peer name " .. other_end) + session_exec(s0, "ip link set dev " .. this_end .. " up promisc on") + for i, option in ipairs(eth_options) do + session_exec(s0, "/sbin/ethtool --offload " .. this_end .. " " .. option .. " off") + session_exec(s0, "/sbin/ethtool --offload " .. other_end .. " " .. option .. " off") + end + session_exec(s0, "ip link set dev " .. other_end .. " up promisc on netns /proc/" .. pid1 .. "/ns/net") + sleep(0.5) +end + +^D^D^D +run lua session_connect_with("s0", "s1") +run lua session_connect_with("s0", "s2") + +cd s1 +ip -6 addr add dev s1_s0 2001:db8:1::1/64 +ip -4 addr add dev s1_s0 192.0.2.1/24 +ip link set dev s1_s0 up promisc on +^D^D^D + +cd s2 +ip -6 addr add dev s2_s0 2001:db8:1::2/64 +ip -6 addr add dev s2_s0 2001:db8:1::3/64 +ip -6 addr add dev s2_s0 2001:db8:1::4/64 +ip -4 addr add dev s2_s0 192.0.2.2/24 +ip -4 addr add dev s2_s0:1 192.0.2.3/24 +ip -4 addr add dev s2_s0:2 192.0.2.4/24 +ip link set dev s2_s0 up promisc on +^D^D^D + +run s1 ip addr +run s2 ip addr +shell VPP +cd VPP +cd /home/ubuntu/vpp +make debug +r +^D^D^D +expect VPP DBGvpp# + +cd lua +-- Initialization of the Lua environment for talking to VPP +vpp = require("vpp-lapi") +root_dir = "/home/ubuntu/vpp" +pneum_path = root_dir .. "/build-root/install-vpp_debug-native/vpp-api/lib64/libpneum.so" +vpp:init({ pneum_path = pneum_path }) +vpp:consume_api(root_dir .. "/build-root/install-vpp_debug-native/vlib-api/vlibmemory/memclnt.api") +vpp:consume_api(root_dir .. "/build-root/install-vpp_debug-native/vpp/vpp-api/vpe.api") +vpp:connect("aytest") +vpp:consume_api(root_dir .. "/plugins/acl-plugin/acl/acl.api", "acl") + +^D^D^D + +cd lua + +reply = vpp:api_call("af_packet_create", { host_if_name = "s0_s1", hw_addr = "AAAAAA" }) +vpp_if_to_s1 = reply[1].sw_if_index + +reply = vpp:api_call("af_packet_create", { host_if_name = "s0_s2", hw_addr = "AAAAAA" }) +vpp_if_to_s2 = reply[1].sw_if_index + +ifaces = { vpp_if_to_s1, vpp_if_to_s2 } + +reply = vpp:api_call("sw_interface_set_flags", { sw_if_index = vpp_if_to_s1, admin_up_down = 1, link_up_down = 1 }) +print(vpp.dump(reply)) +reply = vpp:api_call("sw_interface_set_flags", { sw_if_index = vpp_if_to_s2, admin_up_down = 1, link_up_down = 1 }) +print(vpp.dump(reply)) + +bd_id = 42 + +reply = vpp:api_call("bridge_domain_add_del", { bd_id = bd_id, flood = 1, uu_flood = 1, forward = 1, learn = 1, arp_term = 0, is_add = 1 }) +print(vpp.dump(reply)) + +for i, v in ipairs(ifaces) do + reply = vpp:api_call("sw_interface_set_l2_bridge", { rx_sw_if_index = v, bd_id = bd_id, shg = 0, bvi = 0, enable = 1 } ) + print(vpp.dump(reply)) +end + +^D^D^D + +run s1 ping -c 3 192.0.2.2 +expect s1 packet loss +run s1 ping -c 3 192.0.2.3 +expect s1 packet loss +run s1 ping -c 3 192.0.2.4 +expect s1 packet loss +run s1 ping6 -c 3 2001:db8:1::2 +expect s1 packet loss +run s1 ping6 -c 3 2001:db8:1::3 +expect s1 packet loss +run s1 ping6 -c 3 2001:db8:1::4 +expect s1 packet loss + + +cd lua +--- ACL testing + +--[[ temporary comment out + +reply = vpp:api_call("acl_del", { context = 42, acl_index = 230 }) +print(vpp.dump(reply)) +print("---") + +reply = vpp:api_call("acl_del", { context = 42, acl_index = 8 }) +print(vpp.dump(reply)) +print("---") + +reply = vpp:api_call("acl_del", { context = 42, acl_index = 15 }) +print(vpp.dump(reply)) +print("---") + +reply = vpp:api_call("acl_add_replace", { context = 42, acl_index = -1, count = 2, r = { { is_permit = 1, is_ipv6 = 1 }, { is_permit = 0, is_ipv6 = 1 } } }) +print(vpp.dump(reply)) +print("---") +interface_acl_in = reply[1].acl_index + +reply = vpp:api_call("acl_add_replace", { context = 42, acl_index = -1, count = 3, r = { { is_permit = 1, is_ipv6 = 1 }, { is_permit = 0, is_ipv6 = 1 }, { is_permit = 1, is_ipv6 = 0 } } }) +print(vpp.dump(reply)) +print("---") +interface_acl_out = reply[1].acl_index + + +reply = vpp:api_call("acl_interface_add_del", { context = 42, sw_if_index = 0, is_add = 1, is_input = 1, acl_index = interface_acl_in }) +print(vpp.dump(reply)) +print("---") + +reply = vpp:api_call("acl_interface_add_del", { context = 42, sw_if_index = 0, is_add = 1, is_input = 1, acl_index = interface_acl_in }) +print(vpp.dump(reply)) +print("---") + +reply = vpp:api_call("acl_interface_add_del", { context = 42, sw_if_index = 0, is_add = 1, is_input = 0, acl_index = interface_acl_out }) +print(vpp.dump(reply)) +print("---") + +reply = vpp:api_call("acl_interface_add_del", { context = 42, sw_if_index = 0, is_add = 1, is_input = 0, acl_index = interface_acl_out }) +print(vpp.dump(reply)) +print("---") + +reply = vpp:api_call("acl_add_replace", { context = 42, acl_index = -1, count = 0 }) +print(vpp.dump(reply)) +print("---") + +acl_index_to_delete = reply[1].acl_index +print("Deleting " .. tostring(acl_index_to_delete)) +reply = vpp:api_call("acl_del", { context = 42, acl_index = acl_index_to_delete }) +print(vpp.dump(reply)) +print("---") + +reply = vpp:api_call("acl_dump", { context = 42, sw_if_index = 0}) +for ri, rv in ipairs(reply) do + print("Reply message #" .. tostring(ri)) + print(vpp.dump(rv)) + for ai, av in ipairs(rv.r) do + print("ACL rule #" .. tostring(ai) .. " : " .. vpp.dump(av)) + end + +end +print("---") + +reply = vpp:api_call("acl_del", { context = 42, acl_index = interface_acl_out }) +print(vpp.dump(reply)) +print("---") +reply = vpp:api_call("acl_del", { context = 42, acl_index = interface_acl_in }) +print(vpp.dump(reply)) +print("---") + +reply = vpp:api_call("acl_dump", { context = 42, sw_if_index = 0}) +print(vpp.dump(reply)) +print("---") + +reply = vpp:api_call("acl_dump", { context = 42, sw_if_index = 4294967295 }) +print(vpp.dump(reply)) +print("---") + + +]] -- end of comment out + +---- Should be nothing ^^ +r = { + { is_permit = 1, is_ipv6 = 1, dst_ip_addr = ip46("2001:db8:1::2"), dst_ip_prefix_len = 128 }, + { is_permit = 0, is_ipv6 = 1, dst_ip_addr = ip46("2001:db8:1::3"), dst_ip_prefix_len = 128 }, + { is_permit = 1, is_ipv6 = 1, dst_ip_addr = ip46("2001:db8::"), dst_ip_prefix_len = 32 }, + { is_permit = 1, is_ipv6 = 0, dst_ip_addr = ip46("192.0.2.2"), dst_ip_prefix_len = 32}, + { is_permit = 0, is_ipv6 = 0, dst_ip_addr = ip46("192.0.2.3"), dst_ip_prefix_len = 32 }, +} + +reply = vpp:api_call("acl_add_replace", { context = 42, acl_index = -1, count = 5, r = r }) +print(vpp.dump(reply)) +print("---") +interface_acl_in = reply[1].acl_index + +reply = vpp:api_call("acl_add_replace", { context = 42, acl_index = -1, count = 3, r = { { is_permit = 1, is_ipv6 = 1 }, { is_permit = 0, is_ipv6 = 1 }, { is_permit = 1, is_ipv6 = 0 } } }) +print(vpp.dump(reply)) +print("---") +interface_acl_out = reply[1].acl_in + +reply = vpp:api_call("acl_interface_add_del", { context = 42, sw_if_index = vpp_if_to_s1, is_add = 1, is_input = 1, acl_index = interface_acl_in }) +print(vpp.dump(reply)) +print("---") + +--reply = vpp:api_call("acl_interface_add_del", { context = 42, sw_if_index = vpp_if_to_s2, is_add = 1, is_input = 0, acl_index = interface_acl_out }) +-- print(vpp.dump(reply)) +--print("---") + +^D^D^D + +run VPP clear trace +run VPP trace add af-packet-input 100 +run s1 ping6 -c 3 2001:db8:1::2 +expect s1 packet loss +run VPP show trace +expect VPP match: inacl 0 rule 0 + +run VPP clear trace +run VPP trace add af-packet-input 100 +run s1 ping6 -c 3 2001:db8:1::3 +expect s1 packet loss +run VPP show trace +expect VPP match: inacl 0 rule 1 + +run VPP clear trace +run VPP trace add af-packet-input 100 +run s1 ping6 -c 3 2001:db8:1::4 +expect s1 packet loss +run VPP show trace +expect VPP match: inacl 0 rule 2 + +run VPP clear trace +run VPP trace add af-packet-input 100 +run s1 ping -c 3 192.0.2.2 +expect s1 packet loss +run VPP show trace +expect VPP match: inacl 0 rule 3 + +run VPP clear trace +run VPP trace add af-packet-input 100 +run s1 ping -c 3 192.0.2.3 +expect s1 packet loss +run VPP show trace +expect VPP match: inacl 0 rule 4 + + +cd lua + +--- TEST OUTBOUND ACL + +r1 = { + { is_permit = 1, is_ipv6 = 1, src_ip_addr = ip46("2001:db8:1::1"), src_ip_prefix_len = 128, dst_ip_addr = ip46("2001:db8:1::2"), dst_ip_prefix_len = 128 }, + { is_permit = 0, is_ipv6 = 1, src_ip_addr = ip46("2001:db8:1::1"), src_ip_prefix_len = 128, dst_ip_addr = ip46("2001:db8:1::4"), dst_ip_prefix_len = 128 } +} + +reply = vpp:api_call("acl_add_replace", { context = 42, acl_index = -1, count = 3, r = r1 }) +print(vpp.dump(reply)) +print("---") +interface_acl_out = reply[1].acl_index + +reply = vpp:api_call("acl_interface_add_del", { context = 42, sw_if_index = vpp_if_to_s2, is_add = 1, is_input = 0, acl_index = interface_acl_out }) +print(vpp.dump(reply)) +print("---") + + +^D^D^D + +run VPP clear trace +run VPP trace add af-packet-input 100 +run s1 ping6 -c 3 2001:db8:1::2 +expect s1 packet loss +run VPP show trace +expect VPP match: outacl 2 rule 0 + +run VPP clear trace +run VPP trace add af-packet-input 100 +run s1 ping6 -c 3 2001:db8:1::3 +expect s1 packet loss +run VPP show trace +expect VPP match: inacl 0 rule 1 + +run VPP clear trace +run VPP trace add af-packet-input 100 +run s1 ping6 -c 3 2001:db8:1::4 +expect s1 packet loss +run VPP show trace +expect VPP match: outacl 2 rule 1 + +run lua print("ALL GOOD!") + diff --git a/src/vpp-api/lua/examples/lute/script.lute b/src/vpp-api/lua/examples/lute/script.lute new file mode 100644 index 00000000..c3dd90f2 --- /dev/null +++ b/src/vpp-api/lua/examples/lute/script.lute @@ -0,0 +1,7 @@ +shell s1 +expect s1 $ +run s1 echo testing123 +expect s1 $ +run s1 echo done +quit + diff --git a/src/vpp-api/lua/examples/lute/sessions-acl.lute b/src/vpp-api/lua/examples/lute/sessions-acl.lute new file mode 100644 index 00000000..ac237ef9 --- /dev/null +++ b/src/vpp-api/lua/examples/lute/sessions-acl.lute @@ -0,0 +1,308 @@ +run lua -- collectgarbage("stop") + +shell vppbuild +run vppbuild stty -echo +run vppbuild sudo -u ubuntu -i bash -c "(cd vpp && make plugins && echo ALLGOOD)" +expect vppbuild ALLGOOD + +shell s0 +shell s1 +shell s2 + + +cd s1 +unshare -n /bin/bash +/sbin/ifconfig -a +^D^D^D + +cd s2 +unshare -n /bin/bash +/sbin/ifconfig -a +^D^D^D + + +cd lua + +function session_get_bash_pid(s) + if not has_session(s) then + return nil + end + local fname = "/tmp/lute-"..s.."-pid.txt" + + session_exec(s, "echo $$ >" .. fname) + -- it's a dirty hack but it's quick + sleep(0.5) + local pid = io.lines(fname)() + print("Got pid for " .. s .. " : " .. tostring(pid)) + return(tonumber(pid)) +end + +function session_connect_with(s0, s1) + -- local pid0 = tostring(session_get_bash_pid(s0)) + local pid1 = tostring(session_get_bash_pid(s1)) + local eth_options = { "rx", "tx", "sg", "tso", "ufo", "gso", "gro", "lro", "rxvlan", "txvlan", "rxhash" } + local this_end = s0 .. "_" .. s1 + local other_end = s1 .. "_" .. s0 + session_exec(s0, "ip link add name " .. this_end .. " type veth peer name " .. other_end) + session_exec(s0, "ip link set dev " .. this_end .. " up promisc on") + for i, option in ipairs(eth_options) do + session_exec(s0, "/sbin/ethtool --offload " .. this_end .. " " .. option .. " off") + session_exec(s0, "/sbin/ethtool --offload " .. other_end .. " " .. option .. " off") + end + session_exec(s0, "ip link set dev " .. other_end .. " up promisc on netns /proc/" .. pid1 .. "/ns/net") + sleep(0.5) +end + +^D^D^D +run lua session_connect_with("s0", "s1") +run lua session_connect_with("s0", "s2") + +cd s1 +ip -6 addr add dev s1_s0 2001:db8:1::1/64 +ip -4 addr add dev s1_s0 192.0.2.1/24 +ip link set dev s1_s0 up promisc on +^D^D^D + +cd s2 +ip -6 addr add dev s2_s0 2001:db8:1::2/64 +ip -6 addr add dev s2_s0 2001:db8:1::3/64 +ip -6 addr add dev s2_s0 2001:db8:1::4/64 +ip -4 addr add dev s2_s0 192.0.2.2/24 +ip -4 addr add dev s2_s0:1 192.0.2.3/24 +ip -4 addr add dev s2_s0:2 192.0.2.4/24 +ip link set dev s2_s0 up promisc on +^D^D^D + +run s1 ip addr +run s2 ip addr +shell VPP +cd VPP +cd /home/ubuntu/vpp +make debug +r +^D^D^D +expect VPP DBGvpp# + +cd lua +-- Initialization of the Lua environment for talking to VPP +vpp = require("vpp-lapi") +root_dir = "/home/ubuntu/vpp" +pneum_path = root_dir .. "/build-root/install-vpp_debug-native/vpp-api/lib64/libpneum.so" +vpp:init({ pneum_path = pneum_path }) +vpp:consume_api(root_dir .. "/build-root/install-vpp_debug-native/vlib-api/vlibmemory/memclnt.api") +vpp:consume_api(root_dir .. "/build-root/install-vpp_debug-native/vpp/vpp-api/vpe.api") +vpp:connect("aytest") +vpp:consume_api(root_dir .. "/plugins/acl-plugin/acl/acl.api", "acl") + +^D^D^D + +cd lua + +reply = vpp:api_call("af_packet_create", { host_if_name = "s0_s1", hw_addr = "AAAAAA" }) +vpp_if_to_s1 = reply[1].sw_if_index + +reply = vpp:api_call("af_packet_create", { host_if_name = "s0_s2", hw_addr = "AAAAAA" }) +vpp_if_to_s2 = reply[1].sw_if_index + +ifaces = { vpp_if_to_s1, vpp_if_to_s2 } + +reply = vpp:api_call("sw_interface_set_flags", { sw_if_index = vpp_if_to_s1, admin_up_down = 1, link_up_down = 1 }) +print(vpp.dump(reply)) +reply = vpp:api_call("sw_interface_set_flags", { sw_if_index = vpp_if_to_s2, admin_up_down = 1, link_up_down = 1 }) +print(vpp.dump(reply)) + +bd_id = 42 + +reply = vpp:api_call("bridge_domain_add_del", { bd_id = bd_id, flood = 1, uu_flood = 1, forward = 1, learn = 1, arp_term = 0, is_add = 1 }) +print(vpp.dump(reply)) + +for i, v in ipairs(ifaces) do + reply = vpp:api_call("sw_interface_set_l2_bridge", { rx_sw_if_index = v, bd_id = bd_id, shg = 0, bvi = 0, enable = 1 } ) + print(vpp.dump(reply)) +end + +^D^D^D + +run s1 ping -c 3 192.0.2.2 +expect s1 packet loss +run s1 ping -c 3 192.0.2.3 +expect s1 packet loss +run s1 ping -c 3 192.0.2.4 +expect s1 packet loss +run s1 ping6 -c 3 2001:db8:1::2 +expect s1 packet loss +run s1 ping6 -c 3 2001:db8:1::3 +expect s1 packet loss +run s1 ping6 -c 3 2001:db8:1::4 +expect s1 packet loss + + +cd lua +--- ACL testing + +--[[ temporary comment out + +reply = vpp:api_call("acl_del", { context = 42, acl_index = 230 }) +print(vpp.dump(reply)) +print("---") + +reply = vpp:api_call("acl_del", { context = 42, acl_index = 8 }) +print(vpp.dump(reply)) +print("---") + +reply = vpp:api_call("acl_del", { context = 42, acl_index = 15 }) +print(vpp.dump(reply)) +print("---") + +reply = vpp:api_call("acl_add", { context = 42, count = 2, r = { { is_permit = 1, is_ipv6 = 1 }, { is_permit = 0, is_ipv6 = 1 } } }) +print(vpp.dump(reply)) +print("---") +interface_acl_in = reply[1].acl_index + +reply = vpp:api_call("acl_add", { context = 42, count = 3, r = { { is_permit = 1, is_ipv6 = 1 }, { is_permit = 0, is_ipv6 = 1 }, { is_permit = 1, is_ipv6 = 0 } } }) +print(vpp.dump(reply)) +print("---") +interface_acl_out = reply[1].acl_index + + +reply = vpp:api_call("acl_interface_add_del", { context = 42, sw_if_index = 0, is_add = 1, is_input = 1, acl_index = interface_acl_in }) +print(vpp.dump(reply)) +print("---") + +reply = vpp:api_call("acl_interface_add_del", { context = 42, sw_if_index = 0, is_add = 1, is_input = 1, acl_index = interface_acl_in }) +print(vpp.dump(reply)) +print("---") + +reply = vpp:api_call("acl_interface_add_del", { context = 42, sw_if_index = 0, is_add = 1, is_input = 0, acl_index = interface_acl_out }) +print(vpp.dump(reply)) +print("---") + +reply = vpp:api_call("acl_interface_add_del", { context = 42, sw_if_index = 0, is_add = 1, is_input = 0, acl_index = interface_acl_out }) +print(vpp.dump(reply)) +print("---") + +reply = vpp:api_call("acl_add", { context = 42, count = 0 }) +print(vpp.dump(reply)) +print("---") + +acl_index_to_delete = reply[1].acl_index +print("Deleting " .. tostring(acl_index_to_delete)) +reply = vpp:api_call("acl_del", { context = 42, acl_index = acl_index_to_delete }) +print(vpp.dump(reply)) +print("---") + +reply = vpp:api_call("acl_dump", { context = 42, sw_if_index = 0}) +for ri, rv in ipairs(reply) do + print("Reply message #" .. tostring(ri)) + print(vpp.dump(rv)) + for ai, av in ipairs(rv.r) do + print("ACL rule #" .. tostring(ai) .. " : " .. vpp.dump(av)) + end + +end +print("---") + +reply = vpp:api_call("acl_del", { context = 42, acl_index = interface_acl_out }) +print(vpp.dump(reply)) +print("---") +reply = vpp:api_call("acl_del", { context = 42, acl_index = interface_acl_in }) +print(vpp.dump(reply)) +print("---") + +reply = vpp:api_call("acl_dump", { context = 42, sw_if_index = 0}) +print(vpp.dump(reply)) +print("---") + +reply = vpp:api_call("acl_dump", { context = 42, sw_if_index = 4294967295 }) +print(vpp.dump(reply)) +print("---") + + +]] -- end of comment out + +---- Should be nothing ^^ +r = { + { is_permit = 1, is_ipv6 = 1, dst_ip_addr = ip46("2001:db8:1::2"), dst_ip_prefix_len = 128 }, + { is_permit = 0, is_ipv6 = 1, dst_ip_addr = ip46("2001:db8:1::3"), dst_ip_prefix_len = 128 }, + { is_permit = 1, is_ipv6 = 1, dst_ip_addr = ip46("2001:db8::"), dst_ip_prefix_len = 32 }, + { is_permit = 1, is_ipv6 = 0, dst_ip_addr = ip46("192.0.2.2"), dst_ip_prefix_len = 32}, + { is_permit = 0, is_ipv6 = 0, dst_ip_addr = ip46("192.0.2.3"), dst_ip_prefix_len = 32 }, +} + +reply = vpp:api_call("acl_add", { context = 42, count = 5, r = r }) +print(vpp.dump(reply)) +print("---") +interface_acl_in = reply[1].acl_index + +reply = vpp:api_call("acl_add", { context = 42, count = 3, r = { { is_permit = 1, is_ipv6 = 1 }, { is_permit = 0, is_ipv6 = 1 }, { is_permit = 1, is_ipv6 = 0 } } }) +print(vpp.dump(reply)) +print("---") +interface_acl_out = reply[1].acl_in + +reply = vpp:api_call("acl_interface_add_del", { context = 42, sw_if_index = vpp_if_to_s1, is_add = 1, is_input = 1, acl_index = interface_acl_in }) +print(vpp.dump(reply)) +print("---") + +--- TEST OUTBOUND ACL + +r1 = { + { is_permit = 1, is_ipv6 = 1, src_ip_addr = ip46("2001:db8:1::1"), src_ip_prefix_len = 128, dst_ip_addr = ip46("2001:db8:1::2"), dst_ip_prefix_len = 128 }, + { is_permit = 0, is_ipv6 = 1, src_ip_addr = ip46("2001:db8:1::1"), src_ip_prefix_len = 128, dst_ip_addr = ip46("2001:db8:1::4"), dst_ip_prefix_len = 128 }, + { is_permit = 2, is_ipv6 = 0 } +} + +reply = vpp:api_call("acl_add", { context = 42, count = 3, r = r1 }) +print(vpp.dump(reply)) +print("---") +interface_acl_out = reply[1].acl_index + +reply = vpp:api_call("acl_interface_add_del", { context = 42, sw_if_index = vpp_if_to_s2, is_add = 1, is_input = 0, acl_index = interface_acl_out }) +print(vpp.dump(reply)) +print("---") + +r2 = { + { is_permit = 1, is_ipv6 = 1 }, + { is_permit = 0, is_ipv6 = 0 } +} + +reply = vpp:api_call("acl_add", { context = 42, count = 2, r = r2 }) +print(vpp.dump(reply)) +print("---") +second_interface_acl_in = reply[1].acl_index + +reply = vpp:api_call("acl_interface_add_del", { context = 42, sw_if_index = vpp_if_to_s2, is_add = 1, is_input = 1, acl_index = second_interface_acl_in }) +print(vpp.dump(reply)) +print("---") + +^D^D^D + +run VPP show classify tables +run VPP clear trace +run VPP trace add af-packet-input 100 +run s2 nc -v -l -p 22 +run s1 nc 192.0.2.2 22 +run s1 echo +sleep 1 +run s1 break +sleep 1 +run VPP show trace +expect VPP match: outacl 2 rule 2 +run VPP show classify tables + + +run VPP show classify tables +run VPP clear trace +run VPP trace add af-packet-input 100 +run s2 nc -v -l -p 22 +run s1 nc 192.0.2.2 22 +run s1 echo +sleep 1 +run s1 break +sleep 1 +run VPP show trace +expect VPP match: outacl 2 rule 2 +run VPP show classify tables + + +run lua print("ALL GOOD!") + diff --git a/src/vpp-api/lua/vpp-lapi.lua b/src/vpp-api/lua/vpp-lapi.lua new file mode 100644 index 00000000..587eb110 --- /dev/null +++ b/src/vpp-api/lua/vpp-lapi.lua @@ -0,0 +1,989 @@ +--[[ +/* + * Copyright (c) 2016 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. + */ +]] + +-- json decode/encode from https://gist.github.com/tylerneylon/59f4bcf316be525b30ab +-- licensed by the author tylerneylon into public domain. Thanks! + +local json = {} + +-- Internal functions. + +local function kind_of(obj) + if type(obj) ~= 'table' then return type(obj) end + local i = 1 + for _ in pairs(obj) do + if obj[i] ~= nil then i = i + 1 else return 'table' end + end + if i == 1 then return 'table' else return 'array' end +end + +local function escape_str(s) + local in_char = {'\\', '"', '/', '\b', '\f', '\n', '\r', '\t'} + local out_char = {'\\', '"', '/', 'b', 'f', 'n', 'r', 't'} + for i, c in ipairs(in_char) do + s = s:gsub(c, '\\' .. out_char[i]) + end + return s +end + +-- Returns pos, did_find; there are two cases: +-- 1. Delimiter found: pos = pos after leading space + delim; did_find = true. +-- 2. Delimiter not found: pos = pos after leading space; did_find = false. +-- This throws an error if err_if_missing is true and the delim is not found. +local function skip_delim(str, pos, delim, err_if_missing) + pos = pos + #str:match('^%s*', pos) + if str:sub(pos, pos) ~= delim then + if err_if_missing then + error('Expected ' .. delim .. ' near position ' .. pos) + end + return pos, false + end + return pos + 1, true +end + +-- Expects the given pos to be the first character after the opening quote. +-- Returns val, pos; the returned pos is after the closing quote character. +local function parse_str_val(str, pos, val) + val = val or '' + local early_end_error = 'End of input found while parsing string.' + if pos > #str then error(early_end_error) end + local c = str:sub(pos, pos) + if c == '"' then return val, pos + 1 end + if c ~= '\\' then return parse_str_val(str, pos + 1, val .. c) end + -- We must have a \ character. + local esc_map = {b = '\b', f = '\f', n = '\n', r = '\r', t = '\t'} + local nextc = str:sub(pos + 1, pos + 1) + if not nextc then error(early_end_error) end + return parse_str_val(str, pos + 2, val .. (esc_map[nextc] or nextc)) +end + +-- Returns val, pos; the returned pos is after the number's final character. +local function parse_num_val(str, pos) + local num_str = str:match('^-?%d+%.?%d*[eE]?[+-]?%d*', pos) + local val = tonumber(num_str) + if not val then error('Error parsing number at position ' .. pos .. '.') end + return val, pos + #num_str +end + + +-- Public values and functions. + +function json.stringify(obj, as_key) + local s = {} -- We'll build the string as an array of strings to be concatenated. + local kind = kind_of(obj) -- This is 'array' if it's an array or type(obj) otherwise. + if kind == 'array' then + if as_key then error('Can\'t encode array as key.') end + s[#s + 1] = '[' + for i, val in ipairs(obj) do + if i > 1 then s[#s + 1] = ', ' end + s[#s + 1] = json.stringify(val) + end + s[#s + 1] = ']' + elseif kind == 'table' then + if as_key then error('Can\'t encode table as key.') end + s[#s + 1] = '{' + for k, v in pairs(obj) do + if #s > 1 then s[#s + 1] = ', ' end + s[#s + 1] = json.stringify(k, true) + s[#s + 1] = ':' + s[#s + 1] = json.stringify(v) + end + s[#s + 1] = '}' + elseif kind == 'string' then + return '"' .. escape_str(obj) .. '"' + elseif kind == 'number' then + if as_key then return '"' .. tostring(obj) .. '"' end + return tostring(obj) + elseif kind == 'boolean' then + return tostring(obj) + elseif kind == 'nil' then + return 'null' + else + error('Unjsonifiable type: ' .. kind .. '.') + end + return table.concat(s) +end + +json.null = {} -- This is a one-off table to represent the null value. + +function json.parse(str, pos, end_delim) + pos = pos or 1 + if pos > #str then error('Reached unexpected end of input.') end + local pos = pos + #str:match('^%s*', pos) -- Skip whitespace. + local first = str:sub(pos, pos) + if first == '{' then -- Parse an object. + local obj, key, delim_found = {}, true, true + pos = pos + 1 + while true do + key, pos = json.parse(str, pos, '}') + if key == nil then return obj, pos end + if not delim_found then error('Comma missing between object items.') end + pos = skip_delim(str, pos, ':', true) -- true -> error if missing. + obj[key], pos = json.parse(str, pos) + pos, delim_found = skip_delim(str, pos, ',') + end + elseif first == '[' then -- Parse an array. + local arr, val, delim_found = {}, true, true + pos = pos + 1 + while true do + val, pos = json.parse(str, pos, ']') + if val == nil then return arr, pos end + if not delim_found then error('Comma missing between array items.') end + arr[#arr + 1] = val + pos, delim_found = skip_delim(str, pos, ',') + end + elseif first == '"' then -- Parse a string. + return parse_str_val(str, pos + 1) + elseif first == '-' or first:match('%d') then -- Parse a number. + return parse_num_val(str, pos) + elseif first == end_delim then -- End of an object or array. + return nil, pos + 1 + else -- Parse true, false, or null. + local literals = {['true'] = true, ['false'] = false, ['null'] = json.null} + for lit_str, lit_val in pairs(literals) do + local lit_end = pos + #lit_str - 1 + if str:sub(pos, lit_end) == lit_str then return lit_val, lit_end + 1 end + end + local pos_info_str = 'position ' .. pos .. ': ' .. str:sub(pos, pos + 10) + error('Invalid json syntax starting at ' .. pos_info_str) + end +end + + +local vpp = {} + +local ffi = require("ffi") + +--[[ + +The basic type definitions. A bit of weird gymnastic with +unionization of the hton* and ntoh* functions results +is to make handling of signed and unsigned types a bit cleaner, +essentially building typecasting into a C union. + +The vl_api_opaque_message_t is a synthetic type assumed to have +enough storage to hold the entire API message regardless of the type. +During the operation it is casted to the specific message struct types. + +]] + + +ffi.cdef([[ + +typedef uint8_t u8; +typedef int8_t i8; +typedef uint16_t u16; +typedef int16_t i16; +typedef uint32_t u32; +typedef int32_t i32; +typedef uint64_t u64; +typedef int64_t i64; +typedef double f64; +typedef float f32; + +#pragma pack(1) +typedef union { + u16 u16; + i16 i16; +} lua_ui16t; + +#pragma pack(1) +typedef union { + u32 u32; + i32 i32; +} lua_ui32t; + +u16 ntohs(uint16_t hostshort); +u16 htons(uint16_t hostshort); +u32 htonl(uint32_t along); +u32 ntohl(uint32_t along); +void *memset(void *s, int c, size_t n); +void *memcpy(void *dest, void *src, size_t n); + +#pragma pack(1) +typedef struct _vl_api_opaque_message { + u16 _vl_msg_id; + u8 data[65536]; +} vl_api_opaque_message_t; +]]) + + +-- CRC-based version stuff + +local crc32c_table = ffi.new('const uint32_t[256]', + { 0x00000000, 0xF26B8303, 0xE13B70F7, 0x1350F3F4, + 0xC79A971F, 0x35F1141C, 0x26A1E7E8, 0xD4CA64EB, + 0x8AD958CF, 0x78B2DBCC, 0x6BE22838, 0x9989AB3B, + 0x4D43CFD0, 0xBF284CD3, 0xAC78BF27, 0x5E133C24, + 0x105EC76F, 0xE235446C, 0xF165B798, 0x030E349B, + 0xD7C45070, 0x25AFD373, 0x36FF2087, 0xC494A384, + 0x9A879FA0, 0x68EC1CA3, 0x7BBCEF57, 0x89D76C54, + 0x5D1D08BF, 0xAF768BBC, 0xBC267848, 0x4E4DFB4B, + 0x20BD8EDE, 0xD2D60DDD, 0xC186FE29, 0x33ED7D2A, + 0xE72719C1, 0x154C9AC2, 0x061C6936, 0xF477EA35, + 0xAA64D611, 0x580F5512, 0x4B5FA6E6, 0xB93425E5, + 0x6DFE410E, 0x9F95C20D, 0x8CC531F9, 0x7EAEB2FA, + 0x30E349B1, 0xC288CAB2, 0xD1D83946, 0x23B3BA45, + 0xF779DEAE, 0x05125DAD, 0x1642AE59, 0xE4292D5A, + 0xBA3A117E, 0x4851927D, 0x5B016189, 0xA96AE28A, + 0x7DA08661, 0x8FCB0562, 0x9C9BF696, 0x6EF07595, + 0x417B1DBC, 0xB3109EBF, 0xA0406D4B, 0x522BEE48, + 0x86E18AA3, 0x748A09A0, 0x67DAFA54, 0x95B17957, + 0xCBA24573, 0x39C9C670, 0x2A993584, 0xD8F2B687, + 0x0C38D26C, 0xFE53516F, 0xED03A29B, 0x1F682198, + 0x5125DAD3, 0xA34E59D0, 0xB01EAA24, 0x42752927, + 0x96BF4DCC, 0x64D4CECF, 0x77843D3B, 0x85EFBE38, + 0xDBFC821C, 0x2997011F, 0x3AC7F2EB, 0xC8AC71E8, + 0x1C661503, 0xEE0D9600, 0xFD5D65F4, 0x0F36E6F7, + 0x61C69362, 0x93AD1061, 0x80FDE395, 0x72966096, + 0xA65C047D, 0x5437877E, 0x4767748A, 0xB50CF789, + 0xEB1FCBAD, 0x197448AE, 0x0A24BB5A, 0xF84F3859, + 0x2C855CB2, 0xDEEEDFB1, 0xCDBE2C45, 0x3FD5AF46, + 0x7198540D, 0x83F3D70E, 0x90A324FA, 0x62C8A7F9, + 0xB602C312, 0x44694011, 0x5739B3E5, 0xA55230E6, + 0xFB410CC2, 0x092A8FC1, 0x1A7A7C35, 0xE811FF36, + 0x3CDB9BDD, 0xCEB018DE, 0xDDE0EB2A, 0x2F8B6829, + 0x82F63B78, 0x709DB87B, 0x63CD4B8F, 0x91A6C88C, + 0x456CAC67, 0xB7072F64, 0xA457DC90, 0x563C5F93, + 0x082F63B7, 0xFA44E0B4, 0xE9141340, 0x1B7F9043, + 0xCFB5F4A8, 0x3DDE77AB, 0x2E8E845F, 0xDCE5075C, + 0x92A8FC17, 0x60C37F14, 0x73938CE0, 0x81F80FE3, + 0x55326B08, 0xA759E80B, 0xB4091BFF, 0x466298FC, + 0x1871A4D8, 0xEA1A27DB, 0xF94AD42F, 0x0B21572C, + 0xDFEB33C7, 0x2D80B0C4, 0x3ED04330, 0xCCBBC033, + 0xA24BB5A6, 0x502036A5, 0x4370C551, 0xB11B4652, + 0x65D122B9, 0x97BAA1BA, 0x84EA524E, 0x7681D14D, + 0x2892ED69, 0xDAF96E6A, 0xC9A99D9E, 0x3BC21E9D, + 0xEF087A76, 0x1D63F975, 0x0E330A81, 0xFC588982, + 0xB21572C9, 0x407EF1CA, 0x532E023E, 0xA145813D, + 0x758FE5D6, 0x87E466D5, 0x94B49521, 0x66DF1622, + 0x38CC2A06, 0xCAA7A905, 0xD9F75AF1, 0x2B9CD9F2, + 0xFF56BD19, 0x0D3D3E1A, 0x1E6DCDEE, 0xEC064EED, + 0xC38D26C4, 0x31E6A5C7, 0x22B65633, 0xD0DDD530, + 0x0417B1DB, 0xF67C32D8, 0xE52CC12C, 0x1747422F, + 0x49547E0B, 0xBB3FFD08, 0xA86F0EFC, 0x5A048DFF, + 0x8ECEE914, 0x7CA56A17, 0x6FF599E3, 0x9D9E1AE0, + 0xD3D3E1AB, 0x21B862A8, 0x32E8915C, 0xC083125F, + 0x144976B4, 0xE622F5B7, 0xF5720643, 0x07198540, + 0x590AB964, 0xAB613A67, 0xB831C993, 0x4A5A4A90, + 0x9E902E7B, 0x6CFBAD78, 0x7FAB5E8C, 0x8DC0DD8F, + 0xE330A81A, 0x115B2B19, 0x020BD8ED, 0xF0605BEE, + 0x24AA3F05, 0xD6C1BC06, 0xC5914FF2, 0x37FACCF1, + 0x69E9F0D5, 0x9B8273D6, 0x88D28022, 0x7AB90321, + 0xAE7367CA, 0x5C18E4C9, 0x4F48173D, 0xBD23943E, + 0xF36E6F75, 0x0105EC76, 0x12551F82, 0xE03E9C81, + 0x34F4F86A, 0xC69F7B69, 0xD5CF889D, 0x27A40B9E, + 0x79B737BA, 0x8BDCB4B9, 0x988C474D, 0x6AE7C44E, + 0xBE2DA0A5, 0x4C4623A6, 0x5F16D052, 0xAD7D5351 } +); + +local function CRC8(crc, d) + return bit.bxor(bit.rshift(crc, 8), crc32c_table[bit.band(0xff, bit.bxor(crc, d))]) +end + +local function CRC16(crc, d) + crc = CRC8(crc, bit.band(d, 0xFF)) + d = bit.rshift(d, 8) + crc = CRC8(crc, bit.band(d, 0xFF)) + return crc +end + +local function string_crc(str, crc) + for i=1,#str do + -- print("S", i, string.byte(str, i), string.char(string.byte(str, i))) + crc = CRC8(crc, string.byte(str, i)) + end + return crc +end + +local tokens = { + { ["match"] =' ', ["act"] = { } }, + { ["match"] ='\n', ["act"] = { } }, + { ["match"] ="manual_endian", ["act"] = { "NODE_MANUAL_ENDIAN", "MANUAL_ENDIAN", 276 } }, + { ["match"] ="define", ["act"] = { "NODE_DEFINE", "DEFINE", 267 } }, + { ["match"] ="dont_trace", ["act"] = { "NODE_DONT_TRACE", "DONT_TRACE", 279 } }, + { ["match"] ="f64", ["act"] = { "NODE_F64", "PRIMTYPE", string_crc } }, + { ["match"] ="i16", ["act"] = { "NODE_I16", "PRIMTYPE", string_crc } }, + { ["match"] ="i32", ["act"] = { "NODE_I32", "PRIMTYPE", string_crc } }, + { ["match"] ="i64", ["act"] = { "NODE_I64", "PRIMTYPE", string_crc } }, + { ["match"] ="i8", ["act"] = { "NODE_I8", "PRIMTYPE", string_crc } }, + { ["match"] ="manual_print", ["act"] = { "NODE_MANUAL_PRINT", "MANUAL_PRINT", 275 } }, + { ["match"] ="noversion", ["act"] = { "NODE_NOVERSION", "NOVERSION", 274 } }, + { ["match"] ="packed", ["act"] = { "NODE_PACKED", "TPACKED", 266 } }, + { ["match"] ="typeonly", ["act"] = { "NODE_TYPEONLY", "TYPEONLY", 278 } }, + { ["match"] ="u16", ["act"] = { "NODE_U16", "PRIMTYPE", string_crc } }, + { ["match"] ="u32", ["act"] = { "NODE_U32", "PRIMTYPE", string_crc } }, + { ["match"] ="u64", ["act"] = { "NODE_U64", "PRIMTYPE", string_crc } }, + { ["match"] ="u8", ["act"] = { "NODE_U8", "PRIMTYPE", string_crc } }, + { ["match"] ="union", ["act"] = { "NODE_UNION", "UNION", 271 } }, + { ["match"] ="uword", ["act"] = { "NODE_UWORD", "PRIMTYPE", string_crc } }, + { ["match"] ="%(", ["act"] = { "NODE_LPAR", "LPAR", 259 } }, + { ["match"] ="%)", ["act"] = { "NODE_RPAR", "RPAR", 258 } }, + { ["match"] =";", ["act"] = { "NODE_SEMI", "SEMI", 260 } }, + { ["match"] ="%[", ["act"] = { "NODE_LBRACK", "LBRACK", 261 } }, + { ["match"] ="%]", ["act"] = { "NODE_RBRACK", "RBRACK", 262 } }, + { ["match"] ="%{", ["act"] = { "NODE_LCURLY", "LCURLY", 268 } }, + { ["match"] ="%}", ["act"] = { "NODE_RCURLY", "RCURLY", 269 } }, + { ["match"] ='%b""', ["act"] = { "NODE_STRING", "STRING", string_crc } }, + { ["match"] ='%b@@', ["act"] = { "NODE_HELPER", "HELPER_STRING", string_crc } }, + -- TODO: \ must be consumed + { ["match"] ='[_a-zA-Z][_a-zA-Z0-9]*', + ["act"] = { "NODE_NAME", "NAME", string_crc } }, + { ["match"] ='[0-9]+', ["act"] = { "NODE_NUMBER", "NUMBER", string_crc } }, + { ["match"] ='#[^\n]+', ["act"] = { "NODE_PRAGMA", "PRAGMA", nil } }, +} + + +function vpp.crc_version_string(data) + local input_crc = 0 + -- Get rid of comments + data = data:gsub("/%*.-%*/", "") + data = data:gsub("//[^\n]+", "") + -- print(data) + idx = 1 + while (true) do + local matched = nil + for k, v in ipairs(tokens) do + if not matched then + local x, y, cap = string.find(data, v["match"], idx) + if x == idx then + matched = { ["node"] = v["act"], ["x"] = x, ["y"] = y, ["cap"] = cap, ["chars"] = string.sub(data, x, y) } + -- print(k, v, x, y, cap, matched.chars, matched.node[0] ) + end + end + end + if matched then + idx = idx + (matched.y - matched.x + 1) + if matched.node[1] then + local act = matched.node[3] + if type(act) == "function" then + input_crc = act(matched.chars, input_crc) + elseif type(act) == "number" then + input_crc = CRC16(input_crc, act) + end + -- print(vpp.dump(matched)) + end + else + -- print("NOT MATCHED!") + local crc = CRC16(input_crc, 0xFFFFFFFF) + return string.sub(string.format("%x", crc), -8) + end + end +end + + +function vpp.dump(o) + if type(o) == 'table' then + local s = '{ ' + for k,v in pairs(o) do + if type(k) ~= 'number' then k = '"'..k..'"' end + s = s .. '['..k..'] = ' .. vpp.dump(v) .. ',' + end + return s .. '} ' + else + return tostring(o) + end +end + +function vpp.hex_dump(buf) + local ret = {} + for i=1,math.ceil(#buf/16) * 16 do + if (i-1) % 16 == 0 then table.insert(ret, string.format('%08X ', i-1)) end + table.insert(ret, ( i > #buf and ' ' or string.format('%02X ', buf:byte(i)) )) + if i % 8 == 0 then table.insert(ret, ' ') end + if i % 16 == 0 then table.insert(ret, buf:sub(i-16+1, i):gsub('%c','.')..'\n' ) end + end + return table.concat(ret) +end + + +function vpp.c_str(text_in) + local text = text_in -- \000 will be helpfully added by ffi.copy + local c_str = ffi.new("char[?]", #text+1) + ffi.copy(c_str, text) + return c_str +end + + +function vpp.init(vpp, args) + local vac_api = args.vac_api or [[ + int cough_vac_attach(char *vac_path, char *cough_path); + int vac_connect(char *name, char *chroot_prefix, void *cb); + int vac_disconnect(void); + int vac_read(char **data, int *l); + int vac_write(char *data, int len); + void vac_free(char *data); + uint32_t vac_get_msg_index(unsigned char * name); +]] + + vpp.vac_path = args.vac_path + ffi.cdef(vac_api) + local init_res = 0 + vpp.vac = ffi.load(vpp.vac_path) + if (init_res < 0) then + return nil + end + + vpp.next_msg_num = 1 + vpp.msg_name_to_number = {} + vpp.msg_name_to_fields = {} + vpp.msg_number_to_name = {} + vpp.msg_number_to_type = {} + vpp.msg_number_to_pointer_type = {} + vpp.msg_name_to_crc = {} + vpp.c_type_to_fields = {} + vpp.events = {} + vpp.plugin_version = {} + vpp.is_connected = false + + + vpp.t_lua2c = {} + vpp.t_c2lua = {} + vpp.t_lua2c["u8"] = function(c_type, src, dst_c_ptr) + if type(src) == "string" then + -- ffi.copy adds a zero byte at the end. Grrr. + -- ffi.copy(dst_c_ptr, src) + ffi.C.memcpy(dst_c_ptr, vpp.c_str(src), #src) + return(#src) + elseif type(src) == "table" then + for i,v in ipairs(src) do + ffi.cast("u8 *", dst_c_ptr)[i-1] = v + end + return(#src) + else + return 1, src -- ffi.cast("u8", src) + end + end + vpp.t_c2lua["u8"] = function(c_type, src_ptr, src_len) + if src_len then + return ffi.string(src_ptr, src_len) + else + return (tonumber(src_ptr)) + end + end + + vpp.t_lua2c["u16"] = function(c_type, src, dst_c_ptr) + if type(src) == "table" then + for i,v in ipairs(src) do + ffi.cast("u16 *", dst_c_ptr)[i-1] = ffi.C.htons(v) + end + return(2 * #src) + else + return 2, (ffi.C.htons(src)) + end + end + vpp.t_c2lua["u16"] = function(c_type, src_ptr, src_len) + if src_len then + local out = {} + for i = 0,src_len-1 do + out[i+1] = tonumber(ffi.C.ntohs(src_ptr[i])) + end + return out + else + return (tonumber(ffi.C.ntohs(src_ptr))) + end + end + + vpp.t_lua2c["u32"] = function(c_type, src, dst_c_ptr) + if type(src) == "table" then + for i,v in ipairs(src) do + ffi.cast("u32 *", dst_c_ptr)[i-1] = ffi.C.htonl(v) + end + return(4 * #src) + else + return 4, (ffi.C.htonl(src)) + end + end + vpp.t_c2lua["u32"] = function(c_type, src_ptr, src_len) + if src_len then + local out = {} + for i = 0,src_len-1 do + out[i+1] = tonumber(ffi.C.ntohl(src_ptr[i])) + end + return out + else + return (tonumber(ffi.C.ntohl(src_ptr))) + end + end + vpp.t_lua2c["i32"] = function(c_type, src, dst_c_ptr) + if type(src) == "table" then + for i,v in ipairs(src) do + ffi.cast("i32 *", dst_c_ptr)[i-1] = ffi.C.htonl(v) + end + return(4 * #src) + else + return 4, (ffi.C.htonl(src)) + end + end + vpp.t_c2lua["i32"] = function(c_type, src_ptr, src_len) + local ntohl = function(src) + local u32val = ffi.cast("u32", src) + local ntohlval = (ffi.C.ntohl(u32val)) + local out = tonumber(ffi.cast("i32", ntohlval + 0LL)) + return out + end + if src_len then + local out = {} + for i = 0,src_len-1 do + out[i+1] = tonumber(ntohl(src_ptr[i])) + end + else + return (tonumber(ntohl(src_ptr))) + end + end + + vpp.t_lua2c["u64"] = function(c_type, src, dst_c_ptr) + if type(src) == "table" then + for i,v in ipairs(src) do + ffi.cast("u64 *", dst_c_ptr)[i-1] = v --- FIXME ENDIAN + end + return(8 * #src) + else + return 8, ffi.cast("u64", src) --- FIXME ENDIAN + end + end + vpp.t_c2lua["u64"] = function(c_type, src_ptr, src_len) + if src_len then + local out = {} + for i = 0,src_len-1 do + out[i+1] = tonumber(src_ptr[i]) -- FIXME ENDIAN + end + return out + else + return (tonumber(src_ptr)) --FIXME ENDIAN + end + end + + + + + vpp.t_lua2c["__MSG__"] = function(c_type, src, dst_c_ptr) + local dst = ffi.cast(c_type .. " *", dst_c_ptr) + local additional_len = 0 + local fields_info = vpp.c_type_to_fields[c_type] + -- print("__MSG__ type: " .. tostring(c_type)) + ffi.C.memset(dst_c_ptr, 0, ffi.sizeof(dst[0])) + -- print(vpp.dump(fields_info)) + -- print(vpp.dump(src)) + for k,v in pairs(src) do + local field = fields_info[k] + if not field then + print("ERROR: field " .. tostring(k) .. " in message " .. tostring(c_type) .. " is unknown") + end + local lua2c = vpp.t_lua2c[field.c_type] + -- print("__MSG__ field " .. tostring(k) .. " : " .. vpp.dump(field)) + -- if the field is not an array type, try to coerce the argument to a number + if not field.array and type(v) == "string" then + v = tonumber(v) + end + if not lua2c then + print("__MSG__ " .. tostring(c_type) .. " t_lua2c: can not store field " .. field.name .. + " type " .. field.c_type .. " dst " .. tostring(dst[k])) + return 0 + end + local len = 0 + local val = nil + if field.array and (type(v) == "table") then + -- print("NTFY: field " .. tostring(k) .. " in message " .. tostring(c_type) .. " is an array") + for field_i, field_v in ipairs(v) do + -- print("NTFY: setting member#" .. tostring(field_i) .. " to value " .. vpp.dump(field_v)) + local field_len, field_val = lua2c(field.c_type, field_v, dst[k][field_i-1]) + len = len + field_len + end + else + len, val = lua2c(field.c_type, v, dst[k]) + end + if not field.array then + dst[k] = val + else + if 0 == field.array then + additional_len = additional_len + len + -- print("Adding " .. tostring(len) .. " bytes due to field " .. tostring(field.name)) + -- If there is a variable storing the length + -- and the input table does not set it, do magic + if field.array_size and not src[field.array_size] then + local size_field = fields_info[field.array_size] + if size_field then + dst[field.array_size] = vpp.t_c2lua[size_field.c_type](size_field.c_type, len) + end + end + end + end + -- print("Full message:\n" .. vpp.hex_dump(ffi.string(ffi.cast('void *', req_store_cache), 64))) + end + return (ffi.sizeof(dst[0])+additional_len) + end + + vpp.t_c2lua["__MSG__"] = function(c_type, src_ptr, src_len) + local out = {} + local reply_typed_ptr = ffi.cast(c_type .. " *", src_ptr) + local field_desc = vpp.c_type_to_fields[c_type] + if src_len then + for i = 0,src_len-1 do + out[i+1] = vpp.t_c2lua[c_type](c_type, src_ptr[i]) + end + return out + end + + for k, v in pairs(field_desc) do + local v_c2lua = vpp.t_c2lua[v.c_type] + if v_c2lua then + local len = v.array + -- print(dump(v)) + if len then + local len_field_name = k .. "_length" + local len_field = field_desc[len_field_name] + if (len_field) then + local real_len = vpp.t_c2lua[len_field.c_type](len_field.c_type, reply_typed_ptr[len_field_name]) + out[k] = v_c2lua(v.c_type, reply_typed_ptr[k], real_len) + elseif len == 0 then + -- check if len = 0, then must be a field which contains the size + len_field = field_desc[v.array_size] + local real_len = vpp.t_c2lua[len_field.c_type](len_field.c_type, reply_typed_ptr[v.array_size]) + -- print("REAL length: " .. vpp.dump(v) .. " : " .. tostring(real_len)) + out[k] = v_c2lua(v.c_type, reply_typed_ptr[k], real_len) + else + -- alas, just stuff the entire array + out[k] = v_c2lua(v.c_type, reply_typed_ptr[k], len) + end + else + out[k] = v_c2lua(v.c_type, reply_typed_ptr[k]) + end + else + out[k] = "<no accessor function for type " .. tostring(v.c_type) .. ">" + end + -- print(k, out[k]) + end + return out + end + + return vpp +end + +function vpp.resolve_message_number(msgname) + local name = msgname .. "_" .. vpp.msg_name_to_crc[msgname] + local idx = vpp.vac.vac_get_msg_index(vpp.c_str(name)) + if vpp.debug_dump then + print("Index for " .. tostring(name) .. " is " .. tostring(idx)) + end + vpp.msg_name_to_number[msgname] = idx + vpp.msg_number_to_name[idx] = msgname + vpp.msg_number_to_type[idx] = "vl_api_" .. msgname .. "_t" + vpp.msg_number_to_pointer_type[idx] = vpp.msg_number_to_type[idx] .. " *" + ffi.cdef("\n\n enum { vl_msg_" .. msgname .. " = " .. idx .. " };\n\n") +end + +function vpp.connect(vpp, client_name) + local name = "lua_client" + if client_name then + name = client_name + end + local ret = vpp.vac.vac_connect(vpp.c_str(client_name), nil, nil) + if tonumber(ret) == 0 then + vpp.is_connected = true + end + for k, v in pairs(vpp.msg_name_to_number) do + vpp.resolve_message_number(k) + end + end + +function vpp.disconnect(vpp) + vpp.vac.vac_disconnect() + end + +function vpp.json_api(vpp, path, plugin_name) + -- print("Consuming the VPP api from "..path) + local ffii = {} + local f = io.open(path, "r") + if not f then + print("Could not open " .. path) + return nil + end + local data = f:read("*all") + local json = json.parse(data) + if not (json.types or json.messages) then + print("Can not parse " .. path) + return nil + end + + local all_types = {} + + for i, v in ipairs(json.types) do + table.insert(all_types, { typeonly = 1, desc = v }) + end + for i, v in ipairs(json.messages) do + table.insert(all_types, { typeonly = 0, desc = v }) + end + for i, v in ipairs(all_types) do + local typeonly = v.typeonly + local name = v.desc[1] + local c_type = "vl_api_" .. name .. "_t" + + local fields = {} + -- vpp.msg_name_to_fields[name] = fields + -- print("CTYPE " .. c_type) + vpp.c_type_to_fields[c_type] = fields + vpp.t_lua2c[c_type] = vpp.t_lua2c["__MSG__"] + vpp.t_c2lua[c_type] = vpp.t_c2lua["__MSG__"] + + local cdef = { "\n\n#pragma pack(1)\ntypedef struct _vl_api_", name, " {\n" } + for ii, vv in ipairs(v.desc) do + if type(vv) == "table" then + if vv.crc then + vpp.msg_name_to_crc[name] = string.sub(vv.crc, 3) -- strip the leading 0x + else + local fieldtype = vv[1] + local fieldname = vv[2] + local fieldcount = vv[3] + local fieldcountvar = vv[4] + local fieldrec = { name = fieldname, c_type = fieldtype, array = fieldcount, array_size = fieldcountvar } + if fieldcount then + table.insert(cdef, " " .. fieldtype .. " " .. fieldname .. "[" .. fieldcount .. "];\n") + if fieldtype == "u8" then + -- any array of bytes is treated as a string + elseif vpp.t_lua2c[fieldtype] then + -- print("Array of " .. fieldtype .. " is ok!") + else + print("Unknown array type: ", name, " : " , fieldname, " : ", fieldtype, ":", fieldcount, ":", fieldcountvar) + end + else + table.insert(cdef, " " .. fieldtype .. " " .. fieldname .. ";\n") + end + fields[fieldname] = fieldrec + end + end + end + + table.insert(cdef, "} vl_api_" .. name .. "_t;") + table.insert(ffii, table.concat(cdef)) + + if typeonly == 0 then + -- we will want to resolve this later + if vpp.debug_dump then + print("Remember to resolve " .. name) + end + vpp.msg_name_to_number[name] = -1 + if vpp.is_connected then + vpp.resolve_message_number(name) + end + end + + end + local cdef_full = table.concat(ffii) + ffi.cdef(cdef_full) +end + +function vpp.consume_api(vpp, path, plugin_name) + -- print("Consuming the VPP api from "..path) + local ffii = {} + local f = io.open(path, "r") + if not f then + print("Could not open " .. path) + return nil + end + local data = f:read("*all") + -- Remove all C comments + data = data:gsub("/%*.-%*/", "") + if vpp.is_connected and not plugin_name then + print(path .. ": must specify plugin name!") + return + end + if plugin_name then + vpp.plugin_version[plugin_name] = vpp.crc_version_string(data) + local full_plugin_name = plugin_name .. "_" .. vpp.plugin_version[plugin_name] + local reply = vpp:api_call("get_first_msg_id", { name = full_plugin_name } ) + vpp.next_msg_num = tonumber(reply[1].first_msg_id) + print("Plugin " .. full_plugin_name .. " first message is " .. tostring(vpp.next_msg_num)) + end + -- print ("data len: ", #data) + data = data:gsub("\n(.-)(%S+)%s*{([^}]*)}", function (preamble, name, members) + local _, typeonly = preamble:gsub("typeonly", "") + local maybe_msg_id_field = { [0] = "u16 _vl_msg_id;", "" } + local onedef = "\n\n#pragma pack(1)\ntypedef struct _vl_api_"..name.. " {\n" .. + -- " u16 _vl_msg_id;" .. + maybe_msg_id_field[typeonly] .. + members:gsub("%[[a-zA-Z_]+]", "[0]") .. + "} vl_api_" .. name .. "_t;" + + local c_type = "vl_api_" .. name .. "_t" + + local fields = {} + -- vpp.msg_name_to_fields[name] = fields + -- print("CTYPE " .. c_type) + vpp.c_type_to_fields[c_type] = fields + vpp.t_lua2c[c_type] = vpp.t_lua2c["__MSG__"] + vpp.t_c2lua[c_type] = vpp.t_c2lua["__MSG__"] + local mirec = { name = "_vl_msg_id", c_type = "u16", array = nil, array_size = nil } + if typeonly == 0 then + fields[mirec.name] = mirec + end + + -- populate the field reflection table for the message + -- sets the various type information as well as the accessors for lua<->C conversion + members:gsub("(%S+)%s+(%S+);", function (fieldtype, fieldname) + local fieldcount = nil + local fieldcountvar = nil + -- data = data:gsub("%[[a-zA-Z_]+]", "[0]") + fieldname = fieldname:gsub("(%b[])", function(cnt) + fieldcount = tonumber(cnt:sub(2, -2)); + if not fieldcount then + fieldcount = 0 + fieldcountvar = cnt:sub(2, -2) + end + return "" + end) + local fieldrec = { name = fieldname, c_type = fieldtype, array = fieldcount, array_size = fieldcountvar } + if fieldcount then + if fieldtype == "u8" then + -- any array of bytes is treated as a string + elseif vpp.t_lua2c[fieldtype] then + -- print("Array of " .. fieldtype .. " is ok!") + else + print("Unknown array type: ", name, " : " , fieldname, " : ", fieldtype, ":", fieldcount, ":", fieldcountvar) + end + end + fields[fieldname] = fieldrec + end) + + -- print(dump(fields)) + + if typeonly == 0 then + local this_message_number = vpp.next_msg_num + vpp.next_msg_num = vpp.next_msg_num + 1 + vpp.msg_name_to_number[name] = this_message_number + vpp.msg_number_to_name[this_message_number] = name + vpp.msg_number_to_type[this_message_number] = "vl_api_" .. name .. "_t" + vpp.msg_number_to_pointer_type[this_message_number] = vpp.msg_number_to_type[this_message_number] .. " *" + onedef = onedef .. "\n\n enum { vl_msg_" .. name .. " = " .. this_message_number .. " };\n\n" + end + table.insert(ffii, onedef); + return ""; + end) + local cdef = table.concat(ffii) + -- print(cdef) + ffi.cdef(cdef) + end + + +function vpp.lua2c(vpp, c_type, src, dst_c_ptr) + -- returns the number of bytes written to memory pointed by dst + local lua2c = vpp.t_lua2c[c_type] + if lua2c then + return(lua2c(c_type, src, dst_c_ptr)) + else + print("vpp.lua2c: do not know how to store type " .. tostring(c_type)) + local x = "a" .. nil + return 0 + end +end + +function vpp.c2lua(vpp, c_type, src_ptr, src_len) + -- returns the lua data structure + local c2lua = vpp.t_c2lua[c_type] + if c2lua then + return(c2lua(c_type, src_ptr, src_len)) + else + print("vpp.c2lua: do not know how to load type " .. c_type) + return nil + end +end + +local req_store_cache = ffi.new("vl_api_opaque_message_t[1]") + +function vpp.api_write(vpp, api_name, req_table) + local msg_num = vpp.msg_name_to_number[api_name] + if not msg_num then + print ("API call "..api_name.." is not known") + return nil + end + + if not req_table then + req_table = {} + end + req_table._vl_msg_id = msg_num + + local packed_len = vpp:lua2c(vpp.msg_number_to_type[msg_num], req_table, req_store_cache) + if vpp.debug_dump then + print("Write Message length: " .. tostring(packed_len) .. "\n" .. vpp.hex_dump(ffi.string(ffi.cast('void *', req_store_cache), packed_len))) + end + + res = vpp.vac.vac_write(ffi.cast('void *', req_store_cache), packed_len) + return res + end + +local rep_store_cache = ffi.new("vl_api_opaque_message_t *[1]") +local rep_len_cache = ffi.new("int[1]") + +function vpp.api_read(vpp) + local rep_type = "vl_api_opaque_message_t" + local rep = rep_store_cache + local replen = rep_len_cache + res = vpp.vac.vac_read(ffi.cast("void *", rep), replen) + if vpp.debug_dump then + print("Read Message length: " .. tostring(replen[0]) .. "\n" .. vpp.hex_dump(ffi.string(ffi.cast('void *', rep[0]), replen[0]))) + end + + local reply_msg_num = ffi.C.ntohs(rep[0]._vl_msg_id) + local reply_msg_name = vpp.msg_number_to_name[reply_msg_num] + + local reply_typed_ptr = ffi.cast(vpp.msg_number_to_pointer_type[reply_msg_num], rep[0]) + local out = vpp:c2lua(vpp.msg_number_to_type[reply_msg_num], rep[0], nil, replen[0]) + if type(out) == "table" then + out["luaapi_message_name"] = reply_msg_name + end + + vpp.vac.vac_free(ffi.cast('void *',rep[0])) + + return reply_msg_name, out + end + +function vpp.api_call(vpp, api_name, req_table, options_in) + local msg_num = vpp.msg_name_to_number[api_name] + local end_message_name = api_name .."_reply" + local replies = {} + local cstruct = "" + local options = options_in or {} + if msg_num then + if vpp.debug_dump then + print("Message #" .. tostring(msg_num) .. " for name " .. tostring(api_name)) + end + vpp:api_write(api_name, req_table) + if not vpp.msg_name_to_number[end_message_name] or options.force_ping then + end_message_name = "control_ping_reply" + vpp:api_write("control_ping") + end + repeat + reply_message_name, reply = vpp:api_read() + if reply and not reply.context then + -- there may be async events inbetween + table.insert(vpp.events, reply) + else + if reply_message_name ~= "control_ping_reply" then + -- do not insert the control ping encapsulation + table.insert(replies, reply) + end + end + -- print(reply) + until reply_message_name == end_message_name + else + print(api_name .. " is an unknown API call") + return nil + end + return replies + end + +return vpp diff --git a/src/vpp-api/python/LICENSE.txt b/src/vpp-api/python/LICENSE.txt new file mode 100644 index 00000000..8f71f43f --- /dev/null +++ b/src/vpp-api/python/LICENSE.txt @@ -0,0 +1,202 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + 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. + diff --git a/src/vpp-api/python/Makefile.am b/src/vpp-api/python/Makefile.am new file mode 100644 index 00000000..e6c064e1 --- /dev/null +++ b/src/vpp-api/python/Makefile.am @@ -0,0 +1,23 @@ +# Copyright (c) 2016 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. + +install-exec-local: + (cd $(srcdir) ; $(PYTHON) $(srcdir)/setup.py build \ + --build-base $(shell readlink -f $(builddir))/build \ + install \ + --root / \ + --prefix $(DESTDIR)$(prefix) \ + --single-version-externally-managed \ + --verbose \ + bdist_egg \ + --dist-dir=$(DESTDIR)$(prefix)) diff --git a/src/vpp-api/python/README.rst b/src/vpp-api/python/README.rst new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/vpp-api/python/README.rst diff --git a/src/vpp-api/python/setup.cfg b/src/vpp-api/python/setup.cfg new file mode 100644 index 00000000..79bc6784 --- /dev/null +++ b/src/vpp-api/python/setup.cfg @@ -0,0 +1,5 @@ +[bdist_wheel] +# This flag says that the code is written to work on both Python 2 and Python +# 3. If at all possible, it is good practice to do this. If you cannot, you +# will need to generate wheels for each Python version that you support. +universal=1 diff --git a/src/vpp-api/python/setup.py b/src/vpp-api/python/setup.py new file mode 100644 index 00000000..626dddee --- /dev/null +++ b/src/vpp-api/python/setup.py @@ -0,0 +1,33 @@ +# +# Copyright (c) 2016 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. + +try: + from setuptools import setup +except ImportError: + from distutils.core import setup + +setup (name = 'vpp_papi', + version = '1.4', + description = 'VPP Python binding', + author = 'Ole Troan', + author_email = 'ot@cisco.com', + url = 'https://wiki.fd.io/view/VPP/Python_API', + python_requires='>=2.7, >=3.3', + license = 'Apache-2.0', + test_suite = 'tests', + install_requires=['cffi >= 1.10'], + py_modules=['vpp_papi'], + long_description = '''VPP Python language binding.''', + zip_safe = True, +) diff --git a/src/vpp-api/python/tests/test_cli.py b/src/vpp-api/python/tests/test_cli.py new file mode 100755 index 00000000..66fb6943 --- /dev/null +++ b/src/vpp-api/python/tests/test_cli.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python + +from __future__ import print_function +import unittest, sys, time, threading, struct +import test_base +import vpp_papi +from ipaddress import * + +import glob, subprocess +class TestPAPI(unittest.TestCase): + @classmethod + def setUpClass(cls): + # + # Start main VPP process + cls.vpp_bin = glob.glob(test_base.scriptdir+'/../../../build-root/install-vpp*-native/vpp/bin/vpp')[0] + print("VPP BIN:", cls.vpp_bin) + cls.vpp = subprocess.Popen([cls.vpp_bin, "unix", "nodaemon"], stderr=subprocess.PIPE) + print('Started VPP') + # For some reason unless we let VPP start up the API cannot connect. + time.sleep(0.3) + @classmethod + def tearDownClass(cls): + cls.vpp.terminate() + + def setUp(self): + print("Connecting API") + r = vpp_papi.connect("test_papi") + self.assertEqual(r, 0) + + def tearDown(self): + r = vpp_papi.disconnect() + self.assertEqual(r, 0) + + # + # The tests themselves + # + + # + # Basic request / reply + # + def test_cli_request(self): + print(vpp_papi.cli_exec('show version verbose')) + #t = vpp_papi.cli_inband_request(len(cmd), cmd) + #print('T:',t) + #reply = t.reply[0].decode().rstrip('\x00') + #print(reply) + #program = t.program.decode().rstrip('\x00') + #self.assertEqual('vpe', program) + + +if __name__ == '__main__': + unittest.main() diff --git a/src/vpp-api/python/tests/test_modules.py b/src/vpp-api/python/tests/test_modules.py new file mode 100755 index 00000000..fdcd092c --- /dev/null +++ b/src/vpp-api/python/tests/test_modules.py @@ -0,0 +1,18 @@ +from __future__ import print_function +import unittest +import vpp_papi +import pot, snat +print('Plugins:') +vpp_papi.plugin_show() +r = vpp_papi.connect('ole') + +r = vpp_papi.show_version() +print('R:', r) + +r = snat.snat_interface_add_del_feature(1, 1, 1) +print('R:', r) + +list_name = 'foobar' +r = pot.pot_profile_add(0, 1, 123, 123, 0, 12, 0, 23, len(list_name), list_name) +print('R:', r) +vpp_papi.disconnect() diff --git a/src/vpp-api/python/tests/test_papi.py b/src/vpp-api/python/tests/test_papi.py new file mode 100755 index 00000000..8cbbfc59 --- /dev/null +++ b/src/vpp-api/python/tests/test_papi.py @@ -0,0 +1,119 @@ +from __future__ import print_function +import unittest, sys, time, threading, struct, logging, os +import vpp_papi +from ipaddress import * +scriptdir = os.path.dirname(os.path.realpath(__file__)) +papi_event = threading.Event() +print(vpp_papi.vpe.VL_API_SW_INTERFACE_SET_FLAGS) +def papi_event_handler(result): + if result.vl_msg_id == vpp_papi.vpe.VL_API_SW_INTERFACE_SET_FLAGS: + return + if result.vl_msg_id == vpp_papi.vpe.VL_API_VNET_INTERFACE_COUNTERS: + print('Interface counters', result) + return + if result.vl_msg_id == vpp_papi.vpe.VL_API_VNET_IP6_FIB_COUNTERS: + print('IPv6 FIB counters', result) + papi_event.set() + return + + print('Unknown message id:', result.vl_msg_id) + +import glob, subprocess +class TestPAPI(unittest.TestCase): + @classmethod + def setUpClass(cls): + # + # Start main VPP process + cls.vpp_bin = glob.glob(scriptdir+'/../../../build-root/install-vpp*-native/vpp/bin/vpp')[0] + print("VPP BIN:", cls.vpp_bin) + cls.vpp = subprocess.Popen([cls.vpp_bin, "unix", "nodaemon"], stderr=subprocess.PIPE) + print('Started VPP') + # For some reason unless we let VPP start up the API cannot connect. + time.sleep(0.3) + @classmethod + def tearDownClass(cls): + cls.vpp.terminate() + + def setUp(self): + print("Connecting API") + r = vpp_papi.connect("test_papi") + self.assertEqual(r, 0) + + def tearDown(self): + r = vpp_papi.disconnect() + self.assertEqual(r, 0) + + # + # The tests themselves + # + + # + # Basic request / reply + # + def test_show_version(self): + t = vpp_papi.show_version() + print('T', t); + program = t.program.decode().rstrip('\x00') + self.assertEqual('vpe', program) + + # + # Details / Dump + # + def test_details_dump(self): + t = vpp_papi.sw_interface_dump(0, b'') + print('Dump/details T', t) + + # + # Arrays + # + def test_arrays(self): + t = vpp_papi.vnet_get_summary_stats() + print('Summary stats', t) + print('Packets:', t.total_pkts[0]) + print('Packets:', t.total_pkts[1]) + # + # Variable sized arrays and counters + # + #@unittest.skip("stats") + def test_want_stats(self): + pid = 123 + vpp_papi.register_event_callback(papi_event_handler) + papi_event.clear() + + # Need to configure IPv6 to get som IPv6 FIB stats + t = vpp_papi.create_loopback('') + print(t) + self.assertEqual(t.retval, 0) + + ifindex = t.sw_if_index + addr = str(IPv6Address(u'1::1').packed) + t = vpp_papi.sw_interface_add_del_address(ifindex, 1, 1, 0, 16, addr) + print(t) + self.assertEqual(t.retval, 0) + + # Check if interface is up + # XXX: Add new API to query interface state based on ifindex, instead of dump all. + t = vpp_papi.sw_interface_set_flags(ifindex, 1, 1, 0) + self.assertEqual(t.retval, 0) + + t = vpp_papi.want_stats(True, pid) + + print (t) + + # + # Wait for some stats + # + self.assertEqual(papi_event.wait(15), True) + t = vpp_papi.want_stats(False, pid) + print (t) + + + # + # Plugins? + # + +if __name__ == '__main__': + #logging.basicConfig(level=logging.DEBUG) + unittest.main() +def test_papi(): + print('test') diff --git a/src/vpp-api/python/tests/test_version.py b/src/vpp-api/python/tests/test_version.py new file mode 100755 index 00000000..de39cc24 --- /dev/null +++ b/src/vpp-api/python/tests/test_version.py @@ -0,0 +1,35 @@ +from __future__ import print_function +import unittest, sys, time, threading, struct + +import vpp_papi +from ipaddress import * +import glob, subprocess +class TestPAPI(unittest.TestCase): + def setUp(self): + print("Connecting API") + r = vpp_papi.connect("test_papi") + self.assertEqual(r, 0) + + def tearDown(self): + r = vpp_papi.disconnect() + self.assertEqual(r, 0) + + # + # The tests themselves + # + + # + # Basic request / reply + # + def test_show_version(self): + print(vpp_papi.show_version()) + + # + # Details / Dump + # + def test_details_dump(self): + t = vpp_papi.sw_interface_dump(0, b'') + print('Dump/details T', t) + +if __name__ == '__main__': + unittest.main() diff --git a/src/vpp-api/python/tests/test_vpp_papi2.py b/src/vpp-api/python/tests/test_vpp_papi2.py new file mode 100755 index 00000000..f45f791e --- /dev/null +++ b/src/vpp-api/python/tests/test_vpp_papi2.py @@ -0,0 +1,487 @@ +#!/usr/bin/env python + +from __future__ import print_function +import unittest, sys, threading, struct, logging, os +from vpp_papi import VPP +from ipaddress import * +import glob, json + +papi_event = threading.Event() +import glob + +import fnmatch +import os + +jsonfiles = [] +for root, dirnames, filenames in os.walk('../../../build-root/'): + if root.find('install-') == -1: continue + for filename in fnmatch.filter(filenames, '*.api.json'): + jsonfiles.append(os.path.join(root, filename)) + +class TestPAPI(unittest.TestCase): + show_version_msg = '''["show_version", + ["u16", "_vl_msg_id"], + ["u32", "client_index"], + ["u32", "context"], + {"crc" : "0xf18f9480"} + ]''' + + ip_address_details_msg = '''["ip_address_details", + ["u16", "_vl_msg_id"], + ["u32", "client_index"], + ["u32", "context"], + ["u8", "ip", 16], + ["u8", "prefix_length"], + {"crc" : "0x87d522a1"} + ]''' + + cli_inband_msg = '''["cli_inband", + ["u16", "_vl_msg_id"], + ["u32", "client_index"], + ["u32", "context"], + ["u32", "length"], + ["u8", "cmd", 0, "length"], + {"crc" : "0x22345937"} + ]''' + + def test_adding_new_message_object(self): + p = json.loads(TestPAPI.show_version_msg) + msglist = VPP(testmode=json) + msgdef = msglist.add_message(p[0], p[1:]) + + # Verify that message can be retrieved + self.assertTrue(msglist['show_version']) + self.assertFalse(msglist['foobar']) + + # Test duplicate + self.assertRaises(ValueError, msglist.add_message, p[0], p[1:]) + + # Look at return tuple + self.assertTrue(msglist.ret_tup('show_version')) + + def test_adding_new_message_object_with_array(self): + p = json.loads(TestPAPI.ip_address_details_msg) + msglist = VPP(testmode=True) + msglist.add_message(p[0], p[1:]) + + self.assertTrue(msglist['ip_address_details']) + + def test_message_to_bytes(self): + msglist = VPP(testmode=True) + p = json.loads(TestPAPI.show_version_msg) + msgdef = msglist.add_message(p[0], p[1:]) + + # Give me a byte string for given message and given arguments + + b = msglist.encode(msgdef, {'_vl_msg_id' : 50, 'context' : 123 }) + self.assertEqual(10, len(b)) + rv = msglist.decode(msgdef, b) + self.assertEqual(rv._0, 50) + self.assertEqual(rv.context, 123) + + + p = json.loads(TestPAPI.ip_address_details_msg) + msgdef = msglist.add_message(p[0], p[1:]) + + # Give me a byte string for given message and given arguments + b = msglist.encode(msgdef, {'_vl_msg_id' : 50, 'context' : 123, + 'ip' : b'\xf0\xf1\xf2', + 'prefix_length' : 12}) + self.assertEqual(27, len(b)) + rv = msglist.decode(msgdef, b) + + self.assertEqual(rv.context, 123) + self.assertEqual(rv.ip, b'\xf0\xf1\xf2\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') + self.assertEqual(rv.prefix_length, 12) + + p = json.loads(TestPAPI.cli_inband_msg) + msgdef = msglist.add_message(p[0], p[1:]) + + # Give me a byte string for given message and given arguments + b = msglist.encode(msgdef, { '_vl_msg_id' : 50, 'context' : 123, + 'length' : 20, 'cmd' : 'show version verbose'}) + self.assertEqual(34, len(b)) + rv = msglist.decode(msgdef, b) + self.assertEqual(rv._0, 50) + self.assertEqual(rv.context, 123) + self.assertEqual(rv.cmd.decode('ascii'), 'show version verbose') + + variable_array_16_msg = '''["variable_array_16", + ["u32", "length"], + ["u16", "list", 0, "length"] + ]''' + + p = json.loads(variable_array_16_msg) + msgdef = msglist.add_message(p[0], p[1:]) + + # Give me a byte string for given message and given arguments + b = msglist.encode(msgdef, { 'list' : [1, 2], 'length' :2}) + self.assertEqual(8, len(b)) + rv = msglist.decode(msgdef, b) + self.assertEqual(2, rv.length) + self.assertEqual([1,2], rv.list) + + def test_add_new_types(self): + counter_type = '''["ip4_fib_counter", + ["u32", "address"], + ["u8", "address_length"], + ["u64", "packets"], + ["u64", "bytes"], + {"crc" : "0xb2739495"} + ]''' + + with_type_msg = '''["with_type_msg", + ["u32", "length"], + ["u16", "list", 0, "length"], + ["vl_api_ip4_fib_counter_t", "counter"] + ]''' + + # Add new type + msglist = VPP(testmode=True) + p = json.loads(counter_type) + msglist.add_type(p[0], p[1:]) + p = json.loads(with_type_msg) + msgdef = msglist.add_message(p[0], p[1:]) + b = msglist.encode(msgdef, {'length' : 2, 'list' : [1,2], + 'counter' : { 'address' : 4, 'address_length' : 12, + 'packets': 1235, 'bytes' : 5678}}) + self.assertEqual(29, len(b)) # feil + rv = msglist.decode(msgdef, b) + self.assertEqual(2, rv.length) + self.assertEqual(5678, rv.counter.bytes) + + def test_add_new_compound_type_with_array(self): + counter_type = '''["ip4_fib_counter", + ["u32", "address"], + ["u8", "address_length"], + ["u64", "packets"], + ["u64", "bytes"], + {"crc" : "0xb2739495"} + ]''' + + with_type_msg = '''["with_type_msg", + ["u32", "length"], + ["u16", "list", 0, "length"], + ["vl_api_ip4_fib_counter_t", "counter", 2] + + ]''' + + # Add new type + msglist = VPP(testmode=True) + p = json.loads(counter_type) + msglist.add_type(p[0], p[1:]) + p = json.loads(with_type_msg) + msgdef = msglist.add_message(p[0], p[1:]) + b = msglist.encode(msgdef, {'length' : 2, 'list' : [1,2], + 'counter' : [{ 'address' : 4, 'address_length' : 12, + 'packets': 1235, 'bytes' : 5678}, + { 'address' : 111, 'address_length' : 222, + 'packets': 333, 'bytes' : 444}]}) + self.assertEqual(50, len(b)) + rv = msglist.decode(msgdef, b) + self.assertEqual([1,2], rv.list) + self.assertEqual(1235, rv.counter[0].packets) + + with_type_variable_msg = '''["with_type_variable_msg", + ["u32", "length"], + ["vl_api_ip4_fib_counter_t", "counter", 0, "length"] + + ]''' + + p = json.loads(with_type_variable_msg) + msgdef = msglist.add_message(p[0], p[1:]) + b = msglist.encode(msgdef, {'length' : 2, + 'counter' : [{ 'address' : 4, 'address_length' : 12, + 'packets': 1235, 'bytes' : 5678}, + { 'address' : 111, 'address_length' : 222, + 'packets': 333, 'bytes' : 444}]}) + self.assertEqual(46, len(b)) + rv = msglist.decode(msgdef, b) + self.assertEqual(2, rv.length) + self.assertEqual(1235, rv.counter[0].packets) + self.assertEqual(333, rv.counter[1].packets) + + def test_simple_array(self): + msglist = VPP(testmode=True) + + simple_byte_array = '''["simple_byte_array", + ["u32", "length"], + ["u8", "namecommand", 64] + + ]''' + p = json.loads(simple_byte_array) + msgdef = msglist.add_message(p[0], p[1:]) + b = msglist.encode(msgdef, {'length': 2, 'namecommand': 'foobar'}) + self.assertEqual(68, len(b)) + rv = msglist.decode(msgdef, b) + self.assertEqual(2, rv.length) + + simple_array = '''["simple_array", + ["u32", "length"], + ["u32", "list", 2] + + ]''' + p = json.loads(simple_array) + msgdef = msglist.add_message(p[0], p[1:]) + b = msglist.encode(msgdef, {'length': 2, 'list': [1,2]}) + self.assertEqual(12, len(b)) + rv = msglist.decode(msgdef, b) + self.assertEqual(2, rv.length) + self.assertEqual([1,2], rv.list) + + simple_variable_array = '''["simple_variable_array", + ["u32", "length"], + ["u32", "list", 0, "length"] + + ]''' + p = json.loads(simple_variable_array) + msgdef = msglist.add_message(p[0], p[1:]) + b = msglist.encode(msgdef, {'length':2, 'list': [1,2]}) + self.assertEqual(12, len(b)) + rv = msglist.decode(msgdef, b) + self.assertEqual(2, rv.length) + self.assertEqual([1,2], rv.list) + + simple_variable_byte_array = '''["simple_variable_byte_array", + ["u32", "length"], + ["u8", "list", 0, "length"] + ]''' + p = json.loads(simple_variable_byte_array) + msgdef =msglist.add_message(p[0], p[1:]) + b = msglist.encode(msgdef, {'length': 6, 'list' : 'foobar'}) + self.assertEqual(10, len(b)) + rv = msglist.decode(msgdef, b) + self.assertEqual(6, rv.length) + self.assertEqual('foobar', rv.list) + + def test_old_vla_array(self): + msglist = VPP(testmode = True) + + # VLA + vla_byte_array = '''["vla_byte_array", + ["u32", "foobar"], + ["u32", "list", 2], + ["u32", "propercount"], + ["u8", "propermask", 0, "propercount"], + ["u8", "oldmask", 0], + {"crc" : "0xb2739495"} + ]''' + p = json.loads(vla_byte_array) + msgdef = msglist.add_message(p[0], p[1:]) + b = msglist.encode(msgdef, {'list' : [123, 456], 'oldmask': b'foobar', + 'propercount' : 2, + 'propermask' : [8,9]}) + self.assertEqual(24, len(b)) + rv = msglist.decode(msgdef, b) + self.assertEqual(b'foobar', rv.oldmask) + + def test_old_vla_array_not_last_member(self): + msglist = VPP(testmode = True) + + # VLA + vla_byte_array = '''["vla_byte_array", + ["u8", "oldmask", 0], + ["u32", "foobar"], + {"crc" : "0xb2739495"} + ]''' + p = json.loads(vla_byte_array) + self.assertRaises(ValueError, msglist.add_message, p[0], p[1:]) + + def test_old_vla_array_u32(self): + msglist = VPP(testmode = True) + + # VLA + vla_byte_array = '''["vla_byte_array", + ["u32", "foobar"], + ["u32", "oldmask", 0], + {"crc" : "0xb2739495"} + ]''' + p = json.loads(vla_byte_array) + msgdef = msglist.add_message(p[0], p[1:]) + b = msglist.encode(msgdef, {'foobar' : 123, 'oldmask': [123, 456, 789]}) + self.assertEqual(16, len(b)) + rv = msglist.decode(msgdef, b) + self.assertEqual([123, 456, 789], rv.oldmask) + + def test_old_vla_array_compound(self): + msglist = VPP(testmode = True) + + # VLA + counter_type = '''["ip4_fib_counter", + ["u32", "address"], + ["u8", "address_length"], + ["u64", "packets"], + ["u64", "bytes"], + {"crc" : "0xb2739495"} + ]''' + + vla_byte_array = '''["vla_byte_array", + ["vl_api_ip4_fib_counter_t", "counter", 0], + {"crc" : "0xb2739495"} + ]''' + + p = json.loads(counter_type) + msglist.add_type(p[0], p[1:]) + + p = json.loads(vla_byte_array) + with self.assertRaises(NotImplementedError): + msgdef = msglist.add_message(p[0], p[1:]) + + def test_array_count_not_previous(self): + msglist = VPP(testmode = True) + + # VLA + vla_byte_array = '''["vla_byte_array", + ["u32", "count"], + ["u32", "filler"], + ["u32", "lst", 0, "count"], + {"crc" : "0xb2739495"} + ]''' + + p = json.loads(vla_byte_array) + msgdef = msglist.add_message(p[0], p[1:]) + b = msglist.encode(msgdef, {'count': 3, 'lst': [1,2,3], 'filler' : 1 }) + rv = msglist.decode(msgdef, b) + self.assertEqual(rv.lst, [1,2,3]) + + def test_argument_name(self): + msglist = VPP(testmode=True) + + + simple_name = '''["simple_name", + ["u32", "length"], + ["u8", "name"] + ]''' + p = json.loads(simple_name) + msgdef = msglist.add_message(p[0], p[1:]) + b = msglist.encode(msgdef, {'length': 6, 'name': 1}) + self.assertEqual(5, len(b)) + rv = msglist.decode(msgdef, b) + self.assertEqual(6, rv.length) + self.assertEqual(1, rv.name) + +class TestConnectedPAPI(unittest.TestCase): + def test_request_reply_function(self): + vpp = VPP(jsonfiles) + + vpp.connect('test_vpp_papi2') + + rv = vpp.show_version() + self.assertEqual(0, rv.retval) + self.assertEqual('vpe', rv.program.decode().rstrip('\0x00')) + vpp.disconnect() + + + def test_dump_details_function(self): + vpp = VPP(jsonfiles) + vpp.connect('test_vpp_papi3') + + rv = vpp.sw_interface_dump() + #self.assertEqual(0, rv.retval) + print('RV', rv) + vpp.disconnect() + + def test_vla(self): + vpp = VPP(jsonfiles) + + vpp.connect('test_vpp_papi3') + + cmd = 'show version verbose' + rv = vpp.cli_inband(length=len(cmd), cmd=cmd) + self.assertEqual(0, rv.retval) + print('RV', rv.reply) + + cmd = 'show vlib graph' + rv = vpp.cli_inband(length=len(cmd), cmd=cmd) + self.assertEqual(0, rv.retval) + print('RV', rv.reply) + vpp.disconnect() + + def test_events(self): + vpp = VPP(jsonfiles) + + vpp.connect('test_vpp_papi3') + + vpp.register_event_callback(event_handler) + + rv = vpp.want_interface_events(enable_disable = True) + self.assertEqual(0, rv.retval) + print('RV', rv) + + rv = vpp.create_loopback() + print('RV', rv) + self.assertEqual(0, rv.retval) + + rv = vpp.sw_interface_set_flags(sw_if_index = 1, admin_up_down = 1) + print('RV', rv) + self.assertEqual(0, rv.retval) + rv = vpp.sw_interface_set_flags(sw_if_index = 1, admin_up_down = 0) + print('RV', rv) + self.assertEqual(0, rv.retval) + self.assertEqual(papi_event.wait(10), True) + + vpp.disconnect() + +def event_handler(msgname, result): + print('IN EVENT HANDLER:', msgname, result) + papi_event.set() + +class TestACL(unittest.TestCase): + def test_acl_create(self): + vpp = VPP(jsonfiles) + + vpp.connect('acl-test') + + rv = vpp.acl_plugin_get_version() + print('RV', rv) + self.assertEqual(rv.major, 1) + self.assertEqual(rv.minor, 1) + + rv = vpp.acl_add_replace(acl_index = 0xFFFFFFFF, + r = [{ + "is_permit" : 1, + "is_ipv6" : 0, + "proto" : 6, + "srcport_or_icmptype_first" : 80, + }], + count = 1) + print ('RV', rv) + rv = vpp.acl_add_replace(acl_index = 0xFFFFFFFF, + r = [{ + "is_permit" : 1, + "is_ipv6" : 0, + "proto" : 6, + "srcport_or_icmptype_first" : 81, + }], + count = 1) + self.assertEqual(rv.retval, 0) + print ('RV', rv) + ai = rv.acl_index + rv = vpp.acl_dump() + print ('RV', rv) + + #rv = vpp.acl_del(acl_index = ai) + #self.assertEqual(rv.retval, 0) + + #rv = vpp.acl_dump() + #self.assertEqual([], vpp.acl_dump()) + + vpp.disconnect() + + def test_status(self): + vpp = VPP(jsonfiles) + vpp.status() + + def test_acl_interface_get(self): + vpp = VPP(jsonfiles) + + vpp.connect('test_vpp_papi2') + + rv = vpp.macip_acl_interface_get() + + print('RV', rv) + + vpp.disconnect() + +if __name__ == '__main__': + unittest.main() diff --git a/src/vpp-api/python/vpp_papi.py b/src/vpp-api/python/vpp_papi.py new file mode 100644 index 00000000..7b66c0f4 --- /dev/null +++ b/src/vpp-api/python/vpp_papi.py @@ -0,0 +1,706 @@ +#!/usr/bin/env python +# +# Copyright (c) 2016 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. +# + +from __future__ import print_function +import sys +import os +import logging +import collections +import struct +import json +import threading +import glob +import atexit +from cffi import FFI + +if sys.version[0] == '2': + import Queue as queue +else: + import queue as queue + +ffi = FFI() +ffi.cdef(""" +typedef void (*vac_callback_t)(unsigned char * data, int len); +typedef void (*vac_error_callback_t)(void *, unsigned char *, int); +int vac_connect(char * name, char * chroot_prefix, vac_callback_t cb, + int rx_qlen); +int vac_disconnect(void); +int vac_read(char **data, int *l, unsigned short timeout); +int vac_write(char *data, int len); +void vac_free(void * msg); + +int vac_get_msg_index(unsigned char * name); +int vac_msg_table_size(void); +int vac_msg_table_max_index(void); + +void vac_rx_suspend (void); +void vac_rx_resume (void); +void vac_set_error_handler(vac_error_callback_t); + """) + +# Barfs on failure, no need to check success. +vpp_api = ffi.dlopen('libvppapiclient.so') + + +def vpp_atexit(self): + """Clean up VPP connection on shutdown.""" + if self.connected: + self.logger.debug('Cleaning up VPP on exit') + self.disconnect() + +vpp_object = None + + +def vpp_iterator(d): + if sys.version[0] == '2': + return d.iteritems() + else: + return d.items() + + +@ffi.callback("void(unsigned char *, int)") +def vac_callback_sync(data, len): + vpp_object.msg_handler_sync(ffi.buffer(data, len)) + + +@ffi.callback("void(unsigned char *, int)") +def vac_callback_async(data, len): + vpp_object.msg_handler_async(ffi.buffer(data, len)) + + +@ffi.callback("void(void *, unsigned char *, int)") +def vac_error_handler(arg, msg, msg_len): + vpp_object.logger.warning("VPP API client:: %s", ffi.string(msg, msg_len)) + + +class Empty(object): + pass + + +class FuncWrapper(object): + def __init__(self, func): + self._func = func + self.__name__ = func.__name__ + + def __call__(self, **kwargs): + return self._func(**kwargs) + + +class VPP(): + """VPP interface. + + This class provides the APIs to VPP. The APIs are loaded + from provided .api.json files and makes functions accordingly. + These functions are documented in the VPP .api files, as they + are dynamically created. + + Additionally, VPP can send callback messages; this class + provides a means to register a callback function to receive + these messages in a background thread. + """ + def __init__(self, apifiles=None, testmode=False, async_thread=True, + logger=logging.getLogger('vpp_papi'), loglevel='debug'): + """Create a VPP API object. + + apifiles is a list of files containing API + descriptions that will be loaded - methods will be + dynamically created reflecting these APIs. If not + provided this will load the API files from VPP's + default install location. + """ + global vpp_object + vpp_object = self + self.logger = logger + logging.basicConfig(level=getattr(logging, loglevel.upper())) + + self.messages = {} + self.id_names = [] + self.id_msgdef = [] + self.connected = False + self.header = struct.Struct('>HI') + self.apifiles = [] + self.event_callback = None + self.message_queue = queue.Queue() + self.read_timeout = 0 + self.vpp_api = vpp_api + if async_thread: + self.event_thread = threading.Thread( + target=self.thread_msg_handler) + self.event_thread.daemon = True + self.event_thread.start() + + if not apifiles: + # Pick up API definitions from default directory + apifiles = glob.glob('/usr/share/vpp/api/*.api.json') + + for file in apifiles: + with open(file) as apidef_file: + api = json.load(apidef_file) + for t in api['types']: + self.add_type(t[0], t[1:]) + + for m in api['messages']: + self.add_message(m[0], m[1:]) + self.apifiles = apifiles + + # Basic sanity check + if len(self.messages) == 0 and not testmode: + raise ValueError(1, 'Missing JSON message definitions') + + # Make sure we allow VPP to clean up the message rings. + atexit.register(vpp_atexit, self) + + # Register error handler + vpp_api.vac_set_error_handler(vac_error_handler) + + class ContextId(object): + """Thread-safe provider of unique context IDs.""" + def __init__(self): + self.context = 0 + self.lock = threading.Lock() + + def __call__(self): + """Get a new unique (or, at least, not recently used) context.""" + with self.lock: + self.context += 1 + return self.context + get_context = ContextId() + + def status(self): + """Debug function: report current VPP API status to stdout.""" + print('Connected') if self.connected else print('Not Connected') + print('Read API definitions from', ', '.join(self.apifiles)) + + def __struct(self, t, n=None, e=-1, vl=None): + """Create a packing structure for a message.""" + base_types = {'u8': 'B', + 'u16': 'H', + 'u32': 'I', + 'i32': 'i', + 'u64': 'Q', + 'f64': 'd', } + pack = None + if t in base_types: + pack = base_types[t] + if not vl: + if e > 0 and t == 'u8': + # Fixed byte array + s = struct.Struct('>' + str(e) + 's') + return s.size, s + if e > 0: + # Fixed array of base type + s = struct.Struct('>' + base_types[t]) + return s.size, [e, s] + elif e == 0: + # Old style variable array + s = struct.Struct('>' + base_types[t]) + return s.size, [-1, s] + else: + # Variable length array + if t == 'u8': + s = struct.Struct('>s') + return s.size, [vl, s] + else: + s = struct.Struct('>' + base_types[t]) + return s.size, [vl, s] + + s = struct.Struct('>' + base_types[t]) + return s.size, s + + if t in self.messages: + size = self.messages[t]['sizes'][0] + + # Return a list in case of array + if e > 0 and not vl: + return size, [e, lambda self, encode, buf, offset, args: ( + self.__struct_type(encode, self.messages[t], buf, offset, + args))] + if vl: + return size, [vl, lambda self, encode, buf, offset, args: ( + self.__struct_type(encode, self.messages[t], buf, offset, + args))] + elif e == 0: + # Old style VLA + raise NotImplementedError(1, + 'No support for compound types ' + t) + return size, lambda self, encode, buf, offset, args: ( + self.__struct_type(encode, self.messages[t], buf, offset, args) + ) + + raise ValueError(1, 'Invalid message type: ' + t) + + def __struct_type(self, encode, msgdef, buf, offset, kwargs): + """Get a message packer or unpacker.""" + if encode: + return self.__struct_type_encode(msgdef, buf, offset, kwargs) + else: + return self.__struct_type_decode(msgdef, buf, offset) + + def __struct_type_encode(self, msgdef, buf, offset, kwargs): + off = offset + size = 0 + + for k in kwargs: + if k not in msgdef['args']: + raise ValueError(1,'Non existing argument [' + k + ']' + \ + ' used in call to: ' + \ + self.id_names[kwargs['_vl_msg_id']] + '()' ) + + for k, v in vpp_iterator(msgdef['args']): + off += size + if k in kwargs: + if type(v) is list: + if callable(v[1]): + e = kwargs[v[0]] if v[0] in kwargs else v[0] + if e != len(kwargs[k]): + raise (ValueError(1, 'Input list length mismatch: %s (%s != %s)' % (k, e, len(kwargs[k])))) + size = 0 + for i in range(e): + size += v[1](self, True, buf, off + size, + kwargs[k][i]) + else: + if v[0] in kwargs: + l = kwargs[v[0]] + if l != len(kwargs[k]): + raise ValueError(1, 'Input list length mistmatch: %s (%s != %s)' % (k, l, len(kwargs[k]))) + else: + l = len(kwargs[k]) + if v[1].size == 1: + buf[off:off + l] = bytearray(kwargs[k]) + size = l + else: + size = 0 + for i in kwargs[k]: + v[1].pack_into(buf, off + size, i) + size += v[1].size + else: + if callable(v): + size = v(self, True, buf, off, kwargs[k]) + else: + if type(kwargs[k]) is str and v.size < len(kwargs[k]): + raise ValueError(1, 'Input list length mistmatch: %s (%s < %s)' % (k, v.size, len(kwargs[k]))) + v.pack_into(buf, off, kwargs[k]) + size = v.size + else: + size = v.size if not type(v) is list else 0 + + return off + size - offset + + def __getitem__(self, name): + if name in self.messages: + return self.messages[name] + return None + + def get_size(self, sizes, kwargs): + total_size = sizes[0] + for e in sizes[1]: + if e in kwargs and type(kwargs[e]) is list: + total_size += len(kwargs[e]) * sizes[1][e] + return total_size + + def encode(self, msgdef, kwargs): + # Make suitably large buffer + size = self.get_size(msgdef['sizes'], kwargs) + buf = bytearray(size) + offset = 0 + size = self.__struct_type(True, msgdef, buf, offset, kwargs) + return buf[:offset + size] + + def decode(self, msgdef, buf): + return self.__struct_type(False, msgdef, buf, 0, None)[1] + + def __struct_type_decode(self, msgdef, buf, offset): + res = [] + off = offset + size = 0 + for k, v in vpp_iterator(msgdef['args']): + off += size + if type(v) is list: + lst = [] + if callable(v[1]): # compound type + size = 0 + if v[0] in msgdef['args']: # vla + e = res[v[2]] + else: # fixed array + e = v[0] + res.append(lst) + for i in range(e): + (s, l) = v[1](self, False, buf, off + size, None) + lst.append(l) + size += s + continue + if v[1].size == 1: + if type(v[0]) is int: + size = len(buf) - off + else: + size = res[v[2]] + res.append(buf[off:off + size]) + else: + e = v[0] if type(v[0]) is int else res[v[2]] + if e == -1: + e = (len(buf) - off) / v[1].size + lst = [] + res.append(lst) + size = 0 + for i in range(e): + lst.append(v[1].unpack_from(buf, off + size)[0]) + size += v[1].size + else: + if callable(v): + (s, l) = v(self, False, buf, off, None) + res.append(l) + size += s + else: + res.append(v.unpack_from(buf, off)[0]) + size = v.size + + return off + size - offset, msgdef['return_tuple']._make(res) + + def ret_tup(self, name): + if name in self.messages and 'return_tuple' in self.messages[name]: + return self.messages[name]['return_tuple'] + return None + + def add_message(self, name, msgdef, typeonly=False): + if name in self.messages: + raise ValueError('Duplicate message name: ' + name) + + args = collections.OrderedDict() + argtypes = collections.OrderedDict() + fields = [] + msg = {} + total_size = 0 + sizes = {} + for i, f in enumerate(msgdef): + if type(f) is dict and 'crc' in f: + msg['crc'] = f['crc'] + continue + field_type = f[0] + field_name = f[1] + if len(f) == 3 and f[2] == 0 and i != len(msgdef) - 2: + raise ValueError('Variable Length Array must be last: ' + name) + size, s = self.__struct(*f) + args[field_name] = s + if type(s) == list and type(s[0]) == int and type(s[1]) == struct.Struct: + if s[0] < 0: + sizes[field_name] = size + else: + sizes[field_name] = size + total_size += s[0] * size + else: + sizes[field_name] = size + total_size += size + + argtypes[field_name] = field_type + if len(f) == 4: # Find offset to # elements field + idx = list(args.keys()).index(f[3]) - i + args[field_name].append(idx) + fields.append(field_name) + msg['return_tuple'] = collections.namedtuple(name, fields, + rename=True) + self.messages[name] = msg + self.messages[name]['args'] = args + self.messages[name]['argtypes'] = argtypes + self.messages[name]['typeonly'] = typeonly + self.messages[name]['sizes'] = [total_size, sizes] + return self.messages[name] + + def add_type(self, name, typedef): + return self.add_message('vl_api_' + name + '_t', typedef, + typeonly=True) + + def make_function(self, name, i, msgdef, multipart, async): + if (async): + f = lambda **kwargs: (self._call_vpp_async(i, msgdef, **kwargs)) + else: + f = lambda **kwargs: (self._call_vpp(i, msgdef, multipart, + **kwargs)) + args = self.messages[name]['args'] + argtypes = self.messages[name]['argtypes'] + f.__name__ = str(name) + f.__doc__ = ", ".join(["%s %s" % + (argtypes[k], k) for k in args.keys()]) + return f + + @property + def api(self): + if not hasattr(self, "_api"): + raise Exception("Not connected, api definitions not available") + return self._api + + def _register_functions(self, async=False): + self.id_names = [None] * (self.vpp_dictionary_maxid + 1) + self.id_msgdef = [None] * (self.vpp_dictionary_maxid + 1) + self._api = Empty() + for name, msgdef in vpp_iterator(self.messages): + if self.messages[name]['typeonly']: + continue + crc = self.messages[name]['crc'] + n = name + '_' + crc[2:] + i = vpp_api.vac_get_msg_index(n.encode()) + if i > 0: + self.id_msgdef[i] = msgdef + self.id_names[i] = name + multipart = True if name.find('_dump') > 0 else False + f = self.make_function(name, i, msgdef, multipart, async) + setattr(self._api, name, FuncWrapper(f)) + + # old API stuff starts here - will be removed in 17.07 + if hasattr(self, name): + raise NameError( + 3, "Conflicting name in JSON definition: `%s'" % name) + setattr(self, name, f) + # old API stuff ends here + else: + self.logger.debug( + 'No such message type or failed CRC checksum: %s', n) + + def _write(self, buf): + """Send a binary-packed message to VPP.""" + if not self.connected: + raise IOError(1, 'Not connected') + return vpp_api.vac_write(ffi.from_buffer(buf), len(buf)) + + def _read(self): + if not self.connected: + raise IOError(1, 'Not connected') + mem = ffi.new("char **") + size = ffi.new("int *") + rv = vpp_api.vac_read(mem, size, self.read_timeout) + if rv: + raise IOError(rv, 'vac_read failed') + msg = bytes(ffi.buffer(mem[0], size[0])) + vpp_api.vac_free(mem[0]) + return msg + + def connect_internal(self, name, msg_handler, chroot_prefix, rx_qlen, + async): + pfx = chroot_prefix.encode() if chroot_prefix else ffi.NULL + rv = vpp_api.vac_connect(name.encode(), pfx, msg_handler, rx_qlen) + if rv != 0: + raise IOError(2, 'Connect failed') + self.connected = True + + self.vpp_dictionary_maxid = vpp_api.vac_msg_table_max_index() + self._register_functions(async=async) + + # Initialise control ping + crc = self.messages['control_ping']['crc'] + self.control_ping_index = vpp_api.vac_get_msg_index( + ('control_ping' + '_' + crc[2:]).encode()) + self.control_ping_msgdef = self.messages['control_ping'] + return rv + + def connect(self, name, chroot_prefix=None, async=False, rx_qlen=32): + """Attach to VPP. + + name - the name of the client. + chroot_prefix - if VPP is chroot'ed, the prefix of the jail + async - if true, messages are sent without waiting for a reply + rx_qlen - the length of the VPP message receive queue between + client and server. + """ + msg_handler = vac_callback_sync if not async else vac_callback_async + return self.connect_internal(name, msg_handler, chroot_prefix, rx_qlen, + async) + + def connect_sync(self, name, chroot_prefix=None, rx_qlen=32): + """Attach to VPP in synchronous mode. Application must poll for events. + + name - the name of the client. + chroot_prefix - if VPP is chroot'ed, the prefix of the jail + rx_qlen - the length of the VPP message receive queue between + client and server. + """ + + return self.connect_internal(name, ffi.NULL, chroot_prefix, rx_qlen, + async=False) + + def disconnect(self): + """Detach from VPP.""" + rv = vpp_api.vac_disconnect() + self.connected = False + return rv + + def msg_handler_sync(self, msg): + """Process an incoming message from VPP in sync mode. + + The message may be a reply or it may be an async notification. + """ + r = self.decode_incoming_msg(msg) + if r is None: + return + + # If we have a context, then use the context to find any + # request waiting for a reply + context = 0 + if hasattr(r, 'context') and r.context > 0: + context = r.context + + msgname = type(r).__name__ + + if context == 0: + # No context -> async notification that we feed to the callback + self.message_queue.put_nowait(r) + else: + raise IOError(2, 'RPC reply message received in event handler') + + def decode_incoming_msg(self, msg): + if not msg: + self.logger.warning('vpp_api.read failed') + return + + i, ci = self.header.unpack_from(msg, 0) + if self.id_names[i] == 'rx_thread_exit': + return + + # + # Decode message and returns a tuple. + # + msgdef = self.id_msgdef[i] + if not msgdef: + raise IOError(2, 'Reply message undefined') + + r = self.decode(msgdef, msg) + + return r + + def msg_handler_async(self, msg): + """Process a message from VPP in async mode. + + In async mode, all messages are returned to the callback. + """ + r = self.decode_incoming_msg(msg) + if r is None: + return + + msgname = type(r).__name__ + + if self.event_callback: + self.event_callback(msgname, r) + + def _control_ping(self, context): + """Send a ping command.""" + self._call_vpp_async(self.control_ping_index, + self.control_ping_msgdef, + context=context) + + def _call_vpp(self, i, msgdef, multipart, **kwargs): + """Given a message, send the message and await a reply. + + msgdef - the message packing definition + i - the message type index + multipart - True if the message returns multiple + messages in return. + context - context number - chosen at random if not + supplied. + The remainder of the kwargs are the arguments to the API call. + + The return value is the message or message array containing + the response. It will raise an IOError exception if there was + no response within the timeout window. + """ + + if 'context' not in kwargs: + context = self.get_context() + kwargs['context'] = context + else: + context = kwargs['context'] + kwargs['_vl_msg_id'] = i + b = self.encode(msgdef, kwargs) + + vpp_api.vac_rx_suspend() + self._write(b) + + if multipart: + # Send a ping after the request - we use its response + # to detect that we have seen all results. + self._control_ping(context) + + # Block until we get a reply. + rl = [] + while (True): + msg = self._read() + if not msg: + raise IOError(2, 'VPP API client: read failed') + + r = self.decode_incoming_msg(msg) + msgname = type(r).__name__ + if context not in r or r.context == 0 or context != r.context: + self.message_queue.put_nowait(r) + continue + + if not multipart: + rl = r + break + if msgname == 'control_ping_reply': + break + + rl.append(r) + + vpp_api.vac_rx_resume() + + return rl + + def _call_vpp_async(self, i, msgdef, **kwargs): + """Given a message, send the message and await a reply. + + msgdef - the message packing definition + i - the message type index + context - context number - chosen at random if not + supplied. + The remainder of the kwargs are the arguments to the API call. + """ + if 'context' not in kwargs: + context = self.get_context() + kwargs['context'] = context + else: + context = kwargs['context'] + kwargs['_vl_msg_id'] = i + b = self.encode(msgdef, kwargs) + + self._write(b) + + def register_event_callback(self, callback): + """Register a callback for async messages. + + This will be called for async notifications in sync mode, + and all messages in async mode. In sync mode, replies to + requests will not come here. + + callback is a fn(msg_type_name, msg_type) that will be + called when a message comes in. While this function is + executing, note that (a) you are in a background thread and + may wish to use threading.Lock to protect your datastructures, + and (b) message processing from VPP will stop (so if you take + a long while about it you may provoke reply timeouts or cause + VPP to fill the RX buffer). Passing None will disable the + callback. + """ + self.event_callback = callback + + def thread_msg_handler(self): + """Python thread calling the user registerd message handler. + + This is to emulate the old style event callback scheme. Modern + clients should provide their own thread to poll the event + queue. + """ + while True: + r = self.message_queue.get() + msgname = type(r).__name__ + if self.event_callback: + self.event_callback(msgname, r) 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} |