diff options
Diffstat (limited to 'java')
115 files changed, 10600 insertions, 0 deletions
diff --git a/java/.gitignore b/java/.gitignore new file mode 100644 index 0000000..c69ba33 --- /dev/null +++ b/java/.gitignore @@ -0,0 +1,6 @@ +# Negate "No core files" pattern from the toplevel .gitignore +!**/core +/CMakeFiles +Makefile +cmake_install.cmake +*.jar
\ No newline at end of file diff --git a/java/CMakeLists.txt b/java/CMakeLists.txt new file mode 100644 index 0000000..2da75d6 --- /dev/null +++ b/java/CMakeLists.txt @@ -0,0 +1,331 @@ +# Copyright (c) 2018 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. + +find_path(VNET_INCLUDE_DIR NAMES vnet/api_errno.h) +find_library(VPPINFRA_DIR NAMES vppinfra REQUIRED) +find_library(VLIBMEMORYCLIENT_DIR NAMES vlibmemoryclient REQUIRED) +find_library(SVM_DIR NAMES svm REQUIRED) + +include_directories(${VNET_INCLUDE_DIR} + ${VNET_INCLUDE_DIR}/vpp_plugins + ${VNET_INCLUDE_DIR}/vpp_api + ${Java_INCLUDE_DIRS} + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_BINARY_DIR/../vpp/plugins}) + +add_compile_options(-Wall) +############# Common package ################## +add_library(jvpp_common SHARED jvpp-common/jvpp_common.c) +set_target_properties(jvpp_common PROPERTIES SOVERSION ${JAPI_LIB_VERSION}) +target_link_libraries(jvpp_common ${VPPINFRA_DIR}) +install(TARGETS jvpp_common DESTINATION lib COMPONENT libjvpp_common) +install(FILES jvpp-common/jvpp_common.h DESTINATION include/japi/) + +set(JVPP_LIBS jvpp_common ${VPPINFRA_DIR} ${VLIBMEMORYCLIENT_DIR} ${SVM_DIR} + Threads::Threads m rt) + +############# Registry package ################## +set(PACKAGE_DIR_JVPP_REGISTRY io/fd/vpp/jvpp) +unset(files) +FILE(GLOB files RELATIVE + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/jvpp-registry/${PACKAGE_DIR_JVPP_REGISTRY}/*.java + ${CMAKE_CURRENT_SOURCE_DIR}/jvpp-registry/${PACKAGE_DIR_JVPP_REGISTRY}/*/*.java +) + +add_custom_target (jvpp-registry-classes) +add_custom_command (TARGET jvpp-registry-classes + PRE_BUILD + COMMAND mkdir -p jvpp-registry/target + COMMAND mkdir -p ${CMAKE_CURRENT_BINARY_DIR}/jvpp-registry + COMMAND ${Java_JAVAC_EXECUTABLE} + ARGS -d ${CMAKE_CURRENT_SOURCE_DIR}/jvpp-registry/target -h jvpp-registry ${files} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} +) + +add_library(jvpp_registry SHARED jvpp-registry/jvpp_registry.c) +target_link_libraries(jvpp_registry ${JVPP_LIBS}) +include_directories(jvpp-registry) +add_dependencies(jvpp_registry jvpp_common jvpp-registry-classes) + +add_custom_target (jvpp-registry) +add_dependencies(jvpp-registry jvpp_registry) +add_custom_command(TARGET jvpp-registry + PRE_BUILD + COMMAND cp ${CMAKE_BINARY_DIR}/build-root/lib/libjvpp_registry.so jvpp-registry/target + COMMAND ${Java_JAR_EXECUTABLE} ARGS cf + ${CMAKE_CURRENT_BINARY_DIR}/jvpp-registry-${JAPI_LIB_VERSION}.jar + -C jvpp-registry/target . + COMMAND rm ARGS -rf jvpp-registry/target + jvpp-registry/io_fd_vpp_jvpp_*.h + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + COMMENT "JAR_GEN registry" +) +install( + FILES ${CMAKE_CURRENT_BINARY_DIR}/jvpp-registry-${JAPI_LIB_VERSION}.jar + DESTINATION share/java +) + +############## Functions ######################### +function(japigen name) + if(NOT VPP_JAVA_APIGEN) + set(VPP_JAVA_APIGEN ${CMAKE_CURRENT_SOURCE_DIR}/jvpp/gen/jvpp_gen.py) + endif() + add_custom_target(japigen-${name} DEPENDS jvpp-registry) + add_custom_command(TARGET japigen-${name} + POST_BUILD + COMMAND mkdir -p jvpp-${name}/target + COMMAND mkdir -p ${CMAKE_CURRENT_BINARY_DIR}/jvpp-${name} + COMMAND ${VPP_JAVA_APIGEN} + ARGS --plugin_name ${name} --root_dir jvpp-${name} -i ${ARGN} + COMMAND find jvpp-${name} -name \*.java > jvpp-${name}/jvpp-${name}.files + COMMAND ${Java_JAVAC_EXECUTABLE} + ARGS -cp ${CMAKE_CURRENT_BINARY_DIR}/jvpp-registry-${JAPI_LIB_VERSION}.jar -d + ${CMAKE_CURRENT_SOURCE_DIR}/jvpp-${name}/target -h jvpp-${name} + @jvpp-${name}/jvpp-${name}.files + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + COMMENT "JAVA_API_GEN ${name}" + ) +endfunction() + +function(jargen name) + add_custom_command(TARGET jvpp_${name} + POST_BUILD + COMMAND cp ${CMAKE_BINARY_DIR}/build-root/lib/libjvpp_${name}.so jvpp-${name}/target + COMMAND ${Java_JAR_EXECUTABLE} ARGS cf + ${CMAKE_CURRENT_BINARY_DIR}/jvpp-${name}-${JAPI_LIB_VERSION}.jar + -C jvpp-${name}/target . + COMMAND rm ARGS -rf jvpp-${name}/target jvpp-${name}/jvpp-${name}.files + jvpp-${name}/jvpp_${name}_gen.h jvpp-${name}/io_fd_vpp_jvpp_*.h + jvpp-registry/io_fd_vpp_jvpp_*.h + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + COMMENT "JAR_GEN ${name}" + ) + install( + FILES ${CMAKE_CURRENT_BINARY_DIR}/jvpp-${name}-${JAPI_LIB_VERSION}.jar + DESTINATION share/java + ) +endfunction() + +function(java_api_binding name src_file) + japigen (${name} ${ARGN}) + add_library(jvpp_${name} SHARED jvpp-${name}/jvpp_${src_file}.c) + target_link_libraries(jvpp_${name} ${JVPP_LIBS}) + include_directories(jvpp-${name}) + add_dependencies(jvpp_${name} jvpp_common jvpp_registry japigen-${name}) + jargen (${name}) +endfunction() + +############ Core Package ####################### +unset (files) +unset (corefiles) + +if(${RELEASE_ID} MATCHES Ubuntu) + execute_process(COMMAND dpkg-query -L vpp + COMMAND grep -v memclnt + COMMAND grep api.json OUTPUT_VARIABLE corefiles) + STRING(REGEX REPLACE "\n" ";" corefiles "${corefiles}") +elseif(${RELEASE_ID} MATCHES CentOS) + # Exclude vpp plugin api files. VPP package should not contain VPP-plugins api files and vice-versa. Temporary fixing + # by excluding them manually. + execute_process(COMMAND rpm -ql vpp + COMMAND grep -v memclnt + COMMAND grep -v abf + COMMAND grep -v acl + COMMAND grep -v avf + COMMAND grep -v cdp + COMMAND grep -v dpdk + COMMAND grep -v flowprobe + COMMAND grep -v gbp + COMMAND grep -v gtpu + COMMAND grep -v igmp + COMMAND grep -v ioam_cache + COMMAND grep -v ioam_export + COMMAND grep -v ioam_vxlan_gpe + COMMAND grep -v l2e + COMMAND grep -v lacp + COMMAND grep -v lb + COMMAND grep -v mactime + COMMAND grep -v map + COMMAND grep -v memif + COMMAND grep -v nat + COMMAND grep -v nsh + COMMAND grep -v nsim + COMMAND grep -v pot + COMMAND grep -v pppoe + COMMAND grep -v stn + COMMAND grep -v svs + COMMAND grep -v trace + COMMAND grep -v udp_ping + COMMAND grep -v vmxnet3 + COMMAND grep -v vxlan_gpe_ioam_export + COMMAND grep api.json OUTPUT_VARIABLE corefiles) + STRING(REGEX REPLACE "\n" ";" corefiles "${corefiles}") +endif() + +FILE(GLOB_RECURSE files RELATIVE + ${CMAKE_CURRENT_SOURCE_DIR} + ${corefiles} +) + +# message("COREFILES: ${corefiles}") +java_api_binding (core core ${files}) + +############ Plugin Packages ####################### +unset (files) +unset (pluginfiles) +unset (ACL_JSON_FILE) +unset (NAT_JSON_FILE) +unset (NSH_JSON_FILE) +unset (GTPU_JSON_FILE) +unset (PPPOE_JSON_FILE) +unset (IOAM_TRACE_JSON_FILE) +unset (IOAM_POT_JSON_FILE) +unset (IOAM_EXPORT_JSON_FILE) + +execute_process(COMMAND dpkg-query -L vpp-plugins + COMMAND grep api.json OUTPUT_VARIABLE pluginfiles) +STRING(REGEX REPLACE "\n" ";" pluginfiles "${pluginfiles}") +if(${RELEASE_ID} MATCHES Ubuntu) + execute_process(COMMAND dpkg-query -L vpp-plugins + COMMAND grep api.json OUTPUT_VARIABLE pluginfiles) + STRING(REGEX REPLACE "\n" ";" pluginfiles "${pluginfiles}") +elseif(${RELEASE_ID} MATCHES CentOS) + execute_process(COMMAND rpm -ql vpp-plugins + COMMAND grep api.json OUTPUT_VARIABLE pluginfiles) + STRING(REGEX REPLACE "\n" ";" pluginfiles "${pluginfiles}") +endif() + +# message("PLUGINFILES: ${pluginfiles}") +FILE(GLOB_RECURSE files RELATIVE + ${CMAKE_CURRENT_SOURCE_DIR} + ${pluginfiles} + ) + +foreach (FILE ${pluginfiles}) + if ("${FILE}" MATCHES "/acl.api.json") + set(ACL_JSON_FILE ${FILE}) + endif () + if ("${FILE}" MATCHES "/nat.api.json") + set(NAT_JSON_FILE ${FILE}) + endif () + if ("${FILE}" MATCHES "/nsh.api.json") + set(NSH_JSON_FILE ${FILE}) + endif () + if ("${FILE}" MATCHES "/gtpu.api.json") + set(GTPU_JSON_FILE ${FILE}) + endif () + if ("${FILE}" MATCHES "/pppoe.api.json") + set(PPPOE_JSON_FILE ${FILE}) + endif () + if ("${FILE}" MATCHES "/trace.api.json") + set(IOAM_TRACE_JSON_FILE ${FILE}) + endif () + if ("${FILE}" MATCHES "/pot.api.json") + set(IOAM_POT_JSON_FILE ${FILE}) + endif () + if ("${FILE}" MATCHES "/ioam_export.api.json") + set(IOAM_EXPORT_JSON_FILE ${FILE}) + endif () +endforeach() + +if(ACL_JSON_FILE) + java_api_binding (acl acl ${ACL_JSON_FILE}) +endif() + +if(NAT_JSON_FILE) + java_api_binding (nat nat ${NAT_JSON_FILE}) +endif() + +if(NSH_JSON_FILE) + java_api_binding (nsh nsh ${NSH_JSON_FILE}) +endif() + +if(GTPU_JSON_FILE) + java_api_binding (gtpu gtpu ${GTPU_JSON_FILE}) +endif() + +if(PPPOE_JSON_FILE) + java_api_binding (pppoe pppoe ${PPPOE_JSON_FILE}) +endif() + +if(IOAM_TRACE_JSON_FILE) + java_api_binding (ioamtrace ioam_trace ${IOAM_TRACE_JSON_FILE}) +endif() + +if(IOAM_POT_JSON_FILE) + java_api_binding (ioampot ioam_pot ${IOAM_POT_JSON_FILE}) +endif() + +if(IOAM_EXPORT_JSON_FILE) + java_api_binding (ioamexport ioam_export ${IOAM_EXPORT_JSON_FILE}) +endif() + +# Package Generator ####################################################### +unset(vpp_version) +set(COLUMNS 200) +execute_process( + COMMAND dpkg -l vpp + COMMAND grep vpp + COMMAND awk "{print $3}" OUTPUT_VARIABLE vpp_version) +string(REGEX REPLACE "\n$" "" vpp_version "${vpp_version}") + +set(CPACK_PACKAGE_DESCRIPTION "VPP-Java-API-bindings + This package contains VPP java api bindings.") +set(CPACK_PACKAGE_NAME "vpp-api-java") +set(CPACK_PACKAGE_VERSION "${JVPP_VERSION}") +set(CPACK_PACKAGE_SECTION net) +set(CPACK_PACKAGE_VENDOR "Cisco") +set(CPACK_PACKAGE_CONTACT "hc2vpp@lists.fd.io") +set(CPACK_OUTPUT_FILE_PREFIX build-root/packages) +set(CPACK_PACKAGE_FILE_NAME "vpp-api-java_${JVPP_VERSION}") +if (${RELEASE_ID} MATCHES "Ubuntu") + set(CPACK_GENERATOR "DEB") + set(CPACK_DEBIAN_PACKAGE_NAME "${CPACK_PACKAGE_NAME}") + set(CPACK_DEBIAN_PACKAGE_VERSION "${CPACK_PACKAGE_VERSION}") + set(CPACK_DEBIAN_PACKAGE_DESCRIPTION "${CPACK_PACKAGE_DESCRIPTION}") + set(CPACK_DEBIAN_PACKAGE_SECTION "${CPACK_PACKAGE_SECTION}") + set(CPACK_DEBIAN_PACKAGE_FILE_NAME ${CPACK_PACKAGE_FILE_NAME}) + set(CPACK_DEBIAN_PACKAGE_PRIORITY extra) + set(CPACK_DEBIAN_PACKAGE_DEPENDS "vpp (>= ${vpp_version}), vpp-plugins (>= ${vpp_version})") + + # Print Debian package summary + message("\nDebian package:") + message(" Name: ${CPACK_DEBIAN_PACKAGE_NAME}") + message(" Version: ${CPACK_DEBIAN_PACKAGE_VERSION}") + message(" Description: ${CPACK_DEBIAN_PACKAGE_DESCRIPTION}") + message(" File name: ${CPACK_DEBIAN_PACKAGE_FILE_NAME}") + message(" Depends on: ${CPACK_DEBIAN_PACKAGE_DEPENDS}\n") + include (CPack) +elseif(${RELEASE_ID} MATCHES "CentOS") + set(CPACK_GENERATOR "RPM") + # Excluding /usr/share/java top level directory due to conflict with javapackages-tools (openjdk dependency) + set(CPACK_RPM_EXCLUDE_FROM_AUTO_FILELIST_ADDITION "/usr/share/java") + set(CPACK_RPM_PACKAGE_NAME "${CPACK_PACKAGE_NAME}") + set(CPACK_RPM_PACKAGE_VERSION "${CPACK_PACKAGE_VERSION}") + set(CPACK_RPM_PACKAGE_DESCRIPTION "${CPACK_PACKAGE_DESCRIPTION}") + set(CPACK_RPM_PACKAGE_SECTION "${CPACK_PACKAGE_SECTION}") + set(CPACK_RPM_PACKAGE_FILE_NAME ${CPACK_PACKAGE_FILE_NAME}) + set(CPACK_RPM_PACKAGE_PRIORITY extra) + set(CPACK_RPM_PACKAGE_DEPENDS "vpp (>= ${vpp_version}), vpp-plugins (>= ${vpp_version})") + + # Print CentOS package summary + message("\nRPM package:") + message(" Name: ${CPACK_RPM_PACKAGE_NAME}") + message(" Version: ${CPACK_RPM_PACKAGE_VERSION}") + message(" Description: ${CPACK_RPM_PACKAGE_DESCRIPTION}") + message(" File name: ${CPACK_RPM_PACKAGE_FILE_NAME}") + message(" Depends on: ${CPACK_RPM_PACKAGE_DEPENDS}\n") + include (CPack) +endif () + diff --git a/java/Readme.txt b/java/Readme.txt new file mode 100644 index 0000000..689b9b3 --- /dev/null +++ b/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/java/jvpp-acl/io/fd/vpp/jvpp/acl/examples/AclExpectedDumpData.java b/java/jvpp-acl/io/fd/vpp/jvpp/acl/examples/AclExpectedDumpData.java new file mode 100644 index 0000000..4806052 --- /dev/null +++ b/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/java/jvpp-acl/io/fd/vpp/jvpp/acl/examples/AclTestData.java b/java/jvpp-acl/io/fd/vpp/jvpp/acl/examples/AclTestData.java new file mode 100644 index 0000000..199b1b6 --- /dev/null +++ b/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/java/jvpp-acl/io/fd/vpp/jvpp/acl/examples/AclTestRequests.java b/java/jvpp-acl/io/fd/vpp/jvpp/acl/examples/AclTestRequests.java new file mode 100644 index 0000000..149ea46 --- /dev/null +++ b/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/java/jvpp-acl/io/fd/vpp/jvpp/acl/examples/FutureApiExample.java b/java/jvpp-acl/io/fd/vpp/jvpp/acl/examples/FutureApiExample.java new file mode 100644 index 0000000..862df8d --- /dev/null +++ b/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/java/jvpp-acl/io/fd/vpp/jvpp/acl/examples/Readme.txt b/java/jvpp-acl/io/fd/vpp/jvpp/acl/examples/Readme.txt new file mode 100644 index 0000000..d17fbfc --- /dev/null +++ b/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/java/jvpp-acl/io/fd/vpp/jvpp/acl/test/CallbackApiTest.java b/java/jvpp-acl/io/fd/vpp/jvpp/acl/test/CallbackApiTest.java new file mode 100644 index 0000000..a7bbb7f --- /dev/null +++ b/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/java/jvpp-acl/io/fd/vpp/jvpp/acl/test/FutureApiTest.java b/java/jvpp-acl/io/fd/vpp/jvpp/acl/test/FutureApiTest.java new file mode 100644 index 0000000..ff1c73c --- /dev/null +++ b/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/java/jvpp-acl/io/fd/vpp/jvpp/acl/test/Readme.txt b/java/jvpp-acl/io/fd/vpp/jvpp/acl/test/Readme.txt new file mode 100644 index 0000000..1b46585 --- /dev/null +++ b/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/java/jvpp-acl/jvpp_acl.c b/java/jvpp-acl/jvpp_acl.c new file mode 100644 index 0000000..22e44f1 --- /dev/null +++ b/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, svm_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) \ + if (get_message_id(env, #N) == 0) return; + 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/java/jvpp-acl/jvpp_acl.h b/java/jvpp-acl/jvpp_acl.h new file mode 100644 index 0000000..d1ec78f --- /dev/null +++ b/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 */ + svm_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/java/jvpp-common/jvpp_common.c b/java/jvpp-common/jvpp_common.c new file mode 100644 index 0000000..2425607 --- /dev/null +++ b/java/jvpp-common/jvpp_common.c @@ -0,0 +1,99 @@ +/* + * 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) + 70); + strcpy(msgBuf, "API mismatch detected: "); + strcat(msgBuf, key); + strcat(msgBuf, " is missing in global name_crc hash table."); + DEBUG_LOG("%s", msgBuf); + DEBUG_LOG("Possible reasons:"); + DEBUG_LOG("1) incompatible VPP version used"); + DEBUG_LOG("2) message present in JSON file but not in global name_crc table"); + (*env)->ThrowNew(env, exClass, msgBuf); + clib_mem_free(msgBuf); + return 0; + } + return (u32) p[0]; +} diff --git a/java/jvpp-common/jvpp_common.h b/java/jvpp-common/jvpp_common.h new file mode 100644 index 0000000..e12141b --- /dev/null +++ b/java/jvpp-common/jvpp_common.h @@ -0,0 +1,75 @@ +/* + * 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 <vlibapi/api_types.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 */ + svm_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 clib_atomic_add_fetch(&jm->context_id, 1); +} + +static_always_inline void vppjni_lock(jvpp_main_t * jm, u32 tag) { + while (clib_atomic_test_and_set(&jm->lock)) + ; + 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/java/jvpp-core/io/fd/vpp/jvpp/core/examples/CallbackApiExample.java b/java/jvpp-core/io/fd/vpp/jvpp/core/examples/CallbackApiExample.java new file mode 100644 index 0000000..b6558ff --- /dev/null +++ b/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.GetNodeIndexReplyCallback; +import io.fd.vpp.jvpp.core.callback.ShowVersionReplyCallback; +import io.fd.vpp.jvpp.core.callback.SwInterfaceDetailsCallback; +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 GetNodeIndexReplyCallback, ShowVersionReplyCallback, SwInterfaceDetailsCallback { + + @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, + msg.program, + msg.version, + msg.buildDate, + msg.buildDirectory); + } + + @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", + msg.interfaceName, 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/java/jvpp-core/io/fd/vpp/jvpp/core/examples/CallbackApiReadPerfTest.java b/java/jvpp-core/io/fd/vpp/jvpp/core/examples/CallbackApiReadPerfTest.java new file mode 100644 index 0000000..6ff440d --- /dev/null +++ b/java/jvpp-core/io/fd/vpp/jvpp/core/examples/CallbackApiReadPerfTest.java @@ -0,0 +1,146 @@ +/* + * 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.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.ShowVersionReplyCallback; +import io.fd.vpp.jvpp.core.dto.*; + +import java.util.logging.Logger; + +public class CallbackApiReadPerfTest { + + private static final Logger LOG = Logger.getLogger(CallbackApiReadPerfTest.class.getName()); + private static final ShowVersion REQUEST = new ShowVersion(); + + /** + * + * @param args - for running for one sec requires no parameter + * - for running for set amount of requests requires one parameters, desired REQUEST amount + * @throws Exception if arguments aren't String representations of numbers + */ + public static void main(String[] args) throws Exception { + if (args.length != 0) { + testInvokeCounter(true, Integer.parseUnsignedInt(args[0])); + } else { + testInvokeCounter(false, 0); + } + } + + /** + * + * @param setCount true = run with set amount of requests, false = run for 1 sec + * @param count number of request with which test should be run + * @throws Exception + */ + private static void testInvokeCounter(boolean setCount, int count) throws Exception { + LOG.info("Testing callback API Invocation Counter"); + try (final JVppRegistry registry = new JVppRegistryImpl("CallbackApiReadPerfTest"); + final JVpp jvpp = new JVppCoreImpl()) { + TestCallback callback = new TestCallback(count); + registry.register(jvpp, callback); + if (!setCount) { + for(int i = 0; i < 5; i++) { + callback.reset(); + LOG.info("Starting invocation for 1sec"); + long time = System.nanoTime(); + do { + jvpp.send(REQUEST); + } while (System.nanoTime() - time < 1000000000 || callback.stop()); + int replyCount = callback.getReplyCounter(); + LOG.info(String.format("Invocation count within 1 second: %d", replyCount)); + } + } else { + for (int i = 0; i < 5; i++) { + LOG.info("Starting invocations"); + callback.reset(); + long time = System.nanoTime(); + for (int x = 0; x < count; x++) { + jvpp.send(REQUEST); + } + long timeAfter = callback.getTime(); + LOG.info(String.format("Invocations took %d ns (%f invocations/s)", timeAfter - time, + count * (1000000000.0/(timeAfter - time)))); + } + } + + + Thread.sleep(1000); + LOG.info("Disconnecting..."); + } + Thread.sleep(1000); + } + + static class TestCallback implements ShowVersionReplyCallback { + + private int replyCounter = 0; + private int count; + private long time = 0; + private boolean stop = false; + + public TestCallback(int count) throws Exception { + this.count = count; + } + + public int getReplyCounter() { + return replyCounter; + } + + public void reset() { + replyCounter = 0; + time = 0; + stop = false; + } + + public boolean stop() { + this.stop = true; + return false; + } + + /* actual method called from VPP + not thread safe but since there's only one VPP thread listening for requests and calling + this method it's OK + */ + @Override + public void onShowVersionReply(final ShowVersionReply msg) { + if (stop) { + return; + } + replyCounter++; + if (replyCounter == count ) { + time = System.nanoTime(); + } + } + + @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 long getTime() throws Exception { + while(time == 0) { + Thread.sleep(1000); + } + return time; + } + } +} diff --git a/java/jvpp-core/io/fd/vpp/jvpp/core/examples/CallbackApiWritePerfTest.java b/java/jvpp-core/io/fd/vpp/jvpp/core/examples/CallbackApiWritePerfTest.java new file mode 100644 index 0000000..1940ddc --- /dev/null +++ b/java/jvpp-core/io/fd/vpp/jvpp/core/examples/CallbackApiWritePerfTest.java @@ -0,0 +1,162 @@ +/* + * 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.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.dto.*; +import io.fd.vpp.jvpp.core.callback.ClassifyAddDelTableReplyCallback; + +import java.util.logging.Logger; + +public class CallbackApiWritePerfTest { + + private static final Logger LOG = Logger.getLogger(CallbackApiWritePerfTest.class.getName()); + private static final ClassifyAddDelTable REQUEST = createAddDelTable(); + + private static ClassifyAddDelTable createAddDelTable () { + ClassifyAddDelTable addDelTable = new ClassifyAddDelTable(); + addDelTable.isAdd = 1; + addDelTable.tableIndex = -1; + addDelTable.nbuckets = 2; + addDelTable.memorySize = 2 << 20; + addDelTable.nextTableIndex = ~0; // default + addDelTable.missNextIndex = ~0; // default + addDelTable.skipNVectors = 0; + addDelTable.matchNVectors = 1; + addDelTable.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 addDelTable; + }; + + /** + * + * @param args - for running for one sec requires no parameter + * - for running for set amount of requests requires one parameters, desired REQUEST amount + * @throws Exception if arguments aren't String representations of numbers + */ + public static void main(String[] args) throws Exception { + if (args.length != 0) { + testInvokeCounter(true, Integer.parseUnsignedInt(args[0])); + } else { + testInvokeCounter(false, 0); + } + } + + /** + * + * @param setCount true = run with set amount of requests, false = run for 1 sec + * @param count number of requests with which test should be run + * @throws Exception + */ + private static void testInvokeCounter(boolean setCount, int count) throws Exception { + LOG.info("Testing callback API Invocation Counter"); + try (final JVppRegistry registry = new JVppRegistryImpl("CallbackApiWritePerfTest"); + final JVpp jvpp = new JVppCoreImpl()) { + TestCallback callback = new TestCallback(count); + registry.register(jvpp, callback); + if (!setCount) { + for(int i = 0; i < 5; i++) { + callback.reset(); + LOG.info("Starting invocation for 1sec"); + long time = System.nanoTime(); + do { + jvpp.send(REQUEST); + } while (System.nanoTime() - time < 1000000000 || callback.stop()); + int replyCount = callback.getReplyCounter(); + LOG.info(String.format("Invocation count within 1 second: %d", replyCount)); + } + } else { + for(int i = 0; i < 5; i++) { + LOG.info("Starting invocations"); + callback.reset(); + long time = System.nanoTime(); + for (int x = 1; x <= count; x++) { + jvpp.send(REQUEST); + } + long timeAfter = callback.getTime(); + LOG.info(String.format("Invocations took %d ns (%f invocations/s)", timeAfter - time, + count * (1000000000.0 / (timeAfter - time)))); + } + } + + + Thread.sleep(1000); + LOG.info("Disconnecting..."); + } + Thread.sleep(1000); + } + + static class TestCallback implements ClassifyAddDelTableReplyCallback { + + private int replyCounter = 0; + private int count; + private long time = 0; + private boolean stop = false; + + public TestCallback(int count) throws Exception { + this.count = count; + } + + public int getReplyCounter() { + return replyCounter; + } + + public void reset() { + replyCounter = 0; + time = 0; + stop = false; + } + + public boolean stop() { + this.stop = true; + return false; + } + + /* actual method called from VPP + not thread safe but since there's only one VPP thread listening for requests and calling + this method it's OK + */ + @Override + public void onClassifyAddDelTableReply(final ClassifyAddDelTableReply msg) { + if (stop) { + return; + } + replyCounter++; + if (replyCounter == count ) { + time = System.nanoTime(); + } + } + + @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 long getTime() throws Exception { + while(time == 0) { + Thread.sleep(1000); + } + return time; + } + } +} diff --git a/java/jvpp-core/io/fd/vpp/jvpp/core/examples/CallbackCliApiExample.java b/java/jvpp-core/io/fd/vpp/jvpp/core/examples/CallbackCliApiExample.java new file mode 100644 index 0000000..b692291 --- /dev/null +++ b/java/jvpp-core/io/fd/vpp/jvpp/core/examples/CallbackCliApiExample.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2018 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.CliInbandReplyCallback; +import io.fd.vpp.jvpp.core.dto.CliInband; +import io.fd.vpp.jvpp.core.dto.CliInbandReply; + +import java.nio.charset.StandardCharsets; + +public class CallbackCliApiExample { + + public static void main(String[] args) throws Exception { + testCallbackApi(); + } + + private static void testCallbackApi() throws Exception { + System.out.println("Testing Java callback API for Cli with JVppRegistry"); + try (final JVppRegistry registry = new JVppRegistryImpl("CallbackCliApiExample"); + final JVpp jvpp = new JVppCoreImpl()) { + registry.register(jvpp, new TestCallback()); + + System.out.println("Sending CliInband request..."); + CliInband req = new CliInband(); + req.cmd = "create loopback interface"; + final int result = jvpp.send(req); + System.out.printf("CliInband send result = %d%n", result); + + Thread.sleep(1000); + System.out.println("Disconnecting..."); + } + Thread.sleep(1000); + } + + static class TestCallback implements CliInbandReplyCallback { + + @Override + public void onCliInbandReply(final CliInbandReply msg) { + System.out.printf("%s", msg.toString()); + System.out.printf("Received CliInbandReply: context=%d, reply=%s", msg.context, msg.reply); + } + + @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/java/jvpp-core/io/fd/vpp/jvpp/core/examples/CallbackJVppFacadeExample.java b/java/jvpp-core/io/fd/vpp/jvpp/core/examples/CallbackJVppFacadeExample.java new file mode 100644 index 0000000..a8b9186 --- /dev/null +++ b/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.GetNodeIndexReplyCallback; +import io.fd.vpp.jvpp.core.callback.ShowVersionReplyCallback; +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 ShowVersionReplyCallback showVersionCallback1 = new ShowVersionReplyCallback() { + @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, + msg.program, + msg.version, + msg.buildDate, + msg.buildDirectory); + } + + @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 ShowVersionReplyCallback showVersionCallback2 = new ShowVersionReplyCallback() { + @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, + msg.program, + msg.version, + msg.buildDate, + msg.buildDirectory); + } + + @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 GetNodeIndexReplyCallback getNodeIndexCallback = new GetNodeIndexReplyCallback() { + @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/java/jvpp-core/io/fd/vpp/jvpp/core/examples/CallbackJVppFacadeNotificationExample.java b/java/jvpp-core/io/fd/vpp/jvpp/core/examples/CallbackJVppFacadeNotificationExample.java new file mode 100644 index 0000000..832464a --- /dev/null +++ b/java/jvpp-core/io/fd/vpp/jvpp/core/examples/CallbackJVppFacadeNotificationExample.java @@ -0,0 +1,97 @@ +/* + * 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.WantInterfaceEventsReplyCallback; +import io.fd.vpp.jvpp.core.callback.SwInterfaceEventCallback; +import io.fd.vpp.jvpp.core.callfacade.CallbackJVppCoreFacade; +import io.fd.vpp.jvpp.core.dto.WantInterfaceEventsReply; +import io.fd.vpp.jvpp.core.dto.SwInterfaceEvent; + +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.getEventRegistry().registerSwInterfaceEventCallback( + new SwInterfaceEventCallback() { + public void onSwInterfaceEvent(SwInterfaceEvent reply) { + System.out.printf("Received interface notification: ifc: %s%n", reply); + } + + public void onError (VppCallbackException ex) { + System.out.printf("Received onError exception: call=%s, context=%d, retval=%d%n", + ex.getMethodName(), ex.getCtxId(), ex.getErrorCode()); + } + }); + + jvppCallbackFacade.wantInterfaceEvents(NotificationUtils.getEnableInterfaceNotificationsReq(), + new WantInterfaceEventsReplyCallback() { + @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 WantInterfaceEventsReplyCallback() { + @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/java/jvpp-core/io/fd/vpp/jvpp/core/examples/CallbackNotificationApiExample.java b/java/jvpp-core/io/fd/vpp/jvpp/core/examples/CallbackNotificationApiExample.java new file mode 100644 index 0000000..9ed418e --- /dev/null +++ b/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.SwInterfaceEventCallback; +import io.fd.vpp.jvpp.core.callback.WantInterfaceEventsReplyCallback; +import io.fd.vpp.jvpp.core.dto.SwInterfaceEvent; +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 SwInterfaceEventCallback, + WantInterfaceEventsReplyCallback { + + @Override + public void onSwInterfaceEvent( + final SwInterfaceEvent 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/java/jvpp-core/io/fd/vpp/jvpp/core/examples/CreateSubInterfaceExample.java b/java/jvpp-core/io/fd/vpp/jvpp/core/examples/CreateSubInterfaceExample.java new file mode 100644 index 0000000..3db6d30 --- /dev/null +++ b/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/java/jvpp-core/io/fd/vpp/jvpp/core/examples/FutureApiExample.java b/java/jvpp-core/io/fd/vpp/jvpp/core/examples/FutureApiExample.java new file mode 100644 index 0000000..030e689 --- /dev/null +++ b/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, reply.program, + reply.version, + reply.buildDate, + reply.buildDirectory)); + } + + 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", + details.interfaceName, + 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/java/jvpp-core/io/fd/vpp/jvpp/core/examples/FutureApiNotificationExample.java b/java/jvpp-core/io/fd/vpp/jvpp/core/examples/FutureApiNotificationExample.java new file mode 100644 index 0000000..3c84fd7 --- /dev/null +++ b/java/jvpp-core/io/fd/vpp/jvpp/core/examples/FutureApiNotificationExample.java @@ -0,0 +1,67 @@ +/* + * 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; +import io.fd.vpp.jvpp.core.callback.SwInterfaceEventCallback; +import io.fd.vpp.jvpp.core.dto.SwInterfaceEvent; +import io.fd.vpp.jvpp.VppCallbackException; + +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.getEventRegistry() + .registerSwInterfaceEventCallback(new SwInterfaceEventCallback() { + public void onSwInterfaceEvent(SwInterfaceEvent reply) { + System.out.printf("Received interface notification: ifc: %s%n", reply); + } + + public void onError (VppCallbackException ex) { + System.out.printf("Received onError exception: call=%s, context=%d, retval=%d%n", + ex.getMethodName(), ex.getCtxId(), ex.getErrorCode()); + } + })) { + 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/java/jvpp-core/io/fd/vpp/jvpp/core/examples/FutureApiReadPerfTest.java b/java/jvpp-core/io/fd/vpp/jvpp/core/examples/FutureApiReadPerfTest.java new file mode 100644 index 0000000..f335b28 --- /dev/null +++ b/java/jvpp-core/io/fd/vpp/jvpp/core/examples/FutureApiReadPerfTest.java @@ -0,0 +1,137 @@ +/* + * 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.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.callback.ShowVersionReplyCallback; +import io.fd.vpp.jvpp.core.dto.*; +import io.fd.vpp.jvpp.core.future.FutureJVppCoreFacade; + +import java.util.concurrent.CompletableFuture; +import java.util.logging.Logger; + +public class FutureApiReadPerfTest { + + private static final Logger LOG = Logger.getLogger(FutureApiReadPerfTest.class.getName()); + private static final ShowVersion REQUEST = new ShowVersion(); + private static volatile int currentCount = 0; + private static int desiredCount = 0; + private static long timeAfter = 0; + private static volatile boolean stop = false; + /** + * Run after reply message is received + * in case of running for 1 sec check if time passed (stop variable) and if it does skip processing + * in case of running for set amount of REQUEST, record time in which was last reply received + * not thread save but since reading part process only one message at a time it's ok + */ + private static Runnable replyFc = () -> { + if (stop) { + return; + } + currentCount++; + if(currentCount == desiredCount) { + timeAfter = System.nanoTime(); + } + }; + + /** + * Used to reset counters and flags between runs + */ + private static void reset() { + currentCount = 0; + timeAfter = 0; + stop = false; + } + + public static boolean stop() { + stop = true; + return false; + } + + /** + * + * @return time of last reply received + * @throws Exception during thread sleep + */ + private static long getTime() throws Exception { + while(timeAfter == 0) { + LOG.info(String.format("Received %d replies", currentCount)); + Thread.sleep(1000); + } + return timeAfter; + } + + /** + * + * @param args - for running for one sec requires no parameter + * - for running for set amount of requests requires one parameters, desired REQUEST amount + * @throws Exception if arguments aren't String representations of numbers + */ + public static void main(String[] args) throws Exception { + if (args.length == 1) { + desiredCount = Integer.parseUnsignedInt(args[0]); + testInvokeCounter(true); + } else { + testInvokeCounter(false); + } + } + + /** + * + * @param setCount true = run with set amount of requests, false = run for 1 sec + * @throws Exception + */ + private static void testInvokeCounter(boolean setCount) throws Exception { + LOG.info("Testing callback API Invocation Counter"); + try (final JVppRegistry registry = new JVppRegistryImpl("FutureApiReadPerfTest"); + final FutureJVppCoreFacade jvpp = new FutureJVppCoreFacade(registry, new JVppCoreImpl())) { + if (!setCount) { + for(int i = 0; i < 5; i++) { + reset(); + LOG.info("Starting invocation for 1sec"); + long time = System.nanoTime(); + do { + CompletableFuture<ShowVersionReply> replyFuture = jvpp.showVersion(REQUEST).toCompletableFuture(); + replyFuture.thenRun(replyFc); + } while (System.nanoTime() - time < 1000000000 || stop()); + LOG.info(String.format("Invocation count within 1 second: %d", currentCount)); + } + } else { + for (int i = 0; i < 5; i++) { + LOG.info("Starting invocations"); + reset(); + long time = System.nanoTime(); + for (int x = 0; x < desiredCount; x++) { + CompletableFuture<ShowVersionReply> replyFuture = jvpp.showVersion(REQUEST).toCompletableFuture(); + replyFuture.thenRun(replyFc); + } + LOG.info("Invocations send"); + long timeAfter = getTime(); + LOG.info(String.format("Invocations took %d ns (%f invocations/s)", timeAfter - time, + desiredCount * (1000000000.0/(timeAfter - time)))); + } + } + + + Thread.sleep(1000); + LOG.info("Disconnecting..."); + } + Thread.sleep(1000); + } +} diff --git a/java/jvpp-core/io/fd/vpp/jvpp/core/examples/L2AclExample.java b/java/jvpp-core/io/fd/vpp/jvpp/core/examples/L2AclExample.java new file mode 100644 index 0000000..0be85d4 --- /dev/null +++ b/java/jvpp-core/io/fd/vpp/jvpp/core/examples/L2AclExample.java @@ -0,0 +1,206 @@ +/* + * 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; + +/** + * <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 final char[] hexArray = "0123456789ABCDEF".toCharArray(); + + + 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 String bytesToHex(byte[] bytes) { + char[] hexChars = new char[bytes.length * 2]; + for ( int j = 0; j < bytes.length; j++ ) { + int v = bytes[j] & 0xFF; + hexChars[j * 2] = hexArray[v >>> 4]; + hexChars[j * 2 + 1] = hexArray[v & 0x0F]; + } + return new java.lang.String(hexChars); + } + + 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: " + bytesToHex(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: " + bytesToHex(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/java/jvpp-core/io/fd/vpp/jvpp/core/examples/LispAdjacencyExample.java b/java/jvpp-core/io/fd/vpp/jvpp/core/examples/LispAdjacencyExample.java new file mode 100644 index 0000000..f637669 --- /dev/null +++ b/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/java/jvpp-core/io/fd/vpp/jvpp/core/examples/NotificationUtils.java b/java/jvpp-core/io/fd/vpp/jvpp/core/examples/NotificationUtils.java new file mode 100644 index 0000000..e963d63 --- /dev/null +++ b/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.SwInterfaceEvent; +import io.fd.vpp.jvpp.core.dto.WantInterfaceEvents; + +final class NotificationUtils { + + private NotificationUtils() {} + + static PrintStream printNotification(final SwInterfaceEvent 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/java/jvpp-core/io/fd/vpp/jvpp/core/examples/Readme.txt b/java/jvpp-core/io/fd/vpp/jvpp/core/examples/Readme.txt new file mode 100644 index 0000000..9fe3c7a --- /dev/null +++ b/java/jvpp-core/io/fd/vpp/jvpp/core/examples/Readme.txt @@ -0,0 +1,24 @@ +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-18.01.jar:build-vpp-native/vpp/vpp-api/java/jvpp-core-18.01.jar io.fd.vpp.jvpp.core.examples.[test name] + - debug version: sudo java -cp build-vpp_debug-native/vpp/vpp-api/java/jvpp-registry-18.01.jar:build-vpp_debug-native/vpp/vpp-api/java/jvpp-core-18.01.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) + +CallbackApiReadPerfTest, FutureApiReadPerfTest, CallbackApiWritePerfTest - test provide two ways to count invocations: +1) maximum number of invocations and received replyies within 1 sec +sudo java -cp build-vpp-native/vpp/vpp-api/java/jvpp-registry-18.01.jar:build-vpp-native/vpp/vpp-api/java/jvpp-core-18.01.jar io.fd.vpp.jvpp.core.examples.[test name] +2) measure time in ns from first request to receiving last reply over set amount of requests +sudo java -cp build-vpp-native/vpp/vpp-api/java/jvpp-registry-18.01.jar:build-vpp-native/vpp/vpp-api/java/jvpp-core-18.01.jar io.fd.vpp.jvpp.core.examples.[test name] [number of request to send] + diff --git a/java/jvpp-core/io/fd/vpp/jvpp/core/test/CallbackApiTest.java b/java/jvpp-core/io/fd/vpp/jvpp/core/test/CallbackApiTest.java new file mode 100644 index 0000000..493116c --- /dev/null +++ b/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/java/jvpp-core/io/fd/vpp/jvpp/core/test/FutureApiTest.java b/java/jvpp-core/io/fd/vpp/jvpp/core/test/FutureApiTest.java new file mode 100644 index 0000000..d3acecc --- /dev/null +++ b/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/java/jvpp-core/io/fd/vpp/jvpp/core/test/Readme.txt b/java/jvpp-core/io/fd/vpp/jvpp/core/test/Readme.txt new file mode 100644 index 0000000..b74cf60 --- /dev/null +++ b/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/java/jvpp-core/jvpp_core.c b/java/jvpp-core/jvpp_core.c new file mode 100644 index 0000000..2e62426 --- /dev/null +++ b/java/jvpp-core/jvpp_core.c @@ -0,0 +1,136 @@ +/* + * 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 <jvpp-common/jvpp_common.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_core.h> + + +// TODO: generate jvpp_plugin_name.c files (or at least reuse plugin's main structure) +typedef struct { + /* Pointer to shared memory queue */ + svm_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, svm_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 (exit on mismatch) + #define _(N) \ + if (get_message_id(env, #N) == 0) return; + 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); +} + + +static void _host_to_net_string(JNIEnv * env, jstring javaString, vl_api_string_t * vl_api_string) +{ + const char *nativeString; + // prevent null, which causes jni to crash + if (NULL != javaString) { + nativeString = (*env)->GetStringUTFChars(env, javaString, 0); + } else{ + nativeString = ""; + } + + vl_api_to_api_string(jstr_length(env, javaString) + 1, nativeString, vl_api_string); + + (*env)->ReleaseStringUTFChars(env, javaString, nativeString); +} + + +static jstring _net_to_host_string(JNIEnv * env, const vl_api_string_t * _net) +{ + return (*env)->NewStringUTF(env, (char *)_net->buf); +} + + +static size_t jstr_length(JNIEnv *env, jstring string) +{ + return ((int) (*env)->GetStringUTFLength(env, string)); +} diff --git a/java/jvpp-core/jvpp_core.h b/java/jvpp-core/jvpp_core.h new file mode 100644 index 0000000..032dd33 --- /dev/null +++ b/java/jvpp-core/jvpp_core.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2018 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_JVPP_CORE_H +#define VPP_JVPP_CORE_H + +#include <vlibapi/api_types.h> + +#endif //VPP_JVPP_CORE_H + +// /** +// * Host to network byte order conversion for string type. Converts String in Java to VPP string type. +// * typedef struct +// * { +// * u32 length; +// * u8 buf[0]; +// * } __attribute__ ((packed)) vl_api_string_t; +// */ +static void _host_to_net_string(JNIEnv * env, jstring javaString, vl_api_string_t * vl_api_string); + + +// +// /** +// * Network to host byte order conversion for string type. Converts VPP string type to String in Java +// * typedef struct +// * { +// * u32 length; +// * u8 buf[0]; +// * } __attribute__ ((packed)) vl_api_string_t; +// */ +static jstring _net_to_host_string(JNIEnv * env, const vl_api_string_t * _net); + + +// +// /** +// * Returns the length of jstring as size_t +// */ +static size_t jstr_length(JNIEnv *env, jstring string); diff --git a/java/jvpp-gtpu/jvpp_gtpu.c b/java/jvpp-gtpu/jvpp_gtpu.c new file mode 100644 index 0000000..8e523ca --- /dev/null +++ b/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 = (svm_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) \ + if (get_message_id(env, #N) == 0) return; + 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/java/jvpp-gtpu/jvpp_gtpu.h b/java/jvpp-gtpu/jvpp_gtpu.h new file mode 100644 index 0000000..fa7c8b7 --- /dev/null +++ b/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 */ + svm_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/java/jvpp-ioamexport/io/fd/vpp/jvpp/ioamexport/examples/IoamExportApiExample.java b/java/jvpp-ioamexport/io/fd/vpp/jvpp/ioamexport/examples/IoamExportApiExample.java new file mode 100644 index 0000000..2f5b7db --- /dev/null +++ b/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/java/jvpp-ioamexport/io/fd/vpp/jvpp/ioamexport/examples/Readme.txt b/java/jvpp-ioamexport/io/fd/vpp/jvpp/ioamexport/examples/Readme.txt new file mode 100644 index 0000000..f2dfe91 --- /dev/null +++ b/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/java/jvpp-ioamexport/io/fd/vpp/jvpp/ioamexport/test/CallbackApiTest.java b/java/jvpp-ioamexport/io/fd/vpp/jvpp/ioamexport/test/CallbackApiTest.java new file mode 100644 index 0000000..ba49d77 --- /dev/null +++ b/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/java/jvpp-ioamexport/io/fd/vpp/jvpp/ioamexport/test/FutureApiTest.java b/java/jvpp-ioamexport/io/fd/vpp/jvpp/ioamexport/test/FutureApiTest.java new file mode 100644 index 0000000..048d244 --- /dev/null +++ b/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/java/jvpp-ioamexport/io/fd/vpp/jvpp/ioamexport/test/Readme.txt b/java/jvpp-ioamexport/io/fd/vpp/jvpp/ioamexport/test/Readme.txt new file mode 100644 index 0000000..820071a --- /dev/null +++ b/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/java/jvpp-ioamexport/jvpp_ioam_export.c b/java/jvpp-ioamexport/jvpp_ioam_export.c new file mode 100644 index 0000000..74ae438 --- /dev/null +++ b/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, svm_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) \ + if (get_message_id(env, #N) == 0) return; + 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/java/jvpp-ioamexport/jvpp_ioam_export.h b/java/jvpp-ioamexport/jvpp_ioam_export.h new file mode 100644 index 0000000..596a054 --- /dev/null +++ b/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 */ + svm_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/java/jvpp-ioampot/io/fd/vpp/jvpp/ioampot/examples/IoamPotApiExample.java b/java/jvpp-ioampot/io/fd/vpp/jvpp/ioampot/examples/IoamPotApiExample.java new file mode 100644 index 0000000..b9ed7d0 --- /dev/null +++ b/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.PotProfileAddReplyCallback; +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 PotProfileAddReplyCallback { + + @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/java/jvpp-ioampot/io/fd/vpp/jvpp/ioampot/examples/Readme.txt b/java/jvpp-ioampot/io/fd/vpp/jvpp/ioampot/examples/Readme.txt new file mode 100644 index 0000000..e91550b --- /dev/null +++ b/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/java/jvpp-ioampot/io/fd/vpp/jvpp/ioampot/test/CallbackApiTest.java b/java/jvpp-ioampot/io/fd/vpp/jvpp/ioampot/test/CallbackApiTest.java new file mode 100644 index 0000000..20b85d8 --- /dev/null +++ b/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/java/jvpp-ioampot/io/fd/vpp/jvpp/ioampot/test/FutureApiTest.java b/java/jvpp-ioampot/io/fd/vpp/jvpp/ioampot/test/FutureApiTest.java new file mode 100644 index 0000000..6401c67 --- /dev/null +++ b/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/java/jvpp-ioampot/io/fd/vpp/jvpp/ioampot/test/Readme.txt b/java/jvpp-ioampot/io/fd/vpp/jvpp/ioampot/test/Readme.txt new file mode 100644 index 0000000..f3cae26 --- /dev/null +++ b/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/java/jvpp-ioampot/jvpp_ioam_pot.c b/java/jvpp-ioampot/jvpp_ioam_pot.c new file mode 100644 index 0000000..ce1da69 --- /dev/null +++ b/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, svm_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) \ + if (get_message_id(env, #N) == 0) return; + 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/java/jvpp-ioampot/jvpp_ioam_pot.h b/java/jvpp-ioampot/jvpp_ioam_pot.h new file mode 100644 index 0000000..51db3da --- /dev/null +++ b/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 */ + svm_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/java/jvpp-ioamtrace/io/fd/vpp/jvpp/ioamtrace/examples/IoamTraceApiExample.java b/java/jvpp-ioamtrace/io/fd/vpp/jvpp/ioamtrace/examples/IoamTraceApiExample.java new file mode 100644 index 0000000..d63d137 --- /dev/null +++ b/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.TraceProfileAddReplyCallback; +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 TraceProfileAddReplyCallback { + + @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/java/jvpp-ioamtrace/io/fd/vpp/jvpp/ioamtrace/examples/Readme.txt b/java/jvpp-ioamtrace/io/fd/vpp/jvpp/ioamtrace/examples/Readme.txt new file mode 100644 index 0000000..e8c3907 --- /dev/null +++ b/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/java/jvpp-ioamtrace/io/fd/vpp/jvpp/ioamtrace/test/CallbackApiTest.java b/java/jvpp-ioamtrace/io/fd/vpp/jvpp/ioamtrace/test/CallbackApiTest.java new file mode 100644 index 0000000..4a71db5 --- /dev/null +++ b/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/java/jvpp-ioamtrace/io/fd/vpp/jvpp/ioamtrace/test/FutureApiTest.java b/java/jvpp-ioamtrace/io/fd/vpp/jvpp/ioamtrace/test/FutureApiTest.java new file mode 100644 index 0000000..4e13ed1 --- /dev/null +++ b/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/java/jvpp-ioamtrace/io/fd/vpp/jvpp/ioamtrace/test/Readme.txt b/java/jvpp-ioamtrace/io/fd/vpp/jvpp/ioamtrace/test/Readme.txt new file mode 100644 index 0000000..9a1ba82 --- /dev/null +++ b/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/java/jvpp-ioamtrace/jvpp_ioam_trace.c b/java/jvpp-ioamtrace/jvpp_ioam_trace.c new file mode 100644 index 0000000..3532d91 --- /dev/null +++ b/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, svm_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) \ + if (get_message_id(env, #N) == 0) return; + 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/java/jvpp-ioamtrace/jvpp_ioam_trace.h b/java/jvpp-ioamtrace/jvpp_ioam_trace.h new file mode 100644 index 0000000..752b599 --- /dev/null +++ b/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 */ + svm_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/java/jvpp-nat/io/fd/vpp/jvpp/nat/examples/CallbackApiExample.java b/java/jvpp-nat/io/fd/vpp/jvpp/nat/examples/CallbackApiExample.java new file mode 100644 index 0000000..e0d93ff --- /dev/null +++ b/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.Nat44InterfaceAddDelFeatureReplyCallback; +import io.fd.vpp.jvpp.nat.dto.Nat44InterfaceAddDelFeature; +import io.fd.vpp.jvpp.nat.dto.Nat44InterfaceAddDelFeatureReply; + +public class CallbackApiExample { + + static class TestCallback implements Nat44InterfaceAddDelFeatureReplyCallback { + + @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/java/jvpp-nat/io/fd/vpp/jvpp/nat/examples/Readme.txt b/java/jvpp-nat/io/fd/vpp/jvpp/nat/examples/Readme.txt new file mode 100644 index 0000000..ac75e04 --- /dev/null +++ b/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/java/jvpp-nat/io/fd/vpp/jvpp/nat/test/CallbackApiTest.java b/java/jvpp-nat/io/fd/vpp/jvpp/nat/test/CallbackApiTest.java new file mode 100644 index 0000000..a6f8214 --- /dev/null +++ b/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/java/jvpp-nat/io/fd/vpp/jvpp/nat/test/FutureApiTest.java b/java/jvpp-nat/io/fd/vpp/jvpp/nat/test/FutureApiTest.java new file mode 100644 index 0000000..26d6b98 --- /dev/null +++ b/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.Nat44AddressDetailsReplyDump; +import io.fd.vpp.jvpp.nat.dto.Nat44AddressDump; +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 nat 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 Nat44AddressDump request..."); + final Nat44AddressDump request = new Nat44AddressDump(); + + final Future<Nat44AddressDetailsReplyDump> replyFuture = jvpp.nat44AddressDump(request).toCompletableFuture(); + final Nat44AddressDetailsReplyDump reply = replyFuture.get(); + + if (reply == null || reply.nat44AddressDetails == null) { + throw new IllegalStateException("Received null response for empty dump: " + reply); + } else { + LOG.info( + String.format( + "Received nat address dump reply with list of nat address: %s", + reply.nat44AddressDetails)); + } + } +} diff --git a/java/jvpp-nat/io/fd/vpp/jvpp/nat/test/Readme.txt b/java/jvpp-nat/io/fd/vpp/jvpp/nat/test/Readme.txt new file mode 100644 index 0000000..6f75808 --- /dev/null +++ b/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/java/jvpp-nat/jvpp_nat.c b/java/jvpp-nat/jvpp_nat.c new file mode 100644 index 0000000..bdae1e6 --- /dev/null +++ b/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, svm_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) \ + if (get_message_id(env, #N) == 0) return; + 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/java/jvpp-nat/jvpp_nat.h b/java/jvpp-nat/jvpp_nat.h new file mode 100644 index 0000000..9bff974 --- /dev/null +++ b/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 */ + svm_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/java/jvpp-nsh/jvpp_nsh.c b/java/jvpp-nsh/jvpp_nsh.c new file mode 100644 index 0000000..1736ce7 --- /dev/null +++ b/java/jvpp-nsh/jvpp_nsh.c @@ -0,0 +1,106 @@ +/* + * 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> + +#define vl_typedefs /* define message structures */ +#include <nsh/nsh.api.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-nsh/io_fd_vpp_jvpp_nsh_JVppNshImpl.h" +#include "jvpp_nsh.h" +#include "jvpp-nsh/jvpp_nsh_gen.h" + +/* + * Class: io_fd_vpp_jvpp_nsh_JVppnshImpl + * Method: init0 + * Signature: (JI)V + */ +JNIEXPORT void JNICALL Java_io_fd_vpp_jvpp_nsh_JVppNshImpl_init0 + (JNIEnv *env, jclass clazz, jobject callback, jlong queue_address, jint my_client_index) { + nsh_main_t * plugin_main = &nsh_main; + clib_warning ("Java_io_fd_vpp_jvpp_nsh_JVppNshImpl_init0"); + + plugin_main->my_client_index = my_client_index; + plugin_main->vl_input_queue = (svm_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) \ + if (get_message_id(env, #N) == 0) return; + 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_nsh_JVppNshImpl_close0 +(JNIEnv *env, jclass clazz) { + nsh_main_t * plugin_main = &nsh_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/java/jvpp-nsh/jvpp_nsh.h b/java/jvpp-nsh/jvpp_nsh.h new file mode 100644 index 0000000..5f62af6 --- /dev/null +++ b/java/jvpp-nsh/jvpp_nsh.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_nsh_h__ +#define __included_jvpp_nsh_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-nsh */ +typedef struct { + /* Pointer to shared memory queue */ + svm_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; + +} nsh_main_t; + +nsh_main_t nsh_main __attribute__((aligned (64))); + + +#endif /* __included_jvpp_nsh_h__ */ diff --git a/java/jvpp-pppoe/jvpp_pppoe.c b/java/jvpp-pppoe/jvpp_pppoe.c new file mode 100644 index 0000000..02bd20e --- /dev/null +++ b/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 = (svm_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) \ + if (get_message_id(env, #N) == 0) return; + 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/java/jvpp-pppoe/jvpp_pppoe.h b/java/jvpp-pppoe/jvpp_pppoe.h new file mode 100644 index 0000000..7606a7e --- /dev/null +++ b/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 */ + svm_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/java/jvpp-registry/io/fd/vpp/jvpp/AbstractCallbackApiTest.java b/java/jvpp-registry/io/fd/vpp/jvpp/AbstractCallbackApiTest.java new file mode 100644 index 0000000..d221d1e --- /dev/null +++ b/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/java/jvpp-registry/io/fd/vpp/jvpp/Assertions.java b/java/jvpp-registry/io/fd/vpp/jvpp/Assertions.java new file mode 100644 index 0000000..f8b591f --- /dev/null +++ b/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/java/jvpp-registry/io/fd/vpp/jvpp/JVpp.java b/java/jvpp-registry/io/fd/vpp/jvpp/JVpp.java new file mode 100644 index 0000000..55f25a7 --- /dev/null +++ b/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/java/jvpp-registry/io/fd/vpp/jvpp/JVppRegistry.java b/java/jvpp-registry/io/fd/vpp/jvpp/JVppRegistry.java new file mode 100644 index 0000000..6535db0 --- /dev/null +++ b/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/java/jvpp-registry/io/fd/vpp/jvpp/JVppRegistryImpl.java b/java/jvpp-registry/io/fd/vpp/jvpp/JVppRegistryImpl.java new file mode 100644 index 0000000..baef14c --- /dev/null +++ b/java/jvpp-registry/io/fd/vpp/jvpp/JVppRegistryImpl.java @@ -0,0 +1,187 @@ +/* + * 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); + + // controlPing0 is sending function and can go to waiting in case of e. g. full queue + // because of that it cant be in same synchronization block as used by reply handler function + int context = controlPing0(); + if (context < 0) { + throw new VppInvocationException("controlPing", context); + } + + synchronized (pingCalls) { + // if callback is in map it's because reply was already received + EarlyControlPingReply earlyReplyCallback = (EarlyControlPingReply) pingCalls.remove(context); + if(earlyReplyCallback == null) { + pingCalls.put(context, callback); + } else { + callback.onControlPingReply(earlyReplyCallback.getReply()); + } + } + + return context; + } + + @Override + public void onControlPingReply(final ControlPingReply reply) { + final ControlPingCallback callback; + synchronized (pingCalls) { + callback = pingCalls.remove(reply.context); + if (callback == null) { + // reply received early, because we don't know callback to call + // we wrap the reply and let the sender to call it + pingCalls.put(reply.context, new EarlyControlPingReply(reply)); + 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(); + } + + private static class EarlyControlPingReply implements ControlPingCallback { + + private final ControlPingReply reply; + + public EarlyControlPingReply(final ControlPingReply reply) { + this.reply = reply; + } + + public ControlPingReply getReply() { + return reply; + } + + @Override + public void onError(VppCallbackException ex) { + throw new IllegalStateException("Calling onError in EarlyControlPingReply"); + } + + @Override + public void onControlPingReply(ControlPingReply reply) { + throw new IllegalStateException("Calling onControlPingReply in EarlyControlPingReply"); + } + } +} diff --git a/java/jvpp-registry/io/fd/vpp/jvpp/NativeLibraryLoader.java b/java/jvpp-registry/io/fd/vpp/jvpp/NativeLibraryLoader.java new file mode 100644 index 0000000..ce6d1bf --- /dev/null +++ b/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/java/jvpp-registry/io/fd/vpp/jvpp/VppBaseCallException.java b/java/jvpp-registry/io/fd/vpp/jvpp/VppBaseCallException.java new file mode 100644 index 0000000..7fc1682 --- /dev/null +++ b/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/java/jvpp-registry/io/fd/vpp/jvpp/VppCallbackException.java b/java/jvpp-registry/io/fd/vpp/jvpp/VppCallbackException.java new file mode 100644 index 0000000..adcc5d2 --- /dev/null +++ b/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/java/jvpp-registry/io/fd/vpp/jvpp/VppConnection.java b/java/jvpp-registry/io/fd/vpp/jvpp/VppConnection.java new file mode 100644 index 0000000..e6fd3bd --- /dev/null +++ b/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/java/jvpp-registry/io/fd/vpp/jvpp/VppInvocationException.java b/java/jvpp-registry/io/fd/vpp/jvpp/VppInvocationException.java new file mode 100644 index 0000000..a7ccb19 --- /dev/null +++ b/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/java/jvpp-registry/io/fd/vpp/jvpp/VppJNIConnection.java b/java/jvpp-registry/io/fd/vpp/jvpp/VppJNIConnection.java new file mode 100644 index 0000000..6a414f3 --- /dev/null +++ b/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/java/jvpp-registry/io/fd/vpp/jvpp/callback/ControlPingCallback.java b/java/jvpp-registry/io/fd/vpp/jvpp/callback/ControlPingCallback.java new file mode 100644 index 0000000..efddfdb --- /dev/null +++ b/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/java/jvpp-registry/io/fd/vpp/jvpp/callback/JVppCallback.java b/java/jvpp-registry/io/fd/vpp/jvpp/callback/JVppCallback.java new file mode 100644 index 0000000..ae02063 --- /dev/null +++ b/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/java/jvpp-registry/io/fd/vpp/jvpp/callback/JVppNotificationCallback.java b/java/jvpp-registry/io/fd/vpp/jvpp/callback/JVppNotificationCallback.java new file mode 100644 index 0000000..8ab0cb2 --- /dev/null +++ b/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/java/jvpp-registry/io/fd/vpp/jvpp/coverity/SuppressFBWarnings.java b/java/jvpp-registry/io/fd/vpp/jvpp/coverity/SuppressFBWarnings.java new file mode 100644 index 0000000..1e780bb --- /dev/null +++ b/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/java/jvpp-registry/io/fd/vpp/jvpp/dto/ControlPing.java b/java/jvpp-registry/io/fd/vpp/jvpp/dto/ControlPing.java new file mode 100644 index 0000000..984e167 --- /dev/null +++ b/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/java/jvpp-registry/io/fd/vpp/jvpp/dto/ControlPingReply.java b/java/jvpp-registry/io/fd/vpp/jvpp/dto/ControlPingReply.java new file mode 100644 index 0000000..61e4d0e --- /dev/null +++ b/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/java/jvpp-registry/io/fd/vpp/jvpp/dto/JVppDump.java b/java/jvpp-registry/io/fd/vpp/jvpp/dto/JVppDump.java new file mode 100644 index 0000000..60b9898 --- /dev/null +++ b/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/java/jvpp-registry/io/fd/vpp/jvpp/dto/JVppReply.java b/java/jvpp-registry/io/fd/vpp/jvpp/dto/JVppReply.java new file mode 100644 index 0000000..73f512d --- /dev/null +++ b/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/java/jvpp-registry/io/fd/vpp/jvpp/dto/JVppReplyDump.java b/java/jvpp-registry/io/fd/vpp/jvpp/dto/JVppReplyDump.java new file mode 100644 index 0000000..1511139 --- /dev/null +++ b/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/java/jvpp-registry/io/fd/vpp/jvpp/dto/JVppRequest.java b/java/jvpp-registry/io/fd/vpp/jvpp/dto/JVppRequest.java new file mode 100644 index 0000000..9b301da --- /dev/null +++ b/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/java/jvpp-registry/io/fd/vpp/jvpp/future/AbstractFutureJVppInvoker.java b/java/jvpp-registry/io/fd/vpp/jvpp/future/AbstractFutureJVppInvoker.java new file mode 100644 index 0000000..ac85f53 --- /dev/null +++ b/java/jvpp-registry/io/fd/vpp/jvpp/future/AbstractFutureJVppInvoker.java @@ -0,0 +1,180 @@ +/* + * 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) { + try { + // jvpp.send() can go to waiting state if sending queue is full, putting it into same + // synchronization block as used by receiving part (synchronized(requests)) can lead + // to deadlock between these two sides or at least slowing sending process by slow + // reader + 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"); + } + + synchronized(requests) { + CompletableFuture<? extends JVppReply<?>> replyFuture = requests.get(contextId); + if (replyFuture == null) { + // reply not yet received, put new future into map + replyCompletableFuture = new CompletableFuture<>(); + requests.put(contextId, replyCompletableFuture); + } else { + // reply already received (should be completed by reader), + // remove future from map and return it to caller + replyCompletableFuture = (CompletableFuture<REPLY>) replyFuture; + requests.remove(contextId); + } + } + + // 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) { + try { + // jvpp.send() and registry.controlPing() can go to waiting state if sending queue is full, + // putting it into same synchronization block as used by receiving part (synchronized(requests)) + // can lead to deadlock between these two sides or at least slowing sending process by slow reader + final CompletableDumpFuture<DUMP> replyDumpFuture; + 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"); + } + + synchronized(requests) { + CompletableFuture<? extends JVppReply<?>> replyFuture = requests.get(contextId); + if (replyFuture == null) { + // reply not received yet, put new future to map + replyDumpFuture = new CompletableDumpFuture<>(contextId, emptyReplyDump); + requests.put(contextId, replyDumpFuture); + } else { + // reply already received, save existing future + replyDumpFuture = (CompletableDumpFuture<DUMP>) replyFuture; + } + } + + final int pingId = registry.controlPing(jvpp.getClass()); + + synchronized(requests) { + if (requests.remove(pingId) == null) { + // reply not received yet, put future into map under pingId + requests.put(pingId, replyDumpFuture); + } else { + // reply already received, complete future + // ping reply couldn't complete the future because it is not in map under + // ping id + replyDumpFuture.complete(replyDumpFuture.getReplyDump()); + requests.remove(contextId); + } + } + + // 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 replyDumpFuture; + } 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/java/jvpp-registry/io/fd/vpp/jvpp/future/FutureJVppInvoker.java b/java/jvpp-registry/io/fd/vpp/jvpp/future/FutureJVppInvoker.java new file mode 100644 index 0000000..65250ed --- /dev/null +++ b/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.EventRegistryProvider; + +/** +* Future facade on top of JVpp +*/ +public interface FutureJVppInvoker extends EventRegistryProvider, 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/java/jvpp-registry/io/fd/vpp/jvpp/notification/EventRegistry.java b/java/jvpp-registry/io/fd/vpp/jvpp/notification/EventRegistry.java new file mode 100644 index 0000000..12515a5 --- /dev/null +++ b/java/jvpp-registry/io/fd/vpp/jvpp/notification/EventRegistry.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 EventRegistry extends AutoCloseable { + + void close(); +} diff --git a/java/jvpp-registry/io/fd/vpp/jvpp/notification/EventRegistryProvider.java b/java/jvpp-registry/io/fd/vpp/jvpp/notification/EventRegistryProvider.java new file mode 100644 index 0000000..1ac5d55 --- /dev/null +++ b/java/jvpp-registry/io/fd/vpp/jvpp/notification/EventRegistryProvider.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 EventRegistryProvider { + + /** + * Get current notification registry instance + */ + EventRegistry getEventRegistry(); +} diff --git a/java/jvpp-registry/io/fd/vpp/jvpp/test/ConnectionTest.java b/java/jvpp-registry/io/fd/vpp/jvpp/test/ConnectionTest.java new file mode 100644 index 0000000..27b4d29 --- /dev/null +++ b/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/java/jvpp-registry/jvpp_registry.c b/java/jvpp-registry/jvpp_registry.c new file mode 100644 index 0000000..bbe9719 --- /dev/null +++ b/java/jvpp-registry/jvpp_registry.c @@ -0,0 +1,410 @@ +/* + * 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; +} + +#if USE_DLMALLOC == 1 +void * __jvpp_heap; +#endif + +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 USE_DLMALLOC == 1 + __jvpp_heap = clib_mem_init (0, 1<<20); +#endif + + 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/java/jvpp/gen/jvpp_gen.py b/java/jvpp/gen/jvpp_gen.py new file mode 100755 index 0000000..067a92f --- /dev/null +++ b/java/jvpp/gen/jvpp_gen.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python2 +# +# Copyright (c) 2016,2018 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 logging +import os +import sys + +from jvppgen.types_gen import generate_types +from jvppgen.enums_gen import generate_enums +from jvppgen.unions_gen import generate_unions +from jvppgen.dto_gen import generate_dtos +from jvppgen.jvpp_ifc_gen import generate_java_ifc +from jvppgen.jvpp_impl_gen import generate_java_impl +from jvppgen.callback_gen import generate_callbacks +from jvppgen.jni_gen import generate_jni +from jvppgen.notification_gen import generate_notifications +from jvppgen.jvpp_future_facade_gen import generate_future_facade +from jvppgen.jvpp_callback_facade_gen import generate_callback_facade +from jvppgen.jvpp_model import JVppModel + + +def generate_jvpp(root_dir, model, logger): + base_dir = "%s/target/%s" % (root_dir, model.plugin_package.replace(".", "/")) + generate_types(_work_dir(base_dir, "types"), model, logger) + generate_enums(_work_dir(base_dir, "types"), model, logger) + generate_unions(_work_dir(base_dir, "types"), model, logger) + generate_dtos(_work_dir(base_dir, "dto"), model, logger) + generate_java_ifc(_work_dir(base_dir), model, logger) + generate_java_impl(_work_dir(base_dir), model, logger) + generate_callbacks(_work_dir(base_dir, "callback"), model, logger) + generate_jni(root_dir, model, logger) + generate_notifications(_work_dir(base_dir, "notification"), model, logger) + generate_future_facade(_work_dir(base_dir, "future"), model, logger) + generate_callback_facade(_work_dir(base_dir, "callfacade"), model, logger) + + +def _work_dir(work_dir, sub_dir=None): + if sub_dir: + work_dir = "%s/%s" % (work_dir, sub_dir) + try: + os.makedirs(work_dir) + except OSError: + if not os.path.isdir(work_dir): + raise + return work_dir + + +def _init_logger(): + try: + verbose = int(os.getenv("V", 0)) + except: + verbose = 0 + + log_level = logging.WARNING + if verbose == 1: + log_level = logging.INFO + elif verbose >= 2: + log_level = logging.DEBUG + + logging.basicConfig(stream=sys.stdout, level=log_level) + logger = logging.getLogger("JVPP GEN") + logger.setLevel(log_level) + return logger + + +if __name__ == '__main__': + logger = _init_logger() + + argparser = argparse.ArgumentParser(description="VPP Java API generator") + argparser.add_argument('-i', nargs='+', metavar='api_file.json', help="json vpp api file(s)") + argparser.add_argument('--plugin_name') + argparser.add_argument('--root_dir') + args = argparser.parse_args() + + logger.info("Generating Java API for %s" % args.i) + logger.debug("plugin_name: %s" % args.plugin_name) + logger.debug("root_dir: %s" % args.root_dir) + + model = JVppModel(logger, args.i, args.plugin_name) + generate_jvpp(args.root_dir, model, logger) diff --git a/java/jvpp/gen/jvppgen/__init__.py b/java/jvpp/gen/jvppgen/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/java/jvpp/gen/jvppgen/__init__.py diff --git a/java/jvpp/gen/jvppgen/callback_gen.py b/java/jvpp/gen/jvppgen/callback_gen.py new file mode 100755 index 0000000..b1ad201 --- /dev/null +++ b/java/jvpp/gen/jvppgen/callback_gen.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python2 +# +# Copyright (c) 2016,2018 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 + +from jvpp_model import is_request, is_dump, is_control_ping, is_control_ping_reply + + +def generate_callbacks(work_dir, model, logger): + json_api_files = model.json_api_files + logger.debug("Generating Callback interfaces for %s" % json_api_files) + plugin_package = model.plugin_package + + callbacks = [] + for msg in model.messages: + name = msg.java_name_upper + if is_control_ping(msg) or is_control_ping_reply(msg): + # Skip control_ping managed by jvpp registry. + continue + if is_dump(msg) or is_request(msg): + continue + + callbacks.append("%s.callback.%sCallback" % (plugin_package, name)) + callback = _CALLBACK_TEMPLATE.substitute( + plugin_package=plugin_package, + json_filename=json_api_files, + name=name) + + with open("%s/%sCallback.java" % (work_dir, name), "w") as f: + f.write(callback) + + plugin_name = model.plugin_java_name + with open("%s/JVpp%sGlobalCallback.java" % (work_dir, plugin_name), "w") as f: + f.write(_GLOBAL_CALLBACK_TEMPLATE.substitute( + plugin_package=plugin_package, + json_filename=json_api_files, + plugin_name=plugin_name, + callbacks=", ".join(callbacks) + )) + +_CALLBACK_TEMPLATE = Template("""package $plugin_package.callback; + +/** + * <p>Represents callback for plugin's api message. + * <br>It was generated by jvppgen/callback_gen.py based on $json_filename. + */ +public interface ${name}Callback extends io.fd.vpp.jvpp.callback.JVppCallback { + + void on${name}(${plugin_package}.dto.${name} reply); +} +""") + +_GLOBAL_CALLBACK_TEMPLATE = Template("""package $plugin_package.callback; + +/** + * <p>Global aggregated callback interface. + * <br>It was generated by jvppgen/callback_gen.py based on $json_filename. + */ +public interface JVpp${plugin_name}GlobalCallback extends io.fd.vpp.jvpp.callback.ControlPingCallback, $callbacks { +} +""") diff --git a/java/jvpp/gen/jvppgen/dto_gen.py b/java/jvpp/gen/jvppgen/dto_gen.py new file mode 100755 index 0000000..cbd969d --- /dev/null +++ b/java/jvpp/gen/jvppgen/dto_gen.py @@ -0,0 +1,231 @@ +#!/usr/bin/env python2 +# +# Copyright (c) 2016,2018 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 + +from jvpp_common_gen import generate_hash_code, generate_equals, generate_to_string, generate_fields +from jvpp_model import is_request, is_reply, is_retval, is_dump, is_details, is_event, is_control_ping, \ + is_control_ping_reply + + +def generate_dtos(work_dir, model, logger): + logger.debug("Generating DTOs for %s " % model.json_api_files) + _generate_message_dtos(work_dir, model, logger) + _generate_dump_reply_wrappers(work_dir, model, logger) + + +def _generate_message_dtos(work_dir, model, logger): + for msg in model.messages: + logger.debug("Generating DTO for message %s", msg) + class_name = msg.java_name_upper + + if is_control_ping(msg) or is_control_ping_reply(msg): + # Skip control_ping managed by jvpp registry. + continue + if is_request(msg): + dto = _generate_request_dto(msg, model, base_type="JVppRequest") + elif is_dump(msg): + dto = _generate_request_dto(msg, model, base_type="JVppDump") + elif is_reply(msg) or is_details(msg): + dto = _generate_reply_dto(msg, model) + elif is_event(msg): + dto = _generate_event_dto(msg, model) + else: + logger.warn("Failed to generate DTO for: %s. Message type is not supported." % msg) + continue + with open("%s/%s.java" % (work_dir, class_name), "w") as f: + f.write(dto) + + +def _generate_request_dto(msg, model, base_type): + msg_java_name_upper = msg.java_name_upper + fields = msg.fields + return _REQUEST_TEMPLATE.substitute( + plugin_package=model.plugin_package, + json_filename=model.json_api_files, + json_definition=msg.doc, + class_name=msg_java_name_upper, + base_type=base_type, + fields=generate_fields(fields), + hash_code=generate_hash_code(fields), + equals=generate_equals(msg_java_name_upper, fields), + to_string=generate_to_string(msg_java_name_upper, fields), + send=_generate_send(model, msg)) + +_REQUEST_TEMPLATE = Template(""" +package $plugin_package.dto; + +/** + * <p>This class represents request DTO. + * <br>It was generated by dto_gen.py based on $json_filename: + * <pre> +$json_definition + * </pre> + */ +public final class $class_name implements io.fd.vpp.jvpp.dto.$base_type { +$fields +$hash_code +$equals +$to_string +$send +} +""") + + +def _generate_send(model, msg): + return _SEND_TEMPLATE.substitute( + plugin_package=model.plugin_package, + plugin_name=model.plugin_java_name, + method_name=msg.java_name_lower, + args="this" if msg.has_fields else "" + ) + +_SEND_TEMPLATE = Template(""" + @Override + public int send(final io.fd.vpp.jvpp.JVpp jvpp) throws io.fd.vpp.jvpp.VppInvocationException { + return (($plugin_package.JVpp${plugin_name})jvpp).$method_name($args); + }""") + + +def _generate_reply_dto(msg, model): + msg_java_name_upper = msg.java_name_upper + # Negative retval is mapped to java exception, so filter it out: + fields = filter(lambda field: not is_retval(field), msg.fields) + return _REPLY_TEMPLATE.substitute( + plugin_package=model.plugin_package, + json_filename=model.json_api_files, + json_definition=msg.doc, + class_name=msg_java_name_upper, + request_name=msg.request_java, + fields=generate_fields(fields), + hash_code=generate_hash_code(fields), + equals=generate_equals(msg_java_name_upper, fields), + to_string=generate_to_string(msg_java_name_upper, fields)) + +_REPLY_TEMPLATE = Template(""" +package $plugin_package.dto; + +/** + * <p>This class represents reply DTO. + * <br>It was generated by jvpp_dto_gen.py based on $json_filename: + * <pre> +$json_definition + * </pre> + */ +public final class $class_name implements io.fd.vpp.jvpp.dto.JVppReply<$plugin_package.dto.$request_name> { +$fields +$hash_code +$equals +$to_string +} +""") + + +def _generate_event_dto(msg, model): + msg_java_name_upper = msg.java_name_upper + # Negative retval is mapped to java exception, so filter it out: + fields = filter(lambda field: not is_retval(field), msg.fields) + return _EVENT_TEMPLATE.substitute( + plugin_package=model.plugin_package, + json_filename=model.json_api_files, + json_definition=msg.doc, + class_name=msg_java_name_upper, + fields=generate_fields(fields), + hash_code=generate_hash_code(fields), + equals=generate_equals(msg_java_name_upper, fields), + to_string=generate_to_string(msg_java_name_upper, fields)) + +_EVENT_TEMPLATE = Template(""" +package $plugin_package.dto; + +/** + * <p>This class represents event DTO. + * <br>It was generated by jvpp_dto_gen.py based on $json_filename: + * <pre> +$json_definition + * </pre> + */ +public final class $class_name { +$fields +$hash_code +$equals +$to_string +} +""") + + +def _generate_dump_reply_wrappers(work_dir, model, logger): + for msg in model.messages: + if is_details(msg): + logger.debug("Generating ReplyDump DTO for message %s", msg) + details_class = msg.java_name_upper + dto = _REPLY_DUMP_TEMPLATE.substitute( + plugin_package=model.plugin_package, + json_filename=model.json_api_files, + json_definition=msg.doc, + details_class=details_class, + details_field=msg.java_name_lower, + dump_class=msg.request_java + ) + with open("%s/%sReplyDump.java" % (work_dir, details_class), "w") as f: + f.write(dto) + +_REPLY_DUMP_TEMPLATE = Template(""" +package $plugin_package.dto; + +/** + * <p>This class represents dump reply wrapper. + * <br>It was generated by jvpp_dto_gen.py based on $json_filename: + * <pre> +$json_definition + * </pre> + */ +public final class ${details_class}ReplyDump implements io.fd.vpp.jvpp.dto.JVppReplyDump<${plugin_package}.dto.${dump_class}, ${plugin_package}.dto.${details_class}> { + + public java.util.List<${details_class}> ${details_field} = new java.util.ArrayList<>(); + + @Override + @io.fd.vpp.jvpp.coverity.SuppressFBWarnings("UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD") + public int hashCode() { + return java.util.Objects.hash(${details_field}); + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + final ${details_class}ReplyDump other = (${details_class}ReplyDump) o; + + if (!java.util.Objects.equals(this.${details_field}, other.${details_field})) { + return false; + } + + return true; + } + + @Override + public java.lang.String toString() { + return "${details_class}ReplyDump{" + + "${details_field}=" + ${details_field} + "}"; + } + + +} +""") diff --git a/java/jvpp/gen/jvppgen/enums_gen.py b/java/jvpp/gen/jvppgen/enums_gen.py new file mode 100755 index 0000000..8ba9655 --- /dev/null +++ b/java/jvpp/gen/jvppgen/enums_gen.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python2 +# +# Copyright (c) 2016,2018 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 + +from jvpp_model import Enum + + +def generate_enums(work_dir, model, logger): + logger.debug("Generating enums for %s " % model.json_api_files) + + for t in model.types: + if not isinstance(t, Enum): + continue + logger.debug("Generating DTO for enum %s", t) + type_class_name = t.java_name + type_class = _ENUM_TEMPLATE.substitute( + plugin_package=model.plugin_package, + c_type_name=t.name, + json_filename=model.json_api_files, + json_definition=t.doc, + java_enum_name=type_class_name, + constants=_generate_constants(t.constants), + value_type=t.value.type.java_name + ) + with open("%s/%s.java" % (work_dir, type_class_name), "w") as f: + f.write(type_class) + +_ENUM_TEMPLATE = Template(""" +package $plugin_package.types; + +/** + * <p>This class represents $c_type_name enum definition. + * <br>It was generated by enums_gen.py based on $json_filename: + * <pre> +$json_definition + * </pre> + */ +public enum $java_enum_name { +$constants; + + public final $value_type value; + + $java_enum_name(final $value_type value) { + this.value = value; + } + + public static $java_enum_name forValue(final $value_type value) { + for ($java_enum_name enumeration : $java_enum_name.values()) { + if (value == enumeration.value) { + return enumeration; + } + } + return null; + } +} +""") + + +def _generate_constants(constants): + return ",\n".join(_CONSTANT_TEMPLATE.substitute(name=c['name'], value=c['value']) for c in constants) + +_CONSTANT_TEMPLATE = Template(""" $name($value)""") diff --git a/java/jvpp/gen/jvppgen/jni_common_gen.py b/java/jvpp/gen/jvppgen/jni_common_gen.py new file mode 100755 index 0000000..b52e5ff --- /dev/null +++ b/java/jvpp/gen/jvppgen/jni_common_gen.py @@ -0,0 +1,482 @@ +#!/usr/bin/env python2 +# +# Copyright (c) 2018 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 + +from jvpp_model import is_array, is_retval, Class, Enum, Union + + +def generate_j2c_identifiers(element, class_ref_name, object_ref_name): + identifiers = [] + for field in element.fields: + field_type = field.type + identifiers.append(_REQUEST_FIELD_IDENTIFIER_TEMPLATE.substitute( + java_name=field.java_name, + class_ref_name=class_ref_name, + jni_signature=field_type.jni_signature, + jni_type=field_type.jni_type, + jni_accessor=field_type.jni_accessor, + object_ref_name=object_ref_name + )) + return "".join(identifiers) + +_REQUEST_FIELD_IDENTIFIER_TEMPLATE = Template(""" + jfieldID ${java_name}FieldId = (*env)->GetFieldID(env, ${class_ref_name}, "${java_name}", "${jni_signature}"); + ${jni_type} ${java_name} = (*env)->Get${jni_accessor}Field(env, ${object_ref_name}, ${java_name}FieldId); +""") + + +# TODO(VPP-1187): do not inline JNI object creation inside message handlers to reduce number of special cases +def generate_j2c_swap(element, struct_ref_name, is_alias): + initialization = [] + for field in element.fields: + initialization.append(generate_j2c_field_swap(field, struct_ref_name, is_alias)) + return "\n".join(initialization) + + +def generate_j2c_field_swap(field, struct_ref_name, is_alias): + if is_array(field): + return _generate_j2c_array_swap(field, struct_ref_name, is_alias) + else: + return _generate_j2c_scalar_swap(field, struct_ref_name, is_alias) + + +def _generate_j2c_array_swap(field, struct_ref_name, is_alias): + # TODO(VPP-1186): move the logic to JNI generators + base_type = field.type.base_type + if isinstance(base_type, (Class, Enum, Union)): + return _generate_j2c_object_array_swap(field, struct_ref_name) + elif base_type.is_swap_needed: + return _generate_j2c_primitive_type_array_swap(field, struct_ref_name) + else: + return _generate_j2c_primitive_type_array_no_swap(field, struct_ref_name, is_alias) + + +def _generate_j2c_object_array_swap(field, struct_ref_name): + field_type = field.type + field_reference_name = field.java_name + c_name = field.name + host = "%sArrayElement" % field_reference_name + net = "%s->%s[_i]" % (struct_ref_name, c_name) + swap_elements = field_type.get_host_to_net_function(host, net) + return _J2C_OBJECT_ARRAY_SWAP_TEMPLATE.substitute( + field_reference_name=field_reference_name, + field_length_check=_generate_field_length_check(field), + swap_elements=swap_elements) + +_J2C_OBJECT_ARRAY_SWAP_TEMPLATE = Template(""" + { + 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); + ${swap_elements}; + } + } + } +""") + + +def _generate_j2c_primitive_type_array_swap(field, struct_ref_name): + field_reference_name = field.java_name + field_type = field.type + host = "%sArrayElements[_i]" % field_reference_name + net = "%s->%s[_i]" % (struct_ref_name, field.name) + swap_elements = field_type.get_host_to_net_function(host, net) + return _J2C_PRIMITIVE_TYPE_ARRAY_SWAP_TEMPLATE.substitute( + field_reference_name=field_reference_name, + field_length_check=_generate_field_length_check(field), + base_type=field_type.base_type.jni_accessor, + jni_base_type=field_type.base_type.jni_type, + swap_elements=swap_elements + ) + +_J2C_PRIMITIVE_TYPE_ARRAY_SWAP_TEMPLATE = Template(""" + if (${field_reference_name}) { + ${jni_base_type} * ${field_reference_name}ArrayElements = (*env)->Get${base_type}ArrayElements(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++) { + ${swap_elements}; + } + (*env)->Release${base_type}ArrayElements (env, ${field_reference_name}, ${field_reference_name}ArrayElements, 0); + } + """) + + +def _generate_j2c_primitive_type_array_no_swap(field, struct_ref_name, is_alias): + field_type = field.type + if not is_alias: + template = _J2C_PRIMITIVE_TYPE_ARRAY_NO_SWAP_TEMPLATE + else: + template = _J2C_ALIAS_PRIMITIVE_TYPE_ARRAY_NO_SWAP_TEMPLATE + + return template.substitute( + field_reference_name=field.java_name, + field_length_check=_generate_field_length_check(field), + base_type=field_type.base_type.jni_accessor, + jni_base_type=field_type.base_type.jni_type, + struct_reference_name=struct_ref_name, + c_name=field.name + ) + +_J2C_PRIMITIVE_TYPE_ARRAY_NO_SWAP_TEMPLATE = Template(""" + if (${field_reference_name}) { + jsize cnt = (*env)->GetArrayLength (env, ${field_reference_name}); + ${field_length_check} + (*env)->Get${base_type}ArrayRegion(env, ${field_reference_name}, 0, cnt, (${jni_base_type} *)${struct_reference_name}->${c_name}); + } +""") + + +_J2C_ALIAS_PRIMITIVE_TYPE_ARRAY_NO_SWAP_TEMPLATE = Template(""" + if (${field_reference_name}) { + jsize cnt = (*env)->GetArrayLength (env, ${field_reference_name}); + ${field_length_check} + (*env)->Get${base_type}ArrayRegion(env, ${field_reference_name}, 0, cnt, (${jni_base_type} *)${struct_reference_name}); + } +""") + + +def _generate_field_length_check(field): + # Enforce max length if array has fixed length or uses variable length syntax + field_length = str(field.array_len) + if field.array_len_field: + field_length = field.array_len_field.java_name + + # TODO: remove when ZLAs without length field are disabled + if field_length != "0": + return _FIELD_LENGTH_CHECK.substitute(field_length=field_length) + else: + return "" + + +# Make sure we do not write more elements that are expected +_FIELD_LENGTH_CHECK = Template(""" + size_t max_size = ${field_length}; + if (cnt > max_size) cnt = max_size;""") + + +def _generate_j2c_scalar_swap(field, struct_ref_name, is_alias): + field_type = field.type + if field_type.is_swap_needed: + host = field.java_name + if not is_alias: + net = "%s->%s" % (struct_ref_name, field.name) + if field_type.name == "string": + net = "%s->%s" % (struct_ref_name, field.name) + return " _host_to_net_%s(env, %s, (vl_api_string_t *) &%s);" % (field_type.name, host, net) + else: + return " %s;" % field_type.get_host_to_net_function(host, net) + else: + net = "%s" % struct_ref_name + return " *%s;" % field_type.get_host_to_net_function(host, net) + else: + return " %s->%s = %s;" % (struct_ref_name, field.name, field.java_name) + + +def generate_c2j_swap(element, object_ref_name, struct_ref_name, is_alias): + msg_java_name = element.java_name_lower + initialization = [] + for field in element.fields: + if is_retval(field): + # For retval don't generate setters and generate retval check + continue + elif is_array(field): + initialization.append(_generate_c2j_array_swap(msg_java_name, field, object_ref_name, struct_ref_name, is_alias)) + else: + initialization.append(_generate_c2j_scalar_swap(msg_java_name, field, object_ref_name, struct_ref_name, is_alias)) + return "".join(initialization) + + +def _generate_c2j_array_swap(msg_java_name, field, object_ref_name, struct_ref_name, is_alias): + # TODO(VPP-1186): move the logic to JNI generators + base_type = field.type.base_type + if isinstance(base_type, (Class, Union)): + return _generate_c2j_object_array_swap(msg_java_name, field, object_ref_name, struct_ref_name) + elif isinstance(base_type, Enum): + return _generate_c2j_enum_array_swap(msg_java_name, field, object_ref_name, struct_ref_name) + elif base_type.is_swap_needed: + return _generate_c2j_primitive_type_array_swap(msg_java_name, field, object_ref_name, struct_ref_name) + else: + return _generate_c2j_primitive_type_array_no_swap(msg_java_name, field, object_ref_name, struct_ref_name, is_alias) + + +def _generate_c2j_object_array_swap(msg_java_name, field, object_ref_name, struct_ref_name): + field_type = field.type + return _C2J_OBJECT_ARRAY_SWAP_TEMPLATE.substitute( + field_reference_name=field.java_name, + class_ref_name=msg_java_name, + jni_signature=field_type.jni_signature, + jni_name=field_type.base_type.jni_name, + field_length=_generate_array_length(field, struct_ref_name), + net_to_host_function=field_type.net_to_host_function, + struct_ref_name=struct_ref_name, + object_ref_name=object_ref_name, + c_name=field.name + ) + +_C2J_OBJECT_ARRAY_SWAP_TEMPLATE = Template(""" + jfieldID ${field_reference_name}FieldId = (*env)->GetFieldID(env, ${class_ref_name}Class, "${field_reference_name}", "${jni_signature}"); + { + jclass ${field_reference_name}Class = (*env)->FindClass(env, "${jni_name}"); + 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); + ${net_to_host_function}(env, &(${struct_ref_name}->${c_name}[_i]), ${field_reference_name}ArrayElement); + (*env)->SetObjectArrayElement(env, ${field_reference_name}, _i, ${field_reference_name}ArrayElement); + (*env)->DeleteLocalRef(env, ${field_reference_name}ArrayElement); + } + (*env)->SetObjectField(env, ${object_ref_name}, ${field_reference_name}FieldId, ${field_reference_name}); + (*env)->DeleteLocalRef(env, ${field_reference_name}); + } +""") + + +def _generate_c2j_enum_array_swap(msg_java_name, field, object_ref_name, struct_ref_name): + field_type = field.type + base_type = field_type.base_type + return _C2J_ENUM_ARRAY_SWAP_TEMPLATE.substitute( + field_reference_name=field.java_name, + class_ref_name=msg_java_name, + jni_signature=field_type.jni_signature, + jni_name=base_type.jni_name, + field_length=_generate_array_length(field, struct_ref_name), + net_to_host_function=field_type.net_to_host_function, + jni_signature_enum_value=base_type.value.type.jni_signature, + struct_ref_name=struct_ref_name, + object_ref_name=object_ref_name, + c_name=field.name + ) + +_C2J_ENUM_ARRAY_SWAP_TEMPLATE = Template(""" + jfieldID ${field_reference_name}FieldId = (*env)->GetFieldID(env, ${class_ref_name}Class, "${field_reference_name}", "${jni_signature}"); + { + jclass ${field_reference_name}Class = (*env)->FindClass(env, "${jni_name}"); + jobjectArray ${field_reference_name} = (*env)->NewObjectArray(env, ${field_length}, ${field_reference_name}Class, 0); + jmethodID ${field_reference_name}Constructor = (*env)->GetStaticMethodID(env, ${field_reference_name}Class, "forValue", "(${jni_signature_enum_value})${jni_signature}"); + unsigned int _i; + for (_i = 0; _i < ${field_length}; _i++) { + jobject ${field_reference_name}ArrayElement = (*env)->CallStaticObjectMethod(env, ${field_reference_name}Class, ${field_reference_name}Constructor, ${net_to_host_function}(${struct_ref_name}->${c_name}[_i])); + (*env)->SetObjectArrayElement(env, ${field_reference_name}, _i, ${field_reference_name}ArrayElement); + (*env)->DeleteLocalRef(env, ${field_reference_name}ArrayElement); + } + (*env)->SetObjectField(env, ${object_ref_name}, ${field_reference_name}FieldId, ${field_reference_name}); + (*env)->DeleteLocalRef(env, ${field_reference_name}); + } +""") + + +def _generate_c2j_primitive_type_array_swap(msg_java_name, field, object_ref_name, struct_ref_name): + field_type = field.type + return _C2J_PRIMITIVE_TYPE_ARRAY_SWAP_TEMPLATE.substitute( + field_reference_name=field.java_name, + class_ref_name=msg_java_name, + jni_signature=field_type.jni_signature, + jni_type=field_type.jni_type, + base_type=field_type.base_type.jni_accessor, + field_length=_generate_array_length(field, struct_ref_name), + jni_base_type=field_type.base_type.jni_type, + object_ref_name=object_ref_name, + struct_ref_name=struct_ref_name, + net_to_host_function=field_type.net_to_host_function, + c_name=field.name + ) + +_C2J_PRIMITIVE_TYPE_ARRAY_SWAP_TEMPLATE = Template(""" + jfieldID ${field_reference_name}FieldId = (*env)->GetFieldID(env, ${class_ref_name}Class, "${field_reference_name}", "${jni_signature}"); + { + ${jni_type} ${field_reference_name} = (*env)->New${base_type}Array(env, ${field_length}); + ${jni_base_type} * ${field_reference_name}ArrayElements = (*env)->Get${base_type}ArrayElements(env, ${field_reference_name}, NULL); + unsigned int _i; + for (_i = 0; _i < ${field_length}; _i++) { + ${field_reference_name}ArrayElements[_i] = ${net_to_host_function}(${struct_ref_name}->${c_name}[_i]); + } + + (*env)->Release${base_type}ArrayElements(env, ${field_reference_name}, ${field_reference_name}ArrayElements, 0); + (*env)->SetObjectField(env, ${object_ref_name}, ${field_reference_name}FieldId, ${field_reference_name}); + (*env)->DeleteLocalRef(env, ${field_reference_name}); + } +""") + + +def _generate_c2j_primitive_type_array_no_swap(msg_java_name, field, object_ref_name, struct_ref_name, is_alias): + field_type = field.type + if not is_alias: + template = _C2J_PRIMITIVE_TYPE_ARRAY_NO_SWAP_TEMPLATE + else: + template = _C2J_ALIAS_PRIMITIVE_TYPE_ARRAY_NO_SWAP_TEMPLATE + return template.substitute( + field_reference_name=field.java_name, + class_ref_name=msg_java_name, + jni_signature=field_type.jni_signature, + jni_type=field_type.jni_type, + base_type=field_type.base_type.jni_accessor, + field_length=_generate_array_length(field, struct_ref_name), + jni_base_type=field_type.base_type.jni_type, + object_ref_name=object_ref_name, + struct_ref_name=struct_ref_name, + c_name=field.name + ) + +_C2J_PRIMITIVE_TYPE_ARRAY_NO_SWAP_TEMPLATE = Template(""" + jfieldID ${field_reference_name}FieldId = (*env)->GetFieldID(env, ${class_ref_name}Class, "${field_reference_name}", "${jni_signature}"); + ${jni_type} ${field_reference_name} = (*env)->New${base_type}Array(env, ${field_length}); + (*env)->Set${base_type}ArrayRegion(env, ${field_reference_name}, 0, ${field_length}, (const ${jni_base_type}*)${struct_ref_name}->${c_name}); + (*env)->SetObjectField(env, ${object_ref_name}, ${field_reference_name}FieldId, ${field_reference_name}); + (*env)->DeleteLocalRef(env, ${field_reference_name}); +""") + + +_C2J_ALIAS_PRIMITIVE_TYPE_ARRAY_NO_SWAP_TEMPLATE = Template(""" + jfieldID ${field_reference_name}FieldId = (*env)->GetFieldID(env, ${class_ref_name}Class, "${field_reference_name}", "${jni_signature}"); + ${jni_type} ${field_reference_name} = (*env)->New${base_type}Array(env, ${field_length}); + (*env)->Set${base_type}ArrayRegion(env, ${field_reference_name}, 0, ${field_length}, (const ${jni_base_type}*)${struct_ref_name}); + (*env)->SetObjectField(env, ${object_ref_name}, ${field_reference_name}FieldId, ${field_reference_name}); + (*env)->DeleteLocalRef(env, ${field_reference_name}); +""") + + +def _generate_array_length(field, struct_ref_name): + if field.array_len_field: + len_field = field.array_len_field + if len_field.type.is_swap_needed: + return "%s(%s->%s)" % (len_field.type.host_to_net_function, struct_ref_name, len_field.name) + else: + return "%s->%s" % (struct_ref_name, len_field.name) + return field.array_len + + +def _generate_c2j_scalar_swap(msg_java_name, field, object_ref_name, struct_ref_name, is_alias): + field_type = field.type + if field_type.is_swap_needed: + # TODO(VPP-1186): move the logic to JNI generators + if isinstance(field_type, (Class, Union)): + return _generate_c2j_object_swap(msg_java_name, field, object_ref_name, struct_ref_name) + elif isinstance(field_type, Enum): + return _generate_c2j_enum_swap(msg_java_name, field, object_ref_name, struct_ref_name) + else: + return _generate_c2j_primitive_type_swap(msg_java_name, field, object_ref_name, struct_ref_name, is_alias) + else: + return _generate_c2j_primitive_type_no_swap(msg_java_name, field, object_ref_name, struct_ref_name) + + +def _generate_c2j_object_swap(msg_java_name, field, object_ref_name, struct_ref_name): + field_type = field.type + return _C2J_OBJECT_SWAP_TEMPLATE.substitute( + java_name=field.java_name, + class_ref_name=msg_java_name, + jni_signature=field_type.jni_signature, + jni_name=field_type.jni_name, + jni_accessor=field_type.jni_accessor, + object_ref_name=object_ref_name, + struct_ref_name=struct_ref_name, + net_to_host_function=field_type.net_to_host_function, + c_name=field.name) + +_C2J_OBJECT_SWAP_TEMPLATE = Template(""" + jfieldID ${java_name}FieldId = (*env)->GetFieldID(env, ${class_ref_name}Class, "${java_name}", "${jni_signature}"); + jclass ${java_name}Class = (*env)->FindClass(env, "${jni_name}"); + jmethodID ${java_name}Constructor = (*env)->GetMethodID(env, ${java_name}Class, "<init>", "()V"); + jobject ${java_name} = (*env)->NewObject(env, ${java_name}Class, ${java_name}Constructor); + ${net_to_host_function}(env, &(${struct_ref_name}->${c_name}), ${java_name}); + (*env)->Set${jni_accessor}Field(env, ${object_ref_name}, ${java_name}FieldId, ${java_name}); + (*env)->DeleteLocalRef(env, ${java_name}); +""") + + +def _generate_c2j_enum_swap(msg_java_name, field, object_ref_name, struct_ref_name): + field_type = field.type + return _C2J_ENUM_SWAP_TEMPLATE.substitute( + java_name=field.java_name, + class_ref_name=msg_java_name, + jni_signature=field_type.jni_signature, + jni_signature_enum_value=field_type.value.type.jni_signature, + jni_name=field_type.jni_name, + jni_accessor=field_type.jni_accessor, + object_ref_name=object_ref_name, + struct_ref_name=struct_ref_name, + net_to_host_function=field_type.net_to_host_function, + c_name=field.name) + +_C2J_ENUM_SWAP_TEMPLATE = Template(""" + jfieldID ${java_name}FieldId = (*env)->GetFieldID(env, ${class_ref_name}Class, "${java_name}", "${jni_signature}"); + jclass ${java_name}Class = (*env)->FindClass(env, "${jni_name}"); + jmethodID ${java_name}Constructor = (*env)->GetStaticMethodID(env, ${java_name}Class, "forValue", "(${jni_signature_enum_value})${jni_signature}"); + jobject ${java_name} = (*env)->CallStaticObjectMethod(env, ${java_name}Class, ${java_name}Constructor, ${net_to_host_function}(${struct_ref_name}->${c_name})); + (*env)->SetObjectField(env, ${object_ref_name}, ${java_name}FieldId, ${java_name}); + (*env)->DeleteLocalRef(env, ${java_name}); +""") + + +def _generate_c2j_primitive_type_swap(msg_java_name, field, object_ref_name, struct_ref_name, is_alias): + field_type = field.type + if not is_alias: + if field_type.name == "string": + template = _C2J_STRING_TYPE_SWAP_TEMPLATE + else: + template = _C2J_PRIMITIVE_TYPE_SWAP_TEMPLATE + else: + template = _C2J_ALIAS_PRIMITIVE_TYPE_SWAP_TEMPLATE + return template.substitute( + java_name=field.java_name, + class_ref_name=msg_java_name, + jni_signature=field_type.jni_signature, + jni_accessor=field_type.jni_accessor, + object_ref_name=object_ref_name, + net_to_host_function=field_type.net_to_host_function, + struct_ref_name=struct_ref_name, + c_name=field.name + ) + +_C2J_PRIMITIVE_TYPE_SWAP_TEMPLATE = Template(""" + jfieldID ${java_name}FieldId = (*env)->GetFieldID(env, ${class_ref_name}Class, "${java_name}", "${jni_signature}"); + (*env)->Set${jni_accessor}Field(env, ${object_ref_name}, ${java_name}FieldId, ${net_to_host_function}(${struct_ref_name}->${c_name})); +""") + + +_C2J_STRING_TYPE_SWAP_TEMPLATE = Template(""" + jfieldID ${java_name}FieldId = (*env)->GetFieldID(env, ${class_ref_name}Class, "${java_name}", "${jni_signature}"); + (*env)->Set${jni_accessor}Field(env, ${object_ref_name}, ${java_name}FieldId, ${net_to_host_function}(env, (const vl_api_string_t *) &${struct_ref_name}->${c_name})); +""") + + +_C2J_ALIAS_PRIMITIVE_TYPE_SWAP_TEMPLATE = Template(""" + jfieldID ${java_name}FieldId = (*env)->GetFieldID(env, ${class_ref_name}Class, "${java_name}", "${jni_signature}"); + (*env)->Set${jni_accessor}Field(env, ${object_ref_name}, ${java_name}FieldId, ${net_to_host_function}(*${struct_ref_name})); +""") + + +def _generate_c2j_primitive_type_no_swap(msg_java_name, field, object_ref_name, struct_ref_name): + field_type = field.type + return _C2J_PRIMITIVE_TYPE_NO_SWAP_TEMPLATE.substitute( + java_name=field.java_name, + class_ref_name=msg_java_name, + jni_signature=field_type.jni_signature, + jni_accessor=field_type.jni_accessor, + object_ref_name=object_ref_name, + struct_ref_name=struct_ref_name, + c_name=field.name + ) + +_C2J_PRIMITIVE_TYPE_NO_SWAP_TEMPLATE = Template(""" + jfieldID ${java_name}FieldId = (*env)->GetFieldID(env, ${class_ref_name}Class, "${java_name}", "${jni_signature}"); + (*env)->Set${jni_accessor}Field(env, ${object_ref_name}, ${java_name}FieldId, ${struct_ref_name}->${c_name}); +""") diff --git a/java/jvpp/gen/jvppgen/jni_gen.py b/java/jvpp/gen/jvppgen/jni_gen.py new file mode 100755 index 0000000..ad6c261 --- /dev/null +++ b/java/jvpp/gen/jvppgen/jni_gen.py @@ -0,0 +1,160 @@ +#!/usr/bin/env python2 +# +# Copyright (c) 2016,2018 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 + +from jni_impl_gen import generate_jni_impl +from jni_msg_handlers_gen import generate_jni_handlers +from jni_type_handlers_gen import generate_type_handlers +from jvpp_model import is_control_ping, is_dump, is_request, is_control_ping_reply + + +def generate_jni(work_dir, model, logger): + logger.debug("Generating jvpp C for %s" % model.json_api_files) + plugin_name = model.plugin_name + messages = model.messages + + with open("%s/jvpp_%s_gen.h" % (work_dir, plugin_name), "w") as f: + f.write(_JVPP_C_TEMPLATE.substitute( + json_filename=model.json_api_files, + class_cache=_generate_class_cache(plugin_name, messages), + api_verification=_generate_api_verification(messages), + type_handlers=generate_type_handlers(model, logger), + jni_implementations=generate_jni_impl(model), + msg_handlers=generate_jni_handlers(model), + handler_registration=_generate_handler_registration(messages))) + +_JVPP_C_TEMPLATE = Template("""/** + * This file contains JNI bindings for jvpp Java API. + * It was generated by jvpp_jni_gen.py based on $json_filename. + */ +$class_cache + +$api_verification + +// Type handlers +$type_handlers + +// JNI bindings +$jni_implementations + +// Message handlers +$msg_handlers + +$handler_registration +""") + + +def _generate_class_cache(plugin_name, messages): + references = [] + for msg in messages: + if is_control_ping(msg) or is_control_ping_reply(msg): + # Skip control_ping managed by jvpp registry. + continue + references.append(( + msg.java_name_lower, + 'io/fd/vpp/jvpp/%s/dto/%s' % (plugin_name, msg.java_name_upper) + )) + + references.append(('callbackException', 'io/fd/vpp/jvpp/VppCallbackException')) + + return _CLASS_CACHE_TEMPLATE.substitute( + class_references=_generate_class_references(references), + create_references=_generate_create_references(references), + delete_references=_generate_delete_references(references) + ) + +_CLASS_CACHE_TEMPLATE = Template(""" +// JAVA class reference cache +$class_references + +static int cache_class_references(JNIEnv* env) { +$create_references + return 0; +} + +static void delete_class_references(JNIEnv* env) { +$delete_references +}""") + + +def _generate_class_references(references): + return "\n".join("jclass %sClass;" % r[0] for r in references) + + +def _generate_create_references(references): + items = [] + for r in references: + items.append(_CREATE_GLOBAL_REF_TEMPLATE.substitute( + ref_name=r[0], + fqn_name=r[1] + )) + return "".join(items) + +_CREATE_GLOBAL_REF_TEMPLATE = Template(""" + ${ref_name}Class = (jclass)(*env)->NewGlobalRef(env, (*env)->FindClass(env, "${fqn_name}")); + if ((*env)->ExceptionCheck(env)) { + (*env)->ExceptionDescribe(env); + return JNI_ERR; + }""") + + +def _generate_delete_references(references): + items = [] + for r in references: + items.append(_DELETE_CLASS_INVOCATION_TEMPLATE.substitute(ref_name=r[0])) + return "".join(items) + +_DELETE_CLASS_INVOCATION_TEMPLATE = Template(""" + if (${ref_name}Class) { + (*env)->DeleteGlobalRef(env, ${ref_name}Class); + }""") + + +def _generate_api_verification(messages): + items = [] + for msg in messages: + items.append("_(%s_%s) \\" % (msg.name, msg.crc)) + return _API_VERIFICATION_TEMPLATE.substitute(messages="\n".join(items)) + +_API_VERIFICATION_TEMPLATE = Template(""" +// List of supported API messages used for verification +#define foreach_supported_api_message \\ +$messages +""") + + +def _generate_handler_registration(messages): + """ + Generates msg handler registration for all messages except for dumps and requests. + :param messages: collection of VPP API messages. + """ + handlers = [] + for msg in messages: + if is_control_ping(msg) or is_control_ping_reply(msg): + # Skip control_ping managed by jvpp registry. + continue + if is_dump(msg) or is_request(msg): + continue + name = msg.name + crc = msg.crc + handlers.append("_(%s_%s, %s) \\" % (name, crc, name)) + return _HANDLER_REGISTRATION_TEMPLATE.substitute(handlers="\n".join(handlers)) + +_HANDLER_REGISTRATION_TEMPLATE = Template(""" +// Registration of message handlers in vlib +#define foreach_api_reply_handler \\ +$handlers +""") diff --git a/java/jvpp/gen/jvppgen/jni_impl_gen.py b/java/jvpp/gen/jvppgen/jni_impl_gen.py new file mode 100755 index 0000000..ef1bcbb --- /dev/null +++ b/java/jvpp/gen/jvppgen/jni_impl_gen.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python2 +# +# Copyright (c) 2018 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 + +from jni_common_gen import generate_j2c_identifiers, generate_j2c_swap +from jvpp_model import is_dump, is_request, is_control_ping, is_control_ping_reply + + +def generate_jni_impl(model): + """ + Generates JNI bindings for sending dump and request messages. + :param model: meta-model of VPP API used for jVPP generation. + """ + jni_impl = [] + for msg in model.messages: + if is_control_ping(msg) or is_control_ping_reply(msg): + # Skip control ping managed by jvpp registry. + continue + if not (is_dump(msg) or is_request(msg)): + continue + arguments = "" + request_class = "" + jni_identifiers = "" + msg_initialization = "" + + if msg.has_fields: + arguments = ", jobject request" + request_class = _REQUEST_CLASS_TEMPLATE.substitute( + plugin_name=model.plugin_name, + java_dto_name=msg.java_name_upper + ) + jni_identifiers = generate_j2c_identifiers(msg, class_ref_name="requestClass", object_ref_name="request") + msg_initialization = generate_j2c_swap(msg, struct_ref_name="mp", is_alias=False) + + jni_impl.append(_JNI_IMPL_TEMPLATE.substitute( + c_name=msg.name, + json_filename=model.json_api_files, + json_definition=msg.doc, + plugin_name=model.plugin_name, + plugin_java_name=model.plugin_java_name, + java_method_name=msg.java_name_lower, + arguments=arguments, + request_class=request_class, + jni_identifiers=jni_identifiers, + msg_size=_generate_msg_size(msg), + crc=msg.crc, + msg_initialization=msg_initialization + )) + return "".join(jni_impl) + + +_JNI_IMPL_TEMPLATE = Template(""" +/** + * JNI binding for sending ${c_name} message. + * Generated based on $json_filename: +$json_definition + */ +JNIEXPORT jint JNICALL Java_io_fd_vpp_jvpp_${plugin_name}_JVpp${plugin_java_name}Impl_${java_method_name}0 +(JNIEnv * env, jclass clazz${arguments}) { + ${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: + const size_t _size = ${msg_size}; + mp = vl_msg_api_alloc(_size); + memset (mp, 0, _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: + if (CLIB_DEBUG > 1) + clib_warning ("Sending ${c_name} message"); + vl_msg_api_send_shmem (plugin_main->vl_input_queue, (u8 *)&mp); + if ((*env)->ExceptionCheck(env)) { + return JNI_ERR; + } + return my_context_id; +}""") + +# 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_dto_name}"); +""") + + +def _generate_msg_size(msg): + msg_size = "sizeof(*mp)" + _size_components = [] + for field in msg.fields: + # Ignore ZLAs for simplicity (to support them we need to call JNI functions to check actual size) + if field.array_len_field: + _size_components += " + %s*sizeof(%s)" % (field.array_len_field.java_name, field.type.base_type.vpp_name) + # FIXME(VPP-586): for proper nested structures support, we need generate functions computing type sizes + # and use it instead of sizeof + if field.type.name == "string": + _size_components += " + 1 + jstr_length(env, %s) * sizeof(u8)" % field.name + return msg_size + "".join(_size_components) diff --git a/java/jvpp/gen/jvppgen/jni_msg_handlers_gen.py b/java/jvpp/gen/jvppgen/jni_msg_handlers_gen.py new file mode 100755 index 0000000..ccd3dbc --- /dev/null +++ b/java/jvpp/gen/jvppgen/jni_msg_handlers_gen.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python2 +# +# Copyright (c) 2018 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 + +from jni_common_gen import generate_c2j_swap +from jvpp_model import is_dump, is_request, is_control_ping, is_control_ping_reply, is_retval + + +def generate_jni_handlers(model): + """ + Generates msg handlers for all messages except for dumps and requests (handled by vpp, not client). + :param model: meta-model of VPP API used for jVPP generation. + """ + jni_impl = [] + for msg in model.messages: + msg_name = msg.name + if is_control_ping(msg) or is_control_ping_reply(msg): + # Skip control ping managed by jvpp registry. + continue + if is_dump(msg) or is_request(msg): + continue + + jni_impl.append(_MSG_HANDLER_TEMPLATE.substitute( + c_name=msg_name, + json_filename=model.json_api_files, + json_definition=msg.doc, + plugin_name=model.plugin_name, + err_handler=_generate_error_handler(msg), + class_ref_name=msg.java_name_lower, + dto_name=msg.java_name_upper, + dto_setters=generate_c2j_swap(msg, object_ref_name="dto", struct_ref_name="mp", is_alias=False) + )) + return "".join(jni_impl) + + +_MSG_HANDLER_TEMPLATE = Template(""" +/** + * Handler for ${c_name} message. + * Generated based on $json_filename: +$json_definition + */ +static void vl_api_${c_name}_t_handler (vl_api_${c_name}_t * mp) +{ + ${plugin_name}_main_t *plugin_main = &${plugin_name}_main; + JNIEnv *env = jvpp_main.jenv; + jthrowable exc; +$err_handler + + if (CLIB_DEBUG > 1) + clib_warning ("Received ${c_name} event message"); + + 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_error_handler(msg): + err_handler = "" + for field in msg.fields: + if is_retval(field): + err_handler = _ERR_HANDLER_TEMPLATE.substitute(name=msg.name) + return err_handler + + +# Code fragment for checking result of the operation before sending request reply. +# Error checking is optional (some messages, e.g. detail messages do not have retval field). +_ERR_HANDLER_TEMPLATE = Template(""" + // for negative result don't send callback message but send error callback + if (mp->retval<0) { + call_on_error("${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; + }""") diff --git a/java/jvpp/gen/jvppgen/jni_type_handlers_gen.py b/java/jvpp/gen/jvppgen/jni_type_handlers_gen.py new file mode 100755 index 0000000..05c3e52 --- /dev/null +++ b/java/jvpp/gen/jvppgen/jni_type_handlers_gen.py @@ -0,0 +1,230 @@ +#!/usr/bin/env python2 +# +# Copyright (c) 2018 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 + +from jni_common_gen import generate_j2c_swap, generate_j2c_field_swap, generate_j2c_identifiers, generate_c2j_swap +from jvpp_model import Class, Enum, Union + + +def generate_type_handlers(model, logger): + """ + Generates host-to-net and net-to-host functions for all custom types defined in the VPP API + :param model: meta-model of VPP API used for jVPP generation. + :param logger: jVPP logger + """ + type_handlers = [] + for t in model.types: + #TODO(VPP-1186): move the logic to JNI generators + if isinstance(t, Class): + _generate_class(model, t, type_handlers) + elif isinstance(t, Enum): + _generate_enum(model, t, type_handlers) + elif isinstance(t, Union): + _generate_union(model, t, type_handlers) + else: + logger.debug("Skipping custom JNI type handler generation for %s", t) + + return "\n".join(type_handlers) + + +def _generate_class(model, t, type_handlers): + ref_name = t.java_name_lower + is_alias = t.name in model._aliases + + type_handlers.append(_TYPE_HOST_TO_NET_TEMPLATE.substitute( + c_name=t.name, + json_filename=model.json_api_files, + json_definition=t.doc, + type_reference_name=ref_name, + class_FQN=t.jni_name, + jni_identifiers=generate_j2c_identifiers(t, class_ref_name="%sClass" % ref_name, object_ref_name="_host"), + type_swap=generate_j2c_swap(t, struct_ref_name="_net", is_alias=is_alias) + )) + type_handlers.append(_TYPE_NET_TO_HOST_TEMPLATE.substitute( + c_name=t.name, + json_filename=model.json_api_files, + json_definition=t.doc, + type_reference_name=ref_name, + class_FQN=t.jni_name, + type_swap=generate_c2j_swap(t, object_ref_name="_host", struct_ref_name="_net", is_alias=is_alias) + )) + + +_TYPE_HOST_TO_NET_TEMPLATE = Template(""" +/** + * Host to network byte order conversion for ${c_name} type. + * Generated based on $json_filename: +$json_definition + */ +static inline void _host_to_net_${c_name}(JNIEnv * env, jobject _host, vl_api_${c_name}_t * _net) +{ + jclass ${type_reference_name}Class = (*env)->FindClass(env, "${class_FQN}"); +$jni_identifiers +$type_swap +}""") + +_TYPE_NET_TO_HOST_TEMPLATE = Template(""" +/** + * Network to host byte order conversion for ${c_name} type. + * Generated based on $json_filename: +$json_definition + */ +static inline void _net_to_host_${c_name}(JNIEnv * env, vl_api_${c_name}_t * _net, jobject _host) +{ + jclass ${type_reference_name}Class = (*env)->FindClass(env, "${class_FQN}"); +$type_swap +}""") + + +def _generate_enum(model, t, type_handlers): + value_type = t.value.type + type_handlers.append(_ENUM_NET_TO_HOST_TEMPLATE.substitute( + c_name=t.name, + json_filename=model.json_api_files, + json_definition=t.doc, + class_FQN=t.jni_name, + jni_signature=value_type.jni_signature, + jni_type=value_type.jni_type, + jni_accessor=value_type.jni_accessor, + swap=_generate_scalar_host_to_net_swap(t.value) + )) + + type_handlers.append(_ENUM_HOST_TO_NET_TEMPLATE.substitute( + c_name=t.name, + json_filename=model.json_api_files, + json_definition=t.doc, + class_FQN=t.jni_name, + jni_type=value_type.jni_type, + type_swap=_generate_scalar_net_to_host_swap(t.value) + )) + + +_ENUM_NET_TO_HOST_TEMPLATE = Template(""" +/** + * Host to network byte order conversion for ${c_name} enum. + * Generated based on $json_filename: +$json_definition + */ +static inline void _host_to_net_${c_name}(JNIEnv * env, jobject _host, vl_api_${c_name}_t * _net) +{ + jclass enumClass = (*env)->FindClass(env, "${class_FQN}"); + jmethodID getValueMethod = (*env)->GetMethodID(env, enumClass, "ordinal", "()I"); + ${jni_type} value = (*env)->CallIntMethod(env, _host, getValueMethod); + ${swap}; +}""") + +_ENUM_HOST_TO_NET_TEMPLATE = Template(""" +/** + * Network to host byte order conversion for ${c_name} type. + * Generated based on $json_filename: +$json_definition + */ +static inline ${jni_type} _net_to_host_${c_name}(vl_api_${c_name}_t _net) +{ + return (${jni_type}) $type_swap +}""") + + +def _generate_scalar_host_to_net_swap(field): + field_type = field.type + if field_type.is_swap_needed: + return field_type.get_host_to_net_function(field.java_name, "*_net") + else: + return "*_net = %s" % field.java_name + + +def _generate_scalar_net_to_host_swap(field): + field_type = field.type + if field_type.is_swap_needed: + return "%s((%s) _net);" % (field_type.net_to_host_function, field_type.name) + else: + return "_net" + + +def _generate_union(model, t, type_handlers): + type_handlers.append(_generate_union_host_to_net(model, t)) + type_handlers.append(_generate_union_net_to_host(model, t)) + + +def _generate_union_host_to_net(model, t): + swap = [] + for i, field in enumerate(t.fields): + field_type = field.type + is_alias = t.name in model._aliases + swap.append(_UNION_FIELD_HOST_TO_NET_TEMPLATE.substitute( + field_index=i, + java_name=field.java_name, + jni_signature=field_type.jni_signature, + jni_type=field_type.jni_type, + jni_accessor=field_type.jni_accessor, + swap=generate_j2c_field_swap(field, struct_ref_name="_net", is_alias=is_alias) + )) + + return _UNION_HOST_TO_NET_TEMPLATE.substitute( + c_name=t.name, + json_filename=model.json_api_files, + json_definition=t.doc, + class_FQN=t.jni_name, + swap="".join(swap) + ) + + +_UNION_FIELD_HOST_TO_NET_TEMPLATE = Template(""" + if (_activeMember == ${field_index}) { + jfieldID fieldId = (*env)->GetFieldID(env, _class, "${java_name}", "${jni_signature}"); + ${jni_type} ${java_name} = (*env)->Get${jni_accessor}Field(env, _host, fieldId); + ${swap} + }""") + +_UNION_HOST_TO_NET_TEMPLATE = Template(""" +/** + * Host to network byte order conversion for ${c_name} union. + * Generated based on $json_filename: +$json_definition + */ +static inline void _host_to_net_${c_name}(JNIEnv * env, jobject _host, vl_api_${c_name}_t * _net) +{ + jclass _class = (*env)->FindClass(env, "${class_FQN}"); + + jfieldID _activeMemberFieldId = (*env)->GetFieldID(env, _class, "_activeMember", "I"); + jint _activeMember = (*env)->GetIntField(env, _host, _activeMemberFieldId); +$swap +}""") + + +def _generate_union_net_to_host(model, t): + is_alias = t.name in model._aliases + return _UNION_NET_TO_HOST_TEMPLATE.substitute( + c_name=t.name, + json_filename=model.json_api_files, + json_definition=t.doc, + type_reference_name=t.java_name_lower, + class_FQN=t.jni_name, + swap=generate_c2j_swap(t, object_ref_name="_host", struct_ref_name="_net", is_alias=is_alias) + ) + + +_UNION_NET_TO_HOST_TEMPLATE = Template(""" +/** + * Network to host byte order conversion for ${c_name} union. + * Generated based on $json_filename: +$json_definition + */ +static inline void _net_to_host_${c_name}(JNIEnv * env, vl_api_${c_name}_t * _net, jobject _host) +{ + jclass ${type_reference_name}Class = (*env)->FindClass(env, "${class_FQN}"); +$swap +}""") diff --git a/java/jvpp/gen/jvppgen/jvpp_callback_facade_gen.py b/java/jvpp/gen/jvppgen/jvpp_callback_facade_gen.py new file mode 100644 index 0000000..ebc552b --- /dev/null +++ b/java/jvpp/gen/jvppgen/jvpp_callback_facade_gen.py @@ -0,0 +1,289 @@ +#!/usr/bin/env python2 +# +# Copyright (c) 2016,2018 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 + +from jvpp_model import is_control_ping, is_dump, is_request, is_event, is_control_ping_reply + + +def generate_callback_facade(work_dir, model, logger): + """ Generates callback facade """ + logger.debug("Generating JVpp callback facade for %s" % model.json_api_files) + _generate_ifc(work_dir, model), + _generate_impl(work_dir, model) + _generate_callback(work_dir, model) + + +def _generate_ifc(work_dir, model): + with open("%s/CallbackJVpp%s.java" % (work_dir, model.plugin_java_name), "w") as f: + f.write(_IFC_TEMPLATE.substitute( + plugin_package=model.plugin_package, + json_filename=model.json_api_files, + plugin_name=model.plugin_java_name, + methods=_generate_ifc_methods(model) + )) + +_IFC_TEMPLATE = Template(""" +package $plugin_package.callfacade; + +/** + * <p>Callback Java API representation of $plugin_package plugin. + * <br>It was generated by jvpp_callback_facade_gen.py based on $json_filename. + */ +public interface CallbackJVpp${plugin_name} extends io.fd.vpp.jvpp.notification.EventRegistryProvider, java.lang.AutoCloseable { + + // TODO add send + +$methods +} +""") + + +def _generate_ifc_methods(model): + plugin_package = model.plugin_package + methods = [] + for msg in model.messages: + if is_control_ping(msg): + # Skip control ping managed by jvpp registry. + continue + if not (is_dump(msg) or is_request(msg)): + # Skip replies and messages that do not not have replies (e.g events/counters). + continue + template = _IFC_NO_ARG_METHOD_TEMPLATE + if msg.has_fields: + template = _IFC_METHOD_TEMPLATE + methods.append(template.substitute( + name=msg.java_name_lower, + plugin_package=plugin_package, + request=msg.java_name_upper, + reply=msg.reply_java + )) + return "\n".join(methods) + +_IFC_NO_ARG_METHOD_TEMPLATE = Template( + """ void $name($plugin_package.callback.${reply}Callback callback) throws io.fd.vpp.jvpp.VppInvocationException;""") + +_IFC_METHOD_TEMPLATE = Template( + """ void $name($plugin_package.dto.$request request, $plugin_package.callback.${reply}Callback callback) throws io.fd.vpp.jvpp.VppInvocationException;""") + + +def _generate_impl(work_dir, model): + with open("%s/CallbackJVpp%sFacade.java" % (work_dir, model.plugin_java_name), "w") as f: + f.write(_IMPL_TEMPLATE.substitute( + plugin_package=model.plugin_package, + json_filename=model.json_api_files, + plugin_name=model.plugin_java_name, + methods=_generate_impl_methods(model) + )) + +_IMPL_TEMPLATE = Template(""" +package $plugin_package.callfacade; + +/** + * <p>Default implementation of Callback${plugin_name}JVpp interface. + * <br>It was generated by jvpp_callback_facade_gen.py based on $json_filename. + */ +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, io.fd.vpp.jvpp.callback.JVppCallback> callbacks; + private final $plugin_package.notification.${plugin_name}EventRegistryImpl eventRegistry = new $plugin_package.notification.${plugin_name}EventRegistryImpl(); + /** + * <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 io.fd.vpp.jvpp.JVpp instance + * + * @throws java.io.IOException in case instance cannot connect to JVPP + */ + public CallbackJVpp${plugin_name}Facade(final io.fd.vpp.jvpp.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, eventRegistry)); + } + + @Override + public $plugin_package.notification.${plugin_name}EventRegistry getEventRegistry() { + return eventRegistry; + } + + @Override + public void close() throws Exception { + jvpp.close(); + } + + // TODO add send() + +$methods +} +""") + + +def _generate_impl_methods(model): + plugin_package = model.plugin_package + methods = [] + for msg in model.messages: + if is_control_ping(msg): + # Skip control ping managed by jvpp registry. + continue + if not (is_dump(msg) or is_request(msg)): + # Skip replies and messages that do not not have replies (e.g events/counters). + continue + template = _IMPL_NO_ARG_METHOD_TEMPLATE + if msg.has_fields: + template = _IMPL_METHOD_TEMPLATE + methods.append(template.substitute( + name=msg.java_name_lower, + plugin_package=plugin_package, + request=msg.java_name_upper, + reply=msg.reply_java + )) + return "\n".join(methods) + +_IMPL_NO_ARG_METHOD_TEMPLATE = Template( + """ public final void $name($plugin_package.callback.${reply}Callback callback) throws io.fd.vpp.jvpp.VppInvocationException { + synchronized (callbacks) { + callbacks.put(jvpp.$name(), callback); + } + } +""") + +_IMPL_METHOD_TEMPLATE = Template(""" public final void $name($plugin_package.dto.$request request, $plugin_package.callback.${reply}Callback callback) throws io.fd.vpp.jvpp.VppInvocationException { + synchronized (callbacks) { + callbacks.put(jvpp.$name(request), callback); + } + } +""") + + +def _generate_callback(work_dir, model): + with open("%s/CallbackJVpp%sFacadeCallback.java" % (work_dir, model.plugin_java_name), "w") as f: + f.write(_CALLBACK_TEMPLATE.substitute( + plugin_package=model.plugin_package, + json_filename=model.json_api_files, + plugin_name=model.plugin_java_name, + methods=_generate_callback_methods(model) + )) + +_CALLBACK_TEMPLATE = Template(""" +package $plugin_package.callfacade; + +/** + * <p>Implementation of JVppGlobalCallback interface for Java Callback API. + * <br>It was generated by jvpp_callback_facade_gen.py based on $json_filename. + */ +public final class CallbackJVpp${plugin_name}FacadeCallback implements $plugin_package.callback.JVpp${plugin_name}GlobalCallback { + + private final java.util.Map<Integer, io.fd.vpp.jvpp.callback.JVppCallback> requests; + private final $plugin_package.notification.Global${plugin_name}EventCallback eventCallback; + 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, io.fd.vpp.jvpp.callback.JVppCallback> requestMap, + final $plugin_package.notification.Global${plugin_name}EventCallback eventCallback) { + this.requests = requestMap; + this.eventCallback = eventCallback; + } + + @Override + public void onError(io.fd.vpp.jvpp.VppCallbackException reply) { + + io.fd.vpp.jvpp.callback.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, java.lang.String.format("Callback: %s failed while handling exception: %s", failedCall, reply), ex); + } + } + } + + @Override + @SuppressWarnings("unchecked") + public void onControlPingReply(final io.fd.vpp.jvpp.dto.ControlPingReply reply) { + + io.fd.vpp.jvpp.callback.ControlPingCallback callback; + final int replyId = reply.context; + synchronized(requests) { + callback = (io.fd.vpp.jvpp.callback.ControlPingCallback) requests.remove(replyId); + } + + if(callback != null) { + callback.onControlPingReply(reply); + } + } + +$methods +} +""") + + +def _generate_callback_methods(model): + plugin_package = model.plugin_package + methods = [] + for msg in model.messages: + if is_dump(msg) or is_request(msg): + continue + if is_control_ping_reply(msg): + # Skip control ping managed by jvpp registry. + continue + + # Generate callbacks for all messages except for dumps and requests (handled by vpp, not client). + template = _CALLBACK_METHOD_TEMPLATE + if is_event(msg): + template = _CALLBACK_EVENT_METHOD_TEMPLATE + msg_name = msg.java_name_upper + methods.append(template.substitute( + message=msg_name, + callback="%sCallback" % msg_name, + plugin_package=plugin_package + )) + return "\n".join(methods) + +_CALLBACK_METHOD_TEMPLATE = Template(""" + @Override + @SuppressWarnings("unchecked") + public void on${message}(final $plugin_package.dto.${message} reply) { + + $plugin_package.callback.$callback callback; + final int replyId = reply.context; + if (LOG.isLoggable(java.util.logging.Level.FINE)) { + LOG.fine(java.lang.String.format("Received ${message} event message: %s", reply)); + } + synchronized(requests) { + callback = ($plugin_package.callback.$callback) requests.remove(replyId); + } + + if(callback != null) { + callback.on${message}(reply); + } + } +""") + +_CALLBACK_EVENT_METHOD_TEMPLATE = Template(""" + @Override + @SuppressWarnings("unchecked") + public void on${message}($plugin_package.dto.${message} notification) { + if (LOG.isLoggable(java.util.logging.Level.FINE)) { + LOG.fine(java.lang.String.format("Received ${message} event message: %s", notification)); + } + eventCallback.on${message}(notification); + } +""") diff --git a/java/jvpp/gen/jvppgen/jvpp_common_gen.py b/java/jvpp/gen/jvppgen/jvpp_common_gen.py new file mode 100755 index 0000000..499adbc --- /dev/null +++ b/java/jvpp/gen/jvppgen/jvpp_common_gen.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python2 +# +# Copyright (c) 2018 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 + +from jvpp_model import is_array + + +def generate_fields(fields, access_modifier="public"): + return "\n".join(_FIELD_TEMPLATE + .substitute(access_modifier=access_modifier, type=f.type.java_name_fqn, name=f.java_name) + for f in fields) + +_FIELD_TEMPLATE = Template(""" ${access_modifier} ${type} ${name};""") + + +def generate_hash_code(fields): + if len(fields) == 1 and is_array(fields[0]): + return _HASH_CODE_SINGLE_ARRAY_TEMPLATE.substitute(array_field=fields[0].java_name) + return _HASH_CODE_TEMPLATE.substitute(fields=", ".join(f.java_name for f in fields)) + +_HASH_CODE_TEMPLATE = Template(""" + @Override + @io.fd.vpp.jvpp.coverity.SuppressFBWarnings("UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD") + public int hashCode() { + return java.util.Objects.hash($fields); + }""") + +_HASH_CODE_SINGLE_ARRAY_TEMPLATE = Template(""" + @Override + @io.fd.vpp.jvpp.coverity.SuppressFBWarnings("UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD") + public int hashCode() { + return java.util.Arrays.hashCode($array_field); + }""") + + +def generate_equals(class_name, fields): + comparisons = [] + for f in fields: + if is_array(f): + comparisons.append(_EQUALS_ARRAY_FIELD_TEMPLATE.substitute(field_name=f.java_name)) + else: + comparisons.append(_EQUALS_FIELD_TEMPLATE.substitute(field_name=f.java_name)) + + if comparisons: + comparisons.insert(0, _EQUALS_OTHER_TEMPLATE.substitute(cls_name=class_name)) + return _EQUALS_TEMPLATE.substitute(comparisons="\n".join(comparisons)) + +_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; + }""") + +_EQUALS_OTHER_TEMPLATE = Template(""" + final $cls_name other = ($cls_name) o; +""") + +_EQUALS_FIELD_TEMPLATE = Template(""" if (!java.util.Objects.equals(this.$field_name, other.$field_name)) { + return false; + }""") + +_EQUALS_ARRAY_FIELD_TEMPLATE = Template(""" if (!java.util.Arrays.equals(this.$field_name, other.$field_name)) { + return false; + }""") + + +def generate_to_string(class_name, fields): + to_string = [] + for f in fields: + if is_array(f): + to_string.append(_TO_STRING_ARRAY_FIELD_TEMPLATE.substitute(field_name=f.java_name)) + else: + to_string.append(_TO_STRING_FIELD_TEMPLATE.substitute(field_name=f.java_name)) + + to_string_fields = " \"}\";" + if to_string: + to_string_fields = " + \", \" +\n".join(to_string) + " + \"}\";" + + return _TO_STRING_TEMPLATE.substitute( + class_name=class_name, + to_string_fields=to_string_fields + ) + +_TO_STRING_TEMPLATE = Template(""" + @Override + public java.lang.String toString() { + return "$class_name{" + +$to_string_fields + }""") + +_TO_STRING_FIELD_TEMPLATE = Template(""" \"$field_name=\" + $field_name""") + +_TO_STRING_ARRAY_FIELD_TEMPLATE = Template( + """ \"$field_name=\" + java.util.Arrays.toString($field_name)""") diff --git a/java/jvpp/gen/jvppgen/jvpp_future_facade_gen.py b/java/jvpp/gen/jvppgen/jvpp_future_facade_gen.py new file mode 100644 index 0000000..3da367a --- /dev/null +++ b/java/jvpp/gen/jvppgen/jvpp_future_facade_gen.py @@ -0,0 +1,338 @@ +#!/usr/bin/env python2 +# +# Copyright (c) 2016,2018 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 + +from jvpp_model import is_control_ping, is_control_ping_reply, is_dump, is_request, is_details, is_reply, is_event + + +def generate_future_facade(work_dir, model, logger): + logger.debug("Generating JVpp future facade for %s" % model.json_api_files) + _generate_future_jvpp(work_dir, model), + _generate_future_jvpp_facade(work_dir, model) + _generate_future_jvpp_callback(work_dir, model) + + +def _generate_future_jvpp(work_dir, model): + with open("%s/FutureJVpp%s.java" % (work_dir, model.plugin_java_name), "w") as f: + f.write(_FUTURE_JVPP_TEMPLATE.substitute( + plugin_package=model.plugin_package, + json_filename=model.json_api_files, + plugin_name=model.plugin_java_name, + methods=_generate_future_jvpp_methods(model) + )) + +_FUTURE_JVPP_TEMPLATE = Template(''' +package $plugin_package.future; + +/** + * <p>Async facade extension adding specific methods for each request invocation + * <br>It was generated by jvpp_future_facade_gen.py based on $json_filename. + */ +public interface FutureJVpp${plugin_name} extends io.fd.vpp.jvpp.future.FutureJVppInvoker { +$methods + + @Override + public $plugin_package.notification.${plugin_name}EventRegistry getEventRegistry(); + +} +''') + + +def _generate_future_jvpp_methods(model): + methods = [] + for msg in model.messages: + if is_control_ping(msg) or is_control_ping_reply(msg): + # Skip control_ping managed by jvpp registry. + continue + reply_name = None + if is_request(msg): + reply_name = msg.reply_java + elif is_dump(msg): + # use reply dump wrappers + reply_name = "%sReplyDump" % msg.reply_java + else: + continue + + methods.append(_FUTURE_JVPP_METHOD_TEMPLATE.substitute( + plugin_package=model.plugin_package, + method_name=msg.java_name_lower, + reply_name=reply_name, + request_name=msg.java_name_upper + )) + return "".join(methods) + +_FUTURE_JVPP_METHOD_TEMPLATE = Template(''' + java.util.concurrent.CompletionStage<${plugin_package}.dto.${reply_name}> ${method_name}(${plugin_package}.dto.${request_name} request); +''') + + +def _generate_future_jvpp_facade(work_dir, model): + with open("%s/FutureJVpp%sFacade.java" % (work_dir, model.plugin_java_name), "w") as f: + f.write(_FUTURE_JVPP_FACADE_TEMPLATE.substitute( + plugin_package=model.plugin_package, + json_filename=model.json_api_files, + plugin_name=model.plugin_java_name, + methods=_generate_future_jvpp_facade_methods(model) + )) + +_FUTURE_JVPP_FACADE_TEMPLATE = Template(''' +package $plugin_package.future; + +/** + * <p>Implementation of FutureJVpp based on AbstractFutureJVppInvoker + * <br>It was generated by jvpp_future_facade_gen.py based on $json_filename. + */ +public class FutureJVpp${plugin_name}Facade extends io.fd.vpp.jvpp.future.AbstractFutureJVppInvoker implements FutureJVpp${plugin_name} { + + private final $plugin_package.notification.${plugin_name}EventRegistryImpl eventRegistry = new $plugin_package.notification.${plugin_name}EventRegistryImpl(); + + /** + * <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 io.fd.vpp.jvpp.JVpp instance + * + * @throws java.io.IOException in case instance cannot connect to JVPP + */ + public FutureJVpp${plugin_name}Facade(final io.fd.vpp.jvpp.JVppRegistry registry, final io.fd.vpp.jvpp.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(), eventRegistry)); + } + + @Override + public $plugin_package.notification.${plugin_name}EventRegistry getEventRegistry() { + return eventRegistry; + } + +$methods +} +''') + + +def _generate_future_jvpp_facade_methods(model): + methods = [] + for msg in model.messages: + if is_control_ping(msg) or is_control_ping_reply(msg): + # Skip control_ping managed by jvpp registry. + continue + template = None + if is_request(msg): + template = _FUTURE_JVPP_FACADE_REQUEST_TEMPLATE + elif is_dump(msg): + template = _FUTURE_JVPP_FACADE_DUMP_TEMPLATE + else: + continue + + methods.append(template.substitute( + plugin_package=model.plugin_package, + method_name=msg.java_name_lower, + reply_name=msg.reply_java, + request_name=msg.java_name_upper + )) + return "".join(methods) + +_FUTURE_JVPP_FACADE_REQUEST_TEMPLATE = Template(''' + @Override + public java.util.concurrent.CompletionStage<${plugin_package}.dto.${reply_name}> ${method_name}(${plugin_package}.dto.${request_name} request) { + return send(request); + } +''') + +_FUTURE_JVPP_FACADE_DUMP_TEMPLATE = Template(''' + @Override + public java.util.concurrent.CompletionStage<${plugin_package}.dto.${reply_name}ReplyDump> ${method_name}(${plugin_package}.dto.${request_name} request) { + return send(request, new ${plugin_package}.dto.${reply_name}ReplyDump()); + } +''') + + +def _generate_future_jvpp_callback(work_dir, model): + with open("%s/FutureJVpp%sFacadeCallback.java" % (work_dir, model.plugin_java_name), "w") as f: + f.write(_FUTURE_JVPP_CALLBACK_TEMPLATE.substitute( + plugin_package=model.plugin_package, + json_filename=model.json_api_files, + plugin_name=model.plugin_java_name, + methods=_generate_future_jvpp_callback_methods(model) + )) + +_FUTURE_JVPP_CALLBACK_TEMPLATE = Template(""" +package $plugin_package.future; + +/** + * <p>Async facade callback setting values to future objects + * <br>It was generated by jvpp_future_facade_gen.py based on $json_filename. + */ +public final class FutureJVpp${plugin_name}FacadeCallback implements $plugin_package.callback.JVpp${plugin_name}GlobalCallback { + + private final java.util.Map<java.lang.Integer, java.util.concurrent.CompletableFuture<? extends io.fd.vpp.jvpp.dto.JVppReply<?>>> requests; + private final $plugin_package.notification.Global${plugin_name}EventCallback notificationCallback; + private static final java.util.logging.Logger LOG = java.util.logging.Logger.getLogger(FutureJVpp${plugin_name}FacadeCallback.class.getName()); + + public FutureJVpp${plugin_name}FacadeCallback( + final java.util.Map<java.lang.Integer, java.util.concurrent.CompletableFuture<? extends io.fd.vpp.jvpp.dto.JVppReply<?>>> requestMap, + final $plugin_package.notification.Global${plugin_name}EventCallback notificationCallback) { + this.requests = requestMap; + this.notificationCallback = notificationCallback; + } + + @Override + @SuppressWarnings("unchecked") + public void onError(io.fd.vpp.jvpp.VppCallbackException reply) { + final java.util.concurrent.CompletableFuture<io.fd.vpp.jvpp.dto.JVppReply<?>> completableFuture; + + synchronized(requests) { + completableFuture = (java.util.concurrent.CompletableFuture<io.fd.vpp.jvpp.dto.JVppReply<?>>) requests.get(reply.getCtxId()); + } + + if(completableFuture != null) { + completableFuture.completeExceptionally(reply); + + synchronized(requests) { + requests.remove(reply.getCtxId()); + } + } + } + + @Override + @SuppressWarnings("unchecked") + public void onControlPingReply(final io.fd.vpp.jvpp.dto.ControlPingReply reply) { + java.util.concurrent.CompletableFuture<io.fd.vpp.jvpp.dto.JVppReply<?>> completableFuture; + + final int replyId = reply.context; + synchronized(requests) { + completableFuture = (java.util.concurrent.CompletableFuture<io.fd.vpp.jvpp.dto.JVppReply<?>>) requests.get(replyId); + + if(completableFuture != null) { + // Finish dump call + if (completableFuture instanceof io.fd.vpp.jvpp.future.AbstractFutureJVppInvoker.CompletableDumpFuture) { + completableFuture.complete(((io.fd.vpp.jvpp.future.AbstractFutureJVppInvoker.CompletableDumpFuture) completableFuture).getReplyDump()); + // Remove future mapped to dump call context id + requests.remove(((io.fd.vpp.jvpp.future.AbstractFutureJVppInvoker.CompletableDumpFuture) completableFuture).getContextId()); + } else { + // reply to regular control ping, complete the future + completableFuture.complete(reply); + } + requests.remove(replyId); + } else { + // future not yet created by writer, create new future, complete it and put to map under ping id + completableFuture = new java.util.concurrent.CompletableFuture<>(); + completableFuture.complete(reply); + requests.put(replyId, completableFuture); + } + } + } + +$methods +} +""") + + +def _generate_future_jvpp_callback_methods(model): + methods = [] + for msg in model.messages: + if is_control_ping(msg) or is_control_ping_reply(msg): + # Skip control_ping managed by jvpp registry. + continue + if is_dump(msg) or is_request(msg): + continue + + # Generate callbacks for all messages except for dumps and requests (handled by vpp, not client). + template = None + request_dto = None + if is_details(msg): + template = _FUTURE_JVPP_FACADE_DETAILS_CALLBACK_TEMPLATE + request_dto = msg.request_java + elif is_reply(msg): + template = _FUTURE_JVPP_FACADE_REPLY_CALLBACK_TEMPLATE + request_dto = msg.request_java + elif is_event(msg): + template = _FUTURE_JVPP_FACADE_EVENT_CALLBACK_TEMPLATE + else: + raise TypeError("Unknown message type %s", msg) + + methods.append(template.substitute( + plugin_package=model.plugin_package, + callback_dto=msg.java_name_upper, + request_dto=request_dto, + callback_dto_field=msg.java_name_lower, + )) + return "".join(methods) + + +_FUTURE_JVPP_FACADE_DETAILS_CALLBACK_TEMPLATE = Template(""" + @Override + @SuppressWarnings("unchecked") + public void on$callback_dto(final $plugin_package.dto.$callback_dto reply) { + io.fd.vpp.jvpp.future.AbstractFutureJVppInvoker.CompletableDumpFuture<$plugin_package.dto.${callback_dto}ReplyDump> completableFuture; + final int replyId = reply.context; + if (LOG.isLoggable(java.util.logging.Level.FINE)) { + LOG.fine(java.lang.String.format("Received $callback_dto event message: %s", reply)); + } + synchronized(requests) { + completableFuture = (io.fd.vpp.jvpp.future.AbstractFutureJVppInvoker.CompletableDumpFuture<$plugin_package.dto.${callback_dto}ReplyDump>) requests.get(replyId); + + if(completableFuture == null) { + // reply received before writer created future, + // create new future, and put into map to notify sender that reply is already received, + // following details replies will add information to this future + completableFuture = new io.fd.vpp.jvpp.future.AbstractFutureJVppInvoker.CompletableDumpFuture<>(replyId, + new $plugin_package.dto.${callback_dto}ReplyDump()); + requests.put(replyId, completableFuture); + } + completableFuture.getReplyDump().$callback_dto_field.add(reply); + } + } +""") + +_FUTURE_JVPP_FACADE_REPLY_CALLBACK_TEMPLATE = Template(""" + @Override + @SuppressWarnings("unchecked") + public void on$callback_dto(final $plugin_package.dto.$callback_dto reply) { + java.util.concurrent.CompletableFuture<io.fd.vpp.jvpp.dto.JVppReply<$plugin_package.dto.$request_dto>> completableFuture; + final int replyId = reply.context; + if (LOG.isLoggable(java.util.logging.Level.FINE)) { + LOG.fine(java.lang.String.format("Received $callback_dto event message: %s", reply)); + } + synchronized(requests) { + completableFuture = + (java.util.concurrent.CompletableFuture<io.fd.vpp.jvpp.dto.JVppReply<$plugin_package.dto.$request_dto>>) requests.get(replyId); + + if(completableFuture != null) { + // received reply on request, complete future created by sender and remove it from map + completableFuture.complete(reply); + requests.remove(replyId); + } else { + // reply received before writer created future, + // create new future, complete it and put into map to + // notify sender that reply is already received + completableFuture = new java.util.concurrent.CompletableFuture<>(); + completableFuture.complete(reply); + requests.put(replyId, completableFuture); + } + } + } +""") + +_FUTURE_JVPP_FACADE_EVENT_CALLBACK_TEMPLATE = Template(""" + @Override + public void on$callback_dto($plugin_package.dto.$callback_dto notification) { + if (LOG.isLoggable(java.util.logging.Level.FINE)) { + LOG.fine(java.lang.String.format("Received $callback_dto event message: %s", notification)); + } + notificationCallback.on$callback_dto(notification); + } +""") diff --git a/java/jvpp/gen/jvppgen/jvpp_ifc_gen.py b/java/jvpp/gen/jvppgen/jvpp_ifc_gen.py new file mode 100755 index 0000000..e2b2922 --- /dev/null +++ b/java/jvpp/gen/jvppgen/jvpp_ifc_gen.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python2 +# +# Copyright (c) 2018 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 + +from jvpp_model import is_request, is_dump, is_event + + +def generate_java_ifc(work_dir, model, logger): + logger.debug("Generating JVpp interface for %s" % model.json_api_files) + messages = filter(_jvpp_ifc_filter, model.messages) + plugin_package = model.plugin_package + methods = [] + for msg in messages: + if msg.has_fields: + methods.append(_JVPP_IFC_METHOD_TEMPLATE.substitute( + name=msg.java_name_lower, + plugin_package=plugin_package, + type=msg.java_name_upper)) + else: + methods.append(_JVPP_IFC_NO_ARG_METHOD_TEMPLATE.substitute(name=msg.java_name_lower)) + + plugin_name = model.plugin_java_name + jvpp_interface = _JVPP_IFC_TEMPLATE.substitute( + plugin_package=plugin_package, + json_filename=model.json_api_files, + plugin_name=plugin_name, + methods="\n".join(methods) + ) + with open("%s/JVpp%s.java" % (work_dir, plugin_name), "w") as f: + f.write(jvpp_interface) + + +def _jvpp_ifc_filter(msg): + return is_request(msg) or is_dump(msg) or is_event(msg) + + +_JVPP_IFC_METHOD_TEMPLATE = Template( + """ int $name($plugin_package.dto.$type request) throws io.fd.vpp.jvpp.VppInvocationException;""") + +_JVPP_IFC_NO_ARG_METHOD_TEMPLATE = Template(""" int $name() throws io.fd.vpp.jvpp.VppInvocationException;""") + +_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 $json_filename. + * <br>(python representation of api file generated by vppapigen) + */ +public interface JVpp${plugin_name} extends io.fd.vpp.jvpp.JVpp { + /** + * Generic dispatch method for sending requests to VPP + * + * @throws io.fd.vpp.jvpp.VppInvocationException if send request had failed + */ + int send(io.fd.vpp.jvpp.dto.JVppRequest request) throws io.fd.vpp.jvpp.VppInvocationException; +$methods +} +""") diff --git a/java/jvpp/gen/jvppgen/jvpp_impl_gen.py b/java/jvpp/gen/jvppgen/jvpp_impl_gen.py new file mode 100755 index 0000000..376952b --- /dev/null +++ b/java/jvpp/gen/jvppgen/jvpp_impl_gen.py @@ -0,0 +1,173 @@ +#!/usr/bin/env python2 +# +# Copyright (c) 2016,2018 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 + +from jvpp_model import is_request, is_dump, is_event + + +def generate_java_impl(work_dir, model, logger): + logger.debug("Generating JVpp implementation for %s" % model.json_api_files) + messages = filter(_jvpp_impl_filter, model.messages) + plugin_package = model.plugin_package + methods = [] + for msg in messages: + if msg.has_fields: + methods.append(_JVPP_IMPL_METHOD_TEMPLATE.substitute( + name=msg.java_name_lower, + plugin_package=plugin_package, + type=msg.java_name_upper)) + else: + methods.append(_JVPP_IMPL_NO_ARG_METHOD_TEMPLATE.substitute( + name=msg.java_name_lower, + type=msg.java_name_upper)) + + plugin_name = model.plugin_java_name + jvpp_impl = _JVPP_IMPL_TEMPLATE.substitute( + plugin_package=plugin_package, + json_filename=model.json_api_files, + plugin_name=model.plugin_java_name, + plugin_name_underscore=model.plugin_name, + methods="\n".join(methods)) + + with open("%s/JVpp%sImpl.java" % (work_dir, plugin_name), "w") as f: + f.write(jvpp_impl) + + +def _jvpp_impl_filter(msg): + return is_request(msg) or is_dump(msg) or is_event(msg) + + +_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 java.util.logging.Level; +import io.fd.vpp.jvpp.callback.JVppCallback; +import io.fd.vpp.jvpp.VppConnection; +import io.fd.vpp.jvpp.JVppRegistry; + +/** + * <p>Default implementation of JVpp interface. + * <br>It was generated by jvpp_impl_gen.py based on $json_filename. + * <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 java.lang.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(io.fd.vpp.jvpp.dto.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 +} +""") + +_JVPP_IMPL_METHOD_TEMPLATE = Template(""" + private static native int ${name}0($plugin_package.dto.$type request); + public final int $name($plugin_package.dto.$type request) throws io.fd.vpp.jvpp.VppInvocationException { + java.util.Objects.requireNonNull(request, "Null request object"); + connection.checkActive(); + if (LOG.isLoggable(Level.FINE)) { + LOG.fine(java.lang.String.format("Sending $type event message: %s", request)); + } + int result=${name}0(request); + if (result<0){ + throw new io.fd.vpp.jvpp.VppInvocationException("${name}", result); + } + return result; + }""") + +_JVPP_IMPL_NO_ARG_METHOD_TEMPLATE = Template(""" + private static native int ${name}0() throws io.fd.vpp.jvpp.VppInvocationException; + public final int $name() throws io.fd.vpp.jvpp.VppInvocationException { + connection.checkActive(); + LOG.fine("Sending $type event message"); + int result=${name}0(); + if(result<0){ + throw new io.fd.vpp.jvpp.VppInvocationException("${name}", result); + } + return result; + }""") diff --git a/java/jvpp/gen/jvppgen/jvpp_model.py b/java/jvpp/gen/jvppgen/jvpp_model.py new file mode 100755 index 0000000..9a3204e --- /dev/null +++ b/java/jvpp/gen/jvppgen/jvpp_model.py @@ -0,0 +1,624 @@ +#!/usr/bin/env python2 +# +# Copyright (c) 2018 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 json +import pprint +from collections import OrderedDict + +import binascii + +BASE_PACKAGE = "io.fd.vpp.jvpp" + + +class ParseException(Exception): + pass + + +class Type(object): + def __init__(self, name, java_name, java_name_fqn, jni_signature, jni_type, jni_accessor, + host_to_net_function, net_to_host_function): + """ + Initializes Type class. + + :param name: name of type as defined in .api file, e.g. u8, u32[] or mac_entry + :param java_name: corresponding java name, e.g. byte, int[] or MacEntry + :param java_name_fqn: fully qualified java name, e.g. io.fd.vpp.jvpp.core.types.MacEntry + :param jni_signature: JNI Type signature, e.g. B, [I or Lio.fd.vpp.jvpp.core.types.MacEntry; + See https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/types.html#type_signatures + :param jni_type: JNI reference type, e.g. jbyte jintArray, jobject + See https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/types.html#reference_types + :param jni_accessor: Java type do by used in Get<type>Field, Set<type>Field and other functions. + See https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#accessing_fields_of_objects + :param host_to_net_function: name of function host to net byte order swap function + :param net_to_host_function: name of function net to host byte order swap function + """ + self.name = name + self.java_name = java_name + + # Java generation specific properties, TODO(VPP-1186): move to Java specific subclass + self.java_name_fqn = java_name_fqn + + # JNI generation specific properties, TODO(VPP-1186): move to JNI specific subclass + self.jni_signature = jni_signature + + # Native type, see: + # https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/types.html#primitive_types + # and + # https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/types.html#reference_types + self.jni_type = jni_type + + # Java type do by used in Get<type>Field, Set<type>Field and other functions, see: + # https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#accessing_fields_of_objects + self.jni_accessor = jni_accessor + + self.host_to_net_function = host_to_net_function + self.net_to_host_function = net_to_host_function + self.is_swap_needed = host_to_net_function and net_to_host_function + + +class SimpleType(Type): + def __init__(self, name, java_name, jni_signature, jni_type, jni_accessor, + host_to_net_function=None, net_to_host_function=None): + super(SimpleType, self).__init__( + name=name, + java_name=java_name, + java_name_fqn=java_name, + jni_signature=jni_signature, + jni_type=jni_type, + jni_accessor=jni_accessor, + host_to_net_function=host_to_net_function, + net_to_host_function=net_to_host_function + ) + self.vpp_name = name + + def get_host_to_net_function(self, host_ref_name, net_ref_name): + return "%s = %s(%s)" % (net_ref_name, self.host_to_net_function, host_ref_name) + + def __str__(self): + return "SimpleType{name:%s, java_name:%s}" % (self.name, self.java_name) + + +# TODO(VPP-1187): add array host to net functions to reduce number of members and simplify JNI generation +class Array(Type): + def __init__(self, base_type, name=None): + if name is None: + name = base_type.name + _ARRAY_SUFFIX + super(Array, self).__init__( + name=name, + java_name=base_type.java_name + _ARRAY_SUFFIX, + java_name_fqn=base_type.java_name_fqn + _ARRAY_SUFFIX, + jni_signature="[%s" % base_type.jni_signature, + jni_type="%sArray" % base_type.jni_type, + jni_accessor="Object", + host_to_net_function=base_type.host_to_net_function, + net_to_host_function=base_type.net_to_host_function + ) + self.base_type = base_type + + def get_host_to_net_function(self, host_ref_name, net_ref_name): + return self.base_type.get_host_to_net_function(host_ref_name, net_ref_name) + + def __str__(self): + return "Array{name:%s, java_name:%s}" % (self.name, self.java_name) + + +class Enum(Type): + def __init__(self, name, value, constants, definition, plugin_name): + _java_name = _underscore_to_camelcase_upper(name) + + super(Enum, self).__init__( + name=name, + java_name=_java_name, + java_name_fqn="io.fd.vpp.jvpp.%s.types.%s" % (plugin_name, _java_name), + jni_signature="Lio/fd/vpp/jvpp/%s/types/%s;" % (plugin_name, _java_name), + jni_type="jobject", + jni_accessor="Object", + host_to_net_function="_host_to_net_%s" % name, + net_to_host_function="_net_to_host_%s" % name + ) + + self.value = value + self.constants = constants + self.doc = _message_to_javadoc(definition) + self.java_name_lower = _underscore_to_camelcase_lower(name) + self.vpp_name = "%s%s%s" % (_VPP_TYPE_PREFIX, name, _VPP_TYPE_SUFFIX) + # Fully qualified class name used by FindClass function, see: + # https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#FindClass + self.jni_name = "io/fd/vpp/jvpp/%s/types/%s" % (plugin_name, _java_name) + + def get_host_to_net_function(self, host_ref_name, net_ref_name): + return "_host_to_net_%s(env, %s, &(%s))" % (self.name, host_ref_name, net_ref_name) + + +class Class(Type): + def __init__(self, name, crc, fields, definition, plugin_name): + _java_name = _underscore_to_camelcase_upper(name) + + super(Class, self).__init__( + name=name, + java_name=_java_name, + java_name_fqn="io.fd.vpp.jvpp.%s.types.%s" % (plugin_name, _java_name), + jni_signature="Lio/fd/vpp/jvpp/%s/types/%s;" % (plugin_name, _java_name), + jni_type="jobject", + jni_accessor="Object", + host_to_net_function="_host_to_net_%s" % name, + net_to_host_function="_net_to_host_%s" % name + ) + + self.crc = crc + self.fields = fields + self.doc = _message_to_javadoc(definition) + self.java_name_lower = _underscore_to_camelcase_lower(name) + self.vpp_name = "%s%s%s" % (_VPP_TYPE_PREFIX, name, _VPP_TYPE_SUFFIX) + # Fully qualified class name used by FindClass function, see: + # https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#FindClass + self.jni_name = "io/fd/vpp/jvpp/%s/types/%s" % (plugin_name, _java_name) + + def get_host_to_net_function(self, host_ref_name, net_ref_name): + return "_host_to_net_%s(env, %s, &(%s))" % (self.name, host_ref_name, net_ref_name) + + +class Union(Type): + def __init__(self, name, crc, fields, definition, plugin_name): + _java_name = _underscore_to_camelcase_upper(name) + + super(Union, self).__init__( + name=name, + java_name=_java_name, + java_name_fqn="io.fd.vpp.jvpp.%s.types.%s" % (plugin_name, _java_name), + jni_signature="Lio/fd/vpp/jvpp/%s/types/%s;" % (plugin_name, _java_name), + jni_type="jobject", + jni_accessor="Object", + host_to_net_function="_host_to_net_%s" % name, + net_to_host_function="_net_to_host_%s" % name + ) + + self.crc = crc + self.fields = fields + self.doc = _message_to_javadoc(definition) + self.java_name_lower = _underscore_to_camelcase_lower(name) + self.vpp_name = "%s%s%s" % (_VPP_TYPE_PREFIX, name, _VPP_TYPE_SUFFIX) + # Fully qualified class name used by FindClass function, see: + # https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#FindClass + self.jni_name = "io/fd/vpp/jvpp/%s/types/%s" % (plugin_name, _java_name) + + def get_host_to_net_function(self, host_ref_name, net_ref_name): + return "_host_to_net_%s(env, %s, &(%s))" % (self.name, host_ref_name, net_ref_name) + + +class Field(object): + def __init__(self, name, field_type, array_len=None, array_len_field=None): + self.name = name + self.java_name = _underscore_to_camelcase_lower(name) + self.java_name_upper = _underscore_to_camelcase_upper(name) + self.type = field_type + self.array_len = array_len + self.array_len_field = array_len_field + + def __str__(self): + return "Field{name:%s, java_name:%s, type:%s}" % (self.name, self.java_name, self.type) + + +class Message(object): + def __init__(self, name, crc, fields, definition): + self.name = name + self.java_name_upper = _underscore_to_camelcase_upper(name) + self.java_name_lower = _underscore_to_camelcase_lower(name) + self.crc = crc[2:] + self.fields = fields + self.has_fields = fields != [] + self.doc = _message_to_javadoc(definition) + + def __str__(self): + return "Message{name:%s, java_name:%s, crc:%s, fields:%s}" % ( + self.name, self.java_name_upper, self.crc, self.fields) + + +class Event(Message): + def __init__(self, name, crc, fields, definition): + super(Event, self).__init__(name, crc, fields, definition) + + +class Request(Message): + def __init__(self, name, reply, crc, fields, definition): + super(Request, self).__init__(name, crc, fields, definition) + self.reply = reply + self.reply_java = _underscore_to_camelcase_upper(reply) + + def __str__(self): + return "Request{name:%s, reply:%s, crc:%s, fields:%s}" % (self.name, self.reply, self.crc, self.fields) + + +class Reply(Message): + def __init__(self, name, request, crc, fields, definition): + super(Reply, self).__init__(name, crc, fields, definition) + self.request = request + self.request_java = _underscore_to_camelcase_upper(request) + + def __str__(self): + return "Reply{name:%s, request:%s, crc:%s, fields:%s}" % (self.name, self.request, self.crc, self.fields) + + +class Dump(Message): + def __init__(self, name, details, crc, fields, definition): + super(Dump, self).__init__(name, crc, fields, definition) + self.details = details + self.reply_java = _underscore_to_camelcase_upper(details) + + def __str__(self): + return "Dump{name:%s, details:%s, crc:%s, fields:%s}" % (self.name, self.details, self.crc, self.fields) + + +class Details(Message): + def __init__(self, name, dump, crc, fields, definition): + super(Details, self).__init__(name, crc, fields, definition) + self.dump = dump + self.request_java = _underscore_to_camelcase_upper(dump) + + def __str__(self): + return "Details{name:%s, dump:%s, crc:%s, fields:%s}" % (self.name, self.dump, self.crc, self.fields) + + +def is_retval(field): + return field.name == u'retval' + + +def is_array(field): + return field.array_len is not None + + +def is_request(msg): + return hasattr(msg, 'reply') + + +def is_reply(msg): + return hasattr(msg, 'request') + + +def is_dump(msg): + return hasattr(msg, 'details') + + +def is_details(msg): + return hasattr(msg, 'dump') + + +def is_event(msg): + return isinstance(msg, Event) + + +def is_control_ping(msg): + return msg.name == u'control_ping' + + +def is_control_ping_reply(msg): + return msg.name == u'control_ping_reply' + + +def crc(block): + s = str(block).encode() + return binascii.crc32(s) & 0xffffffff + + +class JVppModel(object): + def __init__(self, logger, json_api_files, plugin_name): + self.logger = logger + # TODO(VPP-1188): provide json_file_by_definition map to improve javadoc + self.json_api_files = json_api_files + self.plugin_package = BASE_PACKAGE + "." + plugin_name + self.plugin_name = plugin_name + self.plugin_java_name = _underscore_to_camelcase_upper(plugin_name) + self._load_json_files(json_api_files) + self._parse_services() + self._parse_messages() + self._validate_messages() + + def _load_json_files(self, json_api_files): + types = {} + self._messages = [] + self._services = {} + self._aliases = {} + for file_name in json_api_files: + with open(file_name) as f: + j = json.load(f) + types.update({d[0]: {'type': 'enum', 'data': d} for d in j['enums']}) + types.update({d[0]: {'type': 'type', 'data': d} for d in j['types']}) + types.update({d[0]: {'type': 'union', 'data': d} for d in j['unions']}) + self._messages.extend(j['messages']) + self._services.update(j['services']) + self._aliases.update(j['aliases']) + + self._parse_types(types) + + def _parse_aliases(self, types): + + # model aliases + for alias_name in self._aliases: + alias = self._aliases[alias_name] + alias_type = {"type": "type"} + java_name_lower = _underscore_to_camelcase_lower(alias_name) + vpp_type = alias["type"] + crc_value = '0x%08x' % crc(alias_name) + if "length" in alias: + length = alias["length"] + alias_type["data"] = [ + alias_name, + [ + vpp_type, + java_name_lower, + length + ], + { + "crc": crc_value + } + ] + else: + alias_type["data"] = [ + alias_name, + [ + vpp_type, + java_name_lower + ], + { + "crc": crc_value + } + ] + + types[alias_name] = alias_type + + def _parse_types(self, types): + self._parse_simple_types() + self._parse_aliases(types) + i = 0 + while True: + unresolved = {} + for name, value in types.items(): + if name in self._types_by_name: + continue + + type = value['type'] + data = value['data'][1:] + try: + if type == 'enum': + type = self._parse_enum(name, data) + elif type == 'union': + type = self._parse_union(name, data) + elif type == 'type': + type = self._parse_type(name, data) + else: + self.logger.warning("Unsupported type %s. Ignoring...", type) + continue + + self._types_by_name[name] = type + self._types_by_name[name + _ARRAY_SUFFIX] = Array(type) + except ParseException as e: + self.logger.debug("Failed to parse %s type in iteration %s: %s.", name, i, e) + unresolved[name] = value + if len(unresolved) == 0: + break + if i > 3: + raise ParseException('Unresolved type definitions {}' + .format(unresolved)) + types = unresolved + i += 1 + + self.types = self._types_by_name.values() + + def _parse_simple_types(self): + # Mapping according to: + # http://docs.oracle.com/javase/7/do+'[]'cs/technotes/guides/jni/spec/types.html + # and + # https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#Get_type_Field_routines + # + # Unsigned types are converted to signed java types that have the same size. + # It is the API user responsibility to interpret them correctly. + + self._types_by_name = OrderedDict({ + 'u8': SimpleType('u8', 'byte', 'B', 'jbyte', 'Byte'), + 'i8': SimpleType('i8', 'byte', 'B', 'jbyte', 'Byte'), + 'u16': SimpleType('u16', 'short', 'S', 'jshort', 'Short', + host_to_net_function='clib_host_to_net_u16', + net_to_host_function='clib_net_to_host_u16'), + 'i16': SimpleType('i16', 'short', 'S', 'jshort', 'Short', + host_to_net_function='clib_host_to_net_i16', + net_to_host_function='clib_net_to_host_i16'), + 'u32': SimpleType('u32', 'int', 'I', 'jint', 'Int', + host_to_net_function='clib_host_to_net_u32', + net_to_host_function='clib_net_to_host_u32'), + 'i32': SimpleType('i32', 'int', 'I', 'jint', 'Int', + host_to_net_function='clib_host_to_net_i32', + net_to_host_function='clib_net_to_host_i32'), + 'u64': SimpleType('u64', 'long', 'J', 'jlong', 'Long', + host_to_net_function='clib_host_to_net_u64', + net_to_host_function='clib_net_to_host_u64'), + 'i64': SimpleType('i64', 'long', 'J', 'jlong', 'Long', + host_to_net_function='clib_host_to_net_i64', + net_to_host_function='clib_net_to_host_i64'), + 'f64': SimpleType('f64', 'double', 'D', 'jdouble', 'Double'), + 'string': SimpleType('string', 'String', 'Ljava/lang/String;', 'jstring', 'Object', + host_to_net_function='_host_to_net_string', + net_to_host_function='_net_to_host_string') + }) + + for n, t in self._types_by_name.items(): + self._types_by_name[n + _ARRAY_SUFFIX] = Array(t) + + def _parse_enum(self, name, definition): + self.logger.debug("Parsing enum %s: %s", name, definition) + constants = [] + type_name = None + for item in definition: + if type(item) is dict and 'enumtype' in item: + type_name = item['enumtype'] + continue + constants.append({'name': item[0], 'value': item[1]}) + if not type_name: + raise ParseException("'enumtype' was not defined for %s" % definition) + return Enum(name, Field('value', self._types_by_name[type_name]), constants, definition, self.plugin_name) + + def _parse_union(self, name, definition): + self.logger.debug("Parsing union %s: %s", name, definition) + crc, fields = self._parse_fields(definition) + return Union(name, crc, fields, definition, self.plugin_name) + + def _parse_type(self, name, definition): + self.logger.debug("Parsing type %s: %s", name, definition) + crc, fields = self._parse_fields(definition) + return Class(name, crc, fields, definition, self.plugin_name) + + def _parse_services(self): + self._dumps_by_details = {} + self._requests_by_reply = {} + for name, service in self._services.iteritems(): + if _is_stream(service): + self._dumps_by_details[service['reply']] = name + else: + self._requests_by_reply[service['reply']] = name + + def _parse_messages(self): + # Preserve ordering from JSON file to make debugging easier. + self._messages_by_name = OrderedDict() + for msg in self._messages: + try: + name = msg[0] + definition = msg[1:] + self._messages_by_name[name] = self._parse_message(name, definition) + except ParseException as e: + self.logger.warning("Failed to parse message %s: %s. Skipping message.", name, e) + + def _parse_message(self, name, definition): + self.logger.debug("Parsing message %s: %s", name, definition) + crc, fields = self._parse_fields(definition) + if name in self._services: + service = self._services[name] + reply = service['reply'] + if _is_stream(service): + return Dump(name, reply, crc, filter(_is_request_field, fields), definition) + if reply: + return Request(name, reply, crc, filter(_is_request_field, fields), definition) + else: + return Event(name, crc, filter(_is_request_field, fields), definition) + elif name in self._requests_by_reply: + return Reply(name, self._requests_by_reply[name], crc, filter(_is_reply_field, fields), definition) + elif name in self._dumps_by_details: + return Details(name, self._dumps_by_details[name], crc, filter(_is_reply_field, fields), definition) + else: + # TODO: some messages like combined_counters are not visible in the services. + # Throw exception instead (requires fixing vppagigen). + return Event(name, crc, filter(_is_request_field, fields), definition) + + def _parse_fields(self, definition): + crc = None + fields = [] + for item in definition: + if type(item) == dict and 'crc' in item: + crc = item['crc'] + else: + fields.append(self._parse_field(item, fields)) + if not crc: + raise ParseException("CRC was not defined for %s" % definition) + return crc, fields + + def _parse_field(self, field, fields): + type_name = _extract_type_name(field[0]) + + if type_name in self._types_by_name: + if len(field) > 2: + # Array field + array_len_field = None + if len(field) == 4: + for f in fields: + if f.name == field[3]: + array_len_field = f + if not array_len_field: + raise ParseException("Could not find field %s declared as length of array %s", + field[3], field[1]) + return Field(field[1], self._types_by_name[type_name + _ARRAY_SUFFIX], field[2], array_len_field) + else: + return Field(field[1], self._types_by_name[type_name]) + else: + raise ParseException("Unknown field type %s" % field) + + def _validate_messages(self): + """ + In case if message A is known to be reply for message B, and message B was not correctly parsed, + remove message A from the set of all messages. + """ + to_be_removed = [] + messages = self._messages_by_name + for name, msg in messages.iteritems(): + if (is_request(msg) and msg.reply not in messages) \ + or (is_reply(msg) and msg.request not in messages) \ + or (is_dump(msg) and msg.details not in messages) \ + or (is_details(msg) and msg.dump not in messages): + to_be_removed.append(name) + + for name in to_be_removed: + del messages[name] + + self.messages = self._messages_by_name.values() + + +_ARRAY_SUFFIX = '[]' + + +def _underscore_to_camelcase_upper(name): + return name.title().replace("_", "") + + +def _underscore_to_camelcase_lower(name): + name = name.title().replace("_", "") + return name[0].lower() + name[1:] + + +def _message_to_javadoc(message_definition): + """ Converts JSON message definition to javadoc """ + formatted_message = pprint.pformat(message_definition, indent=4, width=120, depth=None) + return " * " + formatted_message.replace("\n", "\n * ") + + +def _is_stream(service): + """ + Checks if service represents stream, e.g.: + "ip_address_dump": { + "reply": "ip_address_details", + "stream": true + } + :param service: JSON definition of service + :return: value assigned to "stream" or None + """ + return "stream" in service + + +def _extract_type_name(name): + if name.startswith(_VPP_TYPE_PREFIX) and name.endswith(_VPP_TYPE_SUFFIX): + return name[len(_VPP_TYPE_PREFIX): - len(_VPP_TYPE_SUFFIX)] + return name + +_VPP_TYPE_PREFIX = "vl_api_" + +_VPP_TYPE_SUFFIX = "_t" + + +def _is_request_field(field): + # Skip fields that are hidden to the jvpp user (handled by JNI layer) + return field.name not in {'_vl_msg_id', 'client_index', 'context'} + + +def _is_reply_field(field): + # Skip fields that are hidden to the jvpp user: + # _vl_msg_id is handled at JNI layer, + # Unlike in the request case, context is visible to allow matching replies with requests at Java layer. + return field.name not in {'_vl_msg_id'} diff --git a/java/jvpp/gen/jvppgen/notification_gen.py b/java/jvpp/gen/jvppgen/notification_gen.py new file mode 100644 index 0000000..fa86fe4 --- /dev/null +++ b/java/jvpp/gen/jvppgen/notification_gen.py @@ -0,0 +1,226 @@ +#!/usr/bin/env python2 +# +# Copyright (c) 2016,2018 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 + +from jvpp_model import is_control_ping, is_control_ping_reply, is_dump, is_request + + +def generate_notifications(work_dir, model, logger): + """ Generates notification registry interface and implementation """ + logger.debug("Generating Notification interfaces and implementation for %s" % model.json_api_files) + messages = filter(_notification_filter, model.messages) + _generate_global_event_callback(work_dir, model, messages) + _generate_event_registry(work_dir, model, messages) + _generate_event_registry_impl(work_dir, model, messages) + _generate_event_registry_provider(work_dir, model) + + +def _notification_filter(msg): + # Generate callbacks for all messages except for dumps and requests (handled by vpp, not client). + # Also skip control ping managed by jvpp registry. + return (not is_control_ping(msg)) and \ + (not is_control_ping_reply(msg)) and \ + (not is_dump(msg)) and \ + (not is_request(msg)) + + +def _generate_event_registry(work_dir, model, messages): + plugin_name = model.plugin_java_name + plugin_package = model.plugin_package + + register_callback_methods = [] + for msg in messages: + name = _callback_name(msg) + fqn_name = _fqn_callback_name(plugin_package, name) + # 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%s(%s callback);" % (name, fqn_name)) + + with open("%s/%sEventRegistry.java" % (work_dir, plugin_name), "w") as f: + f.write(_EVENT_REGISTRY_TEMPLATE.substitute( + plugin_package=plugin_package, + plugin_name=plugin_name, + json_filename=model.json_api_files, + register_callback_methods="\n".join(register_callback_methods) + )) + +_EVENT_REGISTRY_TEMPLATE = Template(""" +package $plugin_package.notification; + +/** + * <p>Registry for notification callbacks defined in ${plugin_name}. + * <br>It was generated by notification_gen.py based on $json_filename. + */ +public interface ${plugin_name}EventRegistry extends io.fd.vpp.jvpp.notification.EventRegistry { + +$register_callback_methods + + @Override + void close(); +} +""") + + +def _generate_event_registry_impl(work_dir, model, messages): + plugin_name = model.plugin_java_name + plugin_package = model.plugin_package + + register_callback_methods = [] + handler_methods = [] + for msg in messages: + notification = msg.java_name_upper + callback = "%sCallback" % notification + register_callback_methods.append(_REGISTER_CALLBACK_IMPL_TEMPLATE.substitute( + plugin_package=plugin_package, + notification=notification, + callback=callback + )) + handler_methods.append(_HANDLER_IMPL_TEMPLATE.substitute( + plugin_package=plugin_package, + notification=notification, + callback=callback + )) + + with open("%s/%sEventRegistryImpl.java" % (work_dir, plugin_name), "w") as f: + f.write(_EVENT_REGISTRY_IMPL_TEMPLATE.substitute( + plugin_package=plugin_package, + plugin_name=plugin_name, + json_filename=model.json_api_files, + register_callback_methods="".join(register_callback_methods), + handler_methods="".join(handler_methods) + )) + +_REGISTER_CALLBACK_IMPL_TEMPLATE = Template(""" + public java.lang.AutoCloseable register$callback(final $plugin_package.callback.$callback callback){ + if(null != registeredCallbacks.putIfAbsent($plugin_package.dto.$notification.class, callback)){ + throw new IllegalArgumentException("Callback for " + $plugin_package.dto.$notification.class + + "notification already registered"); + } + return () -> registeredCallbacks.remove($plugin_package.dto.$notification.class); + } +""") + +_HANDLER_IMPL_TEMPLATE = Template(""" + @Override + public void on$notification( + final $plugin_package.dto.$notification notification) { + if (LOG.isLoggable(java.util.logging.Level.FINE)) { + LOG.fine(java.lang.String.format("Received $notification event message: %s", notification)); + } + final io.fd.vpp.jvpp.callback.JVppCallback jVppCallback = registeredCallbacks.get($plugin_package.dto.$notification.class); + if (null != jVppCallback) { + (($plugin_package.callback.$callback) registeredCallbacks + .get($plugin_package.dto.$notification.class)) + .on$notification(notification); + } + } +""") + +_EVENT_REGISTRY_IMPL_TEMPLATE = Template(""" +package $plugin_package.notification; + +/** + * <p>Notification registry delegating notification processing to registered callbacks. + * <br>It was generated by notification_gen.py based on $json_filename. + */ +public final class ${plugin_name}EventRegistryImpl implements ${plugin_name}EventRegistry, Global${plugin_name}EventCallback { + + // TODO add a special NotificationCallback interface and only allow those to be registered + private final java.util.concurrent.ConcurrentMap<Class<?>, io.fd.vpp.jvpp.callback.JVppCallback> registeredCallbacks = + new java.util.concurrent.ConcurrentHashMap<>(); + private static java.util.logging.Logger LOG = java.util.logging.Logger.getLogger(${plugin_name}EventRegistryImpl.class.getName()); + + $register_callback_methods + $handler_methods + + @Override + public void close() { + registeredCallbacks.clear(); + } + + @Override + public void onError(io.fd.vpp.jvpp.VppCallbackException ex) { + java.util.logging.Logger LOG = java.util.logging.Logger.getLogger(${plugin_name}EventRegistryImpl.class.getName()); + LOG.log(java.util.logging.Level.WARNING, java.lang.String.format("Received onError exception: call=%s, context=%d, retval=%d%n", ex.getMethodName(), + ex.getCtxId(), ex.getErrorCode()), ex); + } +} +""") + + +def _generate_global_event_callback(work_dir, model, messages): + plugin_name = model.plugin_java_name + plugin_package = model.plugin_package + + callbacks = "" + callback_list = [] + for msg in messages: + fqn_name = _fqn_callback_name(plugin_package, _callback_name(msg)) + callback_list.append(fqn_name) + + if callback_list: + callbacks = " extends %s" % ", ".join(callback_list) + + with open("%s/Global%sEventCallback.java" % (work_dir, plugin_name), "w") as f: + f.write(_GLOBAL_EVENT_CALLBACK_TEMPLATE.substitute( + plugin_package=plugin_package, + plugin_name=plugin_name, + json_filename=model.json_api_files, + callbacks=callbacks + )) + +_GLOBAL_EVENT_CALLBACK_TEMPLATE = Template(""" +package $plugin_package.notification; + +/** + * <p>Aggregated callback interface for notifications only. + * <br>It was generated by notification_gen.py based on $json_filename. + */ +public interface Global${plugin_name}EventCallback$callbacks { + +} +""") + + +def _generate_event_registry_provider(work_dir, model): + plugin_name = model.plugin_java_name + with open("%s/%sEventRegistryProvider.java" % (work_dir, plugin_name), "w") as f: + f.write(_EVENT_REGISTRY_PROVIDER_TEMPLATE.substitute( + plugin_package=model.plugin_package, + plugin_name=plugin_name, + json_filename=model.json_api_files + )) + +_EVENT_REGISTRY_PROVIDER_TEMPLATE = Template(""" +package $plugin_package.notification; + + /** + * Provides ${plugin_name}EventRegistry. + * <br>The file was generated by notification_gen.py based on $json_filename. + */ +public interface ${plugin_name}EventRegistryProvider extends io.fd.vpp.jvpp.notification.EventRegistryProvider { + + @Override + public ${plugin_name}EventRegistry getEventRegistry(); +} +""") + + +def _callback_name(msg): + return "%sCallback" % msg.java_name_upper + + +def _fqn_callback_name(plugin_package, callback_name): + return "%s.callback.%s" % (plugin_package, callback_name) diff --git a/java/jvpp/gen/jvppgen/types_gen.py b/java/jvpp/gen/jvppgen/types_gen.py new file mode 100755 index 0000000..3767b53 --- /dev/null +++ b/java/jvpp/gen/jvppgen/types_gen.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python2 +# +# Copyright (c) 2016,2018 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 + +from jvpp_common_gen import generate_hash_code, generate_equals, generate_to_string, generate_fields +from jvpp_model import Class + + +def generate_types(work_dir, model, logger): + logger.debug("Generating custom types for %s " % model.json_api_files) + + for t in model.types: + if not isinstance(t, Class): + continue + logger.debug("Generating DTO for type %s", t) + type_class_name = t.java_name + fields = t.fields + type_class = _TYPE_TEMPLATE.substitute( + plugin_package=model.plugin_package, + c_type_name=t.name, + json_filename=model.json_api_files, + json_definition=t.doc, + java_type_name=type_class_name, + fields=generate_fields(fields), + hash_code=generate_hash_code(fields), + equals=generate_equals(type_class_name, fields), + to_string=generate_to_string(type_class_name, fields) + ) + with open("%s/%s.java" % (work_dir, type_class_name), "w") as f: + f.write(type_class) + +_TYPE_TEMPLATE = Template(""" +package $plugin_package.types; + +/** + * <p>This class represents $c_type_name type definition. + * <br>It was generated by jvpp_types_gen.py based on $json_filename: + * <pre> +$json_definition + * </pre> + */ +public final class $java_type_name { +$fields +$hash_code +$equals +$to_string +} +""") diff --git a/java/jvpp/gen/jvppgen/unions_gen.py b/java/jvpp/gen/jvppgen/unions_gen.py new file mode 100755 index 0000000..f67704f --- /dev/null +++ b/java/jvpp/gen/jvppgen/unions_gen.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python2 +# +# Copyright (c) 2018 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 + +from jvpp_common_gen import generate_hash_code, generate_equals, generate_to_string, generate_fields +from jvpp_model import Union + + +def generate_unions(work_dir, model, logger): + logger.debug("Generating unions for %s " % model.json_api_files) + + for t in model.types: + if not isinstance(t, Union): + continue + logger.debug("Generating DTO for union %s", t) + java_union_name = t.java_name + fields = t.fields + type_class = _UNION_TEMPLATE.substitute( + plugin_package=model.plugin_package, + c_type_name=t.name, + json_filename=model.json_api_files, + json_definition=t.doc, + java_union_name=java_union_name, + fields=generate_fields(fields, access_modifier="private"), + constructors=_generate_constructors(java_union_name, fields), + getters=_generate_getters(fields), + hash_code=generate_hash_code(fields), + equals=generate_equals(java_union_name, fields), + to_string=generate_to_string(java_union_name, fields) + ) + with open("%s/%s.java" % (work_dir, java_union_name), "w") as f: + f.write(type_class) + +_UNION_TEMPLATE = Template(""" +package ${plugin_package}.types; + +/** + * <p>This class represents ${c_type_name} union definition. + * <br>It was generated by unions_gen.py based on ${json_filename}: + * <pre> +${json_definition} + * </pre> + */ +public class ${java_union_name} { + private final int _activeMember; +${fields} + private ${java_union_name}() { + // Constructor for JNI usage. All members can be read. + _activeMember = -1; + } +${constructors} +${getters} +${hash_code} +${equals} +${to_string} +} +""") + + +def _generate_constructors(union_name, fields): + return "".join( + _CONSTRUCTOR_TEMPLATE + .substitute(union_name=union_name, + field_type=f.type.java_name_fqn, + field_name=f.java_name, + field_index=i) for i, f in enumerate(fields)) + +_CONSTRUCTOR_TEMPLATE = Template(""" + public ${union_name}(${field_type} ${field_name}) { + this.${field_name} = java.util.Objects.requireNonNull(${field_name}, "${field_name} should not be null"); + _activeMember = $field_index; + }""") + + +def _generate_getters(fields): + return "".join(_GETTER_TEMPLATE.substitute( + type=f.type.java_name_fqn, + getter_name=f.java_name_upper, + field_name=f.java_name + ) for f in fields) + +_GETTER_TEMPLATE = Template(""" + public ${type} get${getter_name}() { + return ${field_name}; + }""") |