From 8f2a4eafeaa439432107563033728e09665c16d9 Mon Sep 17 00:00:00 2001 From: Klement Sekera Date: Thu, 4 May 2017 06:15:18 +0200 Subject: Add new C API Change-Id: I717ce3cd7c867c155de149ec56623269d26d0ff7 Signed-off-by: Klement Sekera --- src/vpp-api/vapi/vapi_json_parser.py | 303 +++++++++++++++++++++++++++++++++++ 1 file changed, 303 insertions(+) create mode 100644 src/vpp-api/vapi/vapi_json_parser.py (limited to 'src/vpp-api/vapi/vapi_json_parser.py') diff --git a/src/vpp-api/vapi/vapi_json_parser.py b/src/vpp-api/vapi/vapi_json_parser.py new file mode 100644 index 00000000..57a22383 --- /dev/null +++ b/src/vpp-api/vapi/vapi_json_parser.py @@ -0,0 +1,303 @@ +#!/usr/bin/env python3 + +import json + + +def msg_is_reply(name): + return name.endswith('_reply') or name.endswith('_details') \ + or name.endswith('_event') or name.endswith('_counters') + + +class ParseError (Exception): + pass + + +magic_prefix = "vl_api_" +magic_suffix = "_t" + + +def remove_magic(what): + if what.startswith(magic_prefix) and what.endswith(magic_suffix): + return what[len(magic_prefix): - len(magic_suffix)] + return what + + +class Field: + + def __init__( + self, + field_name, + field_type, + array_len=None, + nelem_field=None): + self.name = field_name + self.type = field_type + self.len = array_len + self.nelem_field = nelem_field + + def __str__(self): + if self.len is None: + return "name: %s, type: %s" % (self.name, self.type) + elif self.len > 0: + return "name: %s, type: %s, length: %s" % (self.name, self.type, + self.len) + else: + return ("name: %s, type: %s, variable length stored in: %s" % + (self.name, self.type, self.nelem_field)) + + +class Type: + def __init__(self, name): + self.name = name + + +class SimpleType (Type): + + def __init__(self, name): + super().__init__(name) + + def __str__(self): + return self.name + + +def get_msg_header_defs(struct_type_class, field_class, typedict): + return [ + struct_type_class(['msg_header1_t', + ['u16', '_vl_msg_id'], + ['u32', 'context'], + ], + typedict, field_class + ), + struct_type_class(['msg_header2_t', + ['u16', '_vl_msg_id'], + ['u32', 'client_index'], + ['u32', 'context'], + ], + typedict, field_class + ), + ] + + +class Struct: + + def __init__(self, name, fields): + self.name = name + self.fields = fields + self.field_names = [n.name for n in self.fields] + + +class Message: + + def __init__(self, logger, definition, typedict, + struct_type_class, simple_type_class, field_class): + self.logger = logger + m = definition + logger.debug("Parsing message definition `%s'" % m) + name = m[0] + self.name = name + logger.debug("Message name is `%s'" % name) + ignore = True + self.header = None + fields = [] + for header in get_msg_header_defs(struct_type_class, field_class, + typedict): + logger.debug("Probing header `%s'" % header.name) + if header.is_part_of_def(m[1:]): + self.header = header + logger.debug("Found header `%s'" % header.name) + fields.append(field_class(field_name='header', + field_type=self.header)) + ignore = False + break + if ignore and not msg_is_reply(name): + raise ParseError("While parsing message `%s': could not find all " + "common header fields" % name) + for field in m[1:]: + if len(field) == 1 and 'crc' in field: + self.crc = field['crc'] + logger.debug("Found CRC `%s'" % self.crc) + continue + else: + field_type = field[0] + if field_type in typedict: + field_type = typedict[field_type] + else: + field_type = typedict[remove_magic(field_type)] + if len(field) == 2: + if self.header is not None and\ + self.header.has_field(field[1]): + continue + p = field_class(field_name=field[1], + field_type=field_type) + elif len(field) == 3: + if field[2] == 0: + raise ParseError( + "While parsing message `%s': variable length " + "array `%s' doesn't have reference to member " + "containing the actual length" % ( + name, field[1])) + p = field_class( + field_name=field[1], + field_type=field_type, + array_len=field[2]) + elif len(field) == 4: + nelem_field = None + for f in fields: + if f.name == field[3]: + nelem_field = f + if nelem_field is None: + raise ParseError( + "While parsing message `%s': couldn't find " + "variable length array `%s' member containing " + "the actual length `%s'" % ( + name, field[1], field[3])) + p = field_class( + field_name=field[1], + field_type=field_type, + array_len=field[2], + nelem_field=nelem_field) + else: + raise Exception("Don't know how to parse message " + "definition for message `%s': `%s'" % + (m, m[1:])) + logger.debug("Parsed field `%s'" % p) + fields.append(p) + self.fields = fields + + def is_dump(self): + return self.name.endswith('_dump') + + def is_reply(self): + return msg_is_reply(self.name) + + +class StructType (Type, Struct): + + def __init__(self, definition, typedict, field_class): + t = definition + name = t[0] + fields = [] + for field in t[1:]: + if len(field) == 1 and 'crc' in field: + self.crc = field['crc'] + continue + elif len(field) == 2: + p = field_class(field_name=field[1], + field_type=typedict[field[0]]) + elif len(field) == 3: + if field[2] == 0: + raise ParseError("While parsing type `%s': array `%s' has " + "variable length" % (name, field[1])) + p = field_class(field_name=field[1], + field_type=typedict[field[0]], + array_len=field[2]) + else: + raise ParseError( + "Don't know how to parse type definition for " + "type `%s': `%s'" % (t, t[1:])) + fields.append(p) + Type.__init__(self, name) + Struct.__init__(self, name, fields) + + def has_field(self, name): + return name in self.field_names + + def is_part_of_def(self, definition): + for idx in range(len(self.fields)): + field = definition[idx] + p = self.fields[idx] + if field[1] != p.name: + return False + if field[0] != p.type.name: + raise ParseError( + "Unexpected field type `%s' (should be `%s'), " + "while parsing msg/def/field `%s/%s/%s'" % + (field[0], p.type, p.name, definition, field)) + return True + + +class JsonParser: + def __init__(self, logger, files, simple_type_class=SimpleType, + struct_type_class=StructType, field_class=Field, + message_class=Message): + self.messages = {} + self.types = { + x: simple_type_class(x) for x in [ + 'i8', 'i16', 'i32', 'i64', + 'u8', 'u16', 'u32', 'u64', + 'f64' + ] + } + + self.simple_type_class = simple_type_class + self.struct_type_class = struct_type_class + self.field_class = field_class + self.message_class = message_class + + self.exceptions = [] + self.json_files = [] + self.types_by_json = {} + self.messages_by_json = {} + self.logger = logger + for f in files: + self.parse_json_file(f) + self.finalize_parsing() + + def parse_json_file(self, path): + self.logger.info("Parsing json api file: `%s'" % path) + self.json_files.append(path) + self.types_by_json[path] = {} + self.messages_by_json[path] = {} + with open(path) as f: + j = json.load(f) + for t in j['types']: + try: + type_ = self.struct_type_class(t, self.types, + self.field_class) + if type_.name in self.types: + raise ParseError("Duplicate type `%s'" % type_.name) + except ParseError as e: + self.exceptions.append(e) + continue + self.types[type_.name] = type_ + self.types_by_json[path][type_.name] = type_ + for m in j['messages']: + try: + msg = self.message_class(self.logger, m, self.types, + self.struct_type_class, + self.simple_type_class, + self.field_class) + if msg.name in self.messages: + raise ParseError("Duplicate message `%s'" % msg.name) + except ParseError as e: + self.exceptions.append(e) + continue + self.messages[msg.name] = msg + self.messages_by_json[path][msg.name] = msg + + def get_reply(self, message): + if self.messages[message].is_dump(): + return self.messages["%s_details" % message[:-len("_dump")]] + return self.messages["%s_reply" % message] + + def finalize_parsing(self): + if len(self.messages) == 0: + for e in self.exceptions: + self.logger.error(e) + raise Exception("No messages parsed.") + for jn, j in self.messages_by_json.items(): + remove = [] + for n, m in j.items(): + try: + if not m.is_reply(): + try: + m.reply = self.get_reply(n) + except: + raise ParseError( + "Cannot find reply to message `%s'" % n) + except ParseError as e: + self.exceptions.append(e) + remove.append(n) + + self.messages_by_json[jn] = { + k: v for k, v in j.items() if k not in remove} -- cgit 1.2.3-korg From dc15be2ca7c51772b00e4c5548934a35aa7e4add Mon Sep 17 00:00:00 2001 From: Klement Sekera Date: Mon, 12 Jun 2017 06:49:33 +0200 Subject: Add C++ API Change-Id: Iff634f22d43470e2dc028387b3816257fd7b4156 Signed-off-by: Klement Sekera --- .clang-format | 38 ++ build-root/scripts/checkstyle.sh | 62 +- src/configure.ac | 1 + src/vpp-api/vapi/Makefile.am | 27 +- src/vpp-api/vapi/libvapiclient.map | 3 + src/vpp-api/vapi/vapi.c | 59 +- src/vpp-api/vapi/vapi.h | 102 ++- src/vpp-api/vapi/vapi.hpp | 905 ++++++++++++++++++++++++++ src/vpp-api/vapi/vapi_c_gen.py | 188 +----- src/vpp-api/vapi/vapi_common.h | 61 ++ src/vpp-api/vapi/vapi_cpp_gen.py | 262 ++++++++ src/vpp-api/vapi/vapi_dbg.h | 1 + src/vpp-api/vapi/vapi_doc.md | 155 +++++ src/vpp-api/vapi/vapi_internal.h | 22 +- src/vpp-api/vapi/vapi_json_parser.py | 2 + test/ext/Makefile | 26 +- test/ext/fake.api.json | 35 + test/ext/vapi_c_test.c | 1168 ++++++++++++++++++++++++++++++++++ test/ext/vapi_cpp_test.cpp | 591 +++++++++++++++++ test/ext/vapi_test.c | 1152 --------------------------------- test/test_vapi.py | 36 +- 21 files changed, 3483 insertions(+), 1413 deletions(-) create mode 100644 .clang-format create mode 100644 src/vpp-api/vapi/vapi.hpp create mode 100644 src/vpp-api/vapi/vapi_common.h create mode 100755 src/vpp-api/vapi/vapi_cpp_gen.py create mode 100644 src/vpp-api/vapi/vapi_doc.md create mode 100644 test/ext/fake.api.json create mode 100644 test/ext/vapi_c_test.c create mode 100644 test/ext/vapi_cpp_test.cpp delete mode 100644 test/ext/vapi_test.c (limited to 'src/vpp-api/vapi/vapi_json_parser.py') diff --git a/.clang-format b/.clang-format new file mode 100644 index 00000000..977ed2db --- /dev/null +++ b/.clang-format @@ -0,0 +1,38 @@ +--- +AlignEscapedNewlinesLeft: true +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AllowShortFunctionsOnASingleLine: false +AlwaysBreakBeforeMultilineStrings: false +BreakBeforeBinaryOperators: false +BreakBeforeTernaryOperators: true +BinPackParameters: true +BreakBeforeBraces: GNU +ColumnLimit: 79 +IndentCaseLabels: false +MaxEmptyLinesToKeep: 1 +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 60 +PenaltyBreakString: 1000 +PenaltyBreakFirstLessLess: 120 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 60 +PointerBindsToType: false +SpaceBeforeAssignmentOperators: true +SpaceBeforeParens: Always +SpacesBeforeTrailingComments: 1 +SpacesInParentheses: false +SpaceInEmptyParentheses: false +SpacesInCStyleCastParentheses: false +SpaceAfterControlStatementKeyword: true +Cpp11BracedListStyle: true +Standard: Cpp11 +SortIncludes: false +IndentWidth: 2 +TabWidth: 4 +UseTab: Never +IndentFunctionDeclarationAfterType: false +ContinuationIndentWidth: 4 +... diff --git a/build-root/scripts/checkstyle.sh b/build-root/scripts/checkstyle.sh index 55fe4ab5..bd2ba813 100755 --- a/build-root/scripts/checkstyle.sh +++ b/build-root/scripts/checkstyle.sh @@ -32,37 +32,75 @@ fi # don't *fail*. command -v indent > /dev/null if [ $? != 0 ]; then - echo "Cound not find required commend \"indent\". Checkstyle aborted" + echo "Cound not find required command \"indent\". Checkstyle aborted" exit ${EXIT_CODE} fi indent --version +# Check to make sure we have clang-format. Exit if we don't with an error message, but +# don't *fail*. +command -v clang-format > /dev/null +if [ $? != 0 ]; then + echo "Could not find command \"clang-format\". Checking C++ files will cause abort" + HAVE_CLANG_FORMAT=0 +else + HAVE_CLANG_FORMAT=1 + clang-format --version +fi + cd ${VPP_DIR} git status for i in ${FILELIST}; do if [ -f ${i} ] && [ ${i} != "build-root/scripts/checkstyle.sh" ] && [ ${i} != "extras/emacs/fix-coding-style.el" ]; then grep -q "fd.io coding-style-patch-verification: ON" ${i} if [ $? == 0 ]; then + EXTENSION=`basename ${i} | sed 's/^\w\+.//'` + case ${EXTENSION} in + hpp|cpp|cc|hh) + CMD="clang-format" + if [ ${HAVE_CLANG_FORMAT} == 0 ]; then + echo "C++ file detected. Abort. (missing clang-format)" + exit ${EXIT_CODE} + fi + ;; + *) + CMD="indent" + ;; + esac CHECKSTYLED_FILES="${CHECKSTYLED_FILES} ${i}" if [ ${FIX} == 0 ]; then - indent ${i} -o ${i}.out1 > /dev/null 2>&1 - indent ${i}.out1 -o ${i}.out2 > /dev/null 2>&1 - # Remove trailing whitespace - sed -i -e 's/[[:space:]]*$//' ${i}.out2 + if [ "${CMD}" == "clang-format" ] + then + clang-format ${i} > ${i}.out2 + else + indent ${i} -o ${i}.out1 > /dev/null 2>&1 + indent ${i}.out1 -o ${i}.out2 > /dev/null 2>&1 + fi + # Remove trailing whitespace + sed -i -e 's/[[:space:]]*$//' ${i}.out2 diff -q ${i} ${i}.out2 else - indent ${i} - indent ${i} - # Remove trailing whitespace - sed -i -e 's/[[:space:]]*$//' ${i} + if [ "${CMD}" == "clang-format" ]; then + clang-format -i ${i} > /dev/null 2>&1 + else + indent ${i} + indent ${i} + fi + # Remove trailing whitespace + sed -i -e 's/[[:space:]]*$//' ${i} fi if [ $? != 0 ]; then EXIT_CODE=1 echo echo "Checkstyle failed for ${i}." - echo "Run indent (twice!) as shown to fix the problem:" - echo "indent ${VPP_DIR}${i}" - echo "indent ${VPP_DIR}${i}" + if [ "${CMD}" == "clang-format" ]; then + echo "Run clang-format as shown to fix the problem:" + echo "clang-format -i ${VPP_DIR}${i}" + else + echo "Run indent (twice!) as shown to fix the problem:" + echo "indent ${VPP_DIR}${i}" + echo "indent ${VPP_DIR}${i}" + fi fi if [ -f ${i}.out1 ]; then rm ${i}.out1 diff --git a/src/configure.ac b/src/configure.ac index 2efb23ad..f5ce3be2 100644 --- a/src/configure.ac +++ b/src/configure.ac @@ -7,6 +7,7 @@ AC_CONFIG_FILES([Makefile plugins/Makefile vpp-api/python/Makefile vpp-api/java/ AC_CONFIG_MACRO_DIR([m4]) AC_PROG_CC +AC_PROG_CXX AM_PROG_AS AM_PROG_LIBTOOL AC_PROG_YACC diff --git a/src/vpp-api/vapi/Makefile.am b/src/vpp-api/vapi/Makefile.am index ce681c38..74b2b47e 100644 --- a/src/vpp-api/vapi/Makefile.am +++ b/src/vpp-api/vapi/Makefile.am @@ -15,7 +15,7 @@ AUTOMAKE_OPTIONS = foreign ACLOCAL_AMFLAGS = -I m4 AM_LIBTOOLFLAGS = --quiet -AM_CFLAGS = -Wall -I${top_srcdir} -I${top_builddir} -I. -I$(top_srcdir)/vpp-api/vapi +AM_CFLAGS = -Wall -I${top_srcdir} -I${top_builddir} -I. -I$(top_srcdir)/vpp-api/ AM_LDFLAGS = -shared -avoid-version -rpath /none -no-undefined @@ -23,26 +23,33 @@ bin_PROGRAMS = noinst_LTLIBRARIES = CLEANDIRS = -%.api.vapi.h: %.api.json vapi_c_gen.py +vapi/%.api.vapi.h: %.api.json vapi_c_gen.py vapi_json_parser.py @echo " VAPI C GEN $< " $@ ; \ mkdir -p `dirname $@` ; \ - $(top_srcdir)/vpp-api/vapi/vapi_c_gen.py $< + $(top_srcdir)/vpp-api/vapi/vapi_c_gen.py --prefix=vapi $< + +vapi/%.api.vapi.hpp: %.api.json vapi_cpp_gen.py vapi_c_gen.py vapi_json_parser.py + @echo " VAPI CPP GEN $< " $@ ; \ + mkdir -p `dirname $@` ; \ + $(top_srcdir)/vpp-api/vapi/vapi_cpp_gen.py --prefix=vapi --gen-h-prefix=vapi $< %.api.json: find $(top_builddir) -name '$@' | xargs ln -s BUILT_SOURCES = $(shell find $(top_builddir) -name '*.api.json' | xargs -n1 basename) \ - $(patsubst %.api.json,%.api.vapi.h,$(JSON_FILES)) + $(patsubst %.api.json,vapi/%.api.vapi.h,$(JSON_FILES)) \ + $(patsubst %.api.json,vapi/%.api.vapi.hpp,$(JSON_FILES)) vapi.c: $(BUILT_SOURCES) JSON_FILES = $(wildcard *.api.json) - lib_LTLIBRARIES = libvapiclient.la libvapiclient_la_SOURCES = vapi.c +libvapiclient_la_DEPENDENCIES = libvapiclient.map + libvapiclient_la_LIBADD = -lpthread -lm -lrt \ $(top_builddir)/libvppinfra.la \ $(top_builddir)/libvlibmemoryclient.la \ @@ -54,10 +61,14 @@ libvapiclient_la_LDFLAGS = \ libvapiclient_la_CPPFLAGS = -I. -I$(top_builddir)/vpp-api/vapi -nobase_include_HEADERS = ${top_srcdir}/vpp-api/client/vppapiclient.h \ - vapi.h \ +vapiincludedir = $(includedir)/vapi + +vapiinclude_HEADERS = vapi.h \ + vapi.hpp \ vapi_dbg.h \ + vapi_common.h \ vapi_internal.h \ - $(patsubst %.api.json,%.api.vapi.h,$(JSON_FILES)) + $(patsubst %.api.json,vapi/%.api.vapi.h,$(JSON_FILES)) \ + $(patsubst %.api.json,vapi/%.api.vapi.hpp,$(JSON_FILES)) # vi:syntax=automake diff --git a/src/vpp-api/vapi/libvapiclient.map b/src/vpp-api/vapi/libvapiclient.map index 53733002..6b58d1e9 100644 --- a/src/vpp-api/vapi/libvapiclient.map +++ b/src/vpp-api/vapi/libvapiclient.map @@ -23,6 +23,7 @@ VAPICLIENT_17.07 { vapi_register_msg; vapi_get_client_index; vapi_is_nonblocking; + vapi_requests_empty; vapi_requests_full; vapi_gen_req_context; vapi_producer_lock; @@ -36,6 +37,8 @@ VAPICLIENT_17.07 { vapi_get_context_offset; vapi_msg_id_control_ping; vapi_msg_id_control_ping_reply; + vapi_get_message_count; + vapi_get_msg_name; local: *; }; diff --git a/src/vpp-api/vapi/vapi.c b/src/vpp-api/vapi/vapi.c index b9c81a13..59415e03 100644 --- a/src/vpp-api/vapi/vapi.c +++ b/src/vpp-api/vapi/vapi.c @@ -102,7 +102,7 @@ vapi_requests_full (vapi_ctx_t ctx) return (ctx->requests_count == ctx->requests_size); } -static bool +bool vapi_requests_empty (vapi_ctx_t ctx) { return (0 == ctx->requests_count); @@ -229,6 +229,16 @@ vapi_msg_free (vapi_ctx_t ctx, void *msg) vl_msg_api_free (msg); } +vapi_msg_id_t +vapi_lookup_vapi_msg_id_t (vapi_ctx_t ctx, u16 vl_msg_id) +{ + if (vl_msg_id <= ctx->vl_msg_id_max) + { + return ctx->vl_msg_id_to_vapi_msg_t[vl_msg_id]; + } + return ~0; +} + vapi_error_e vapi_ctx_alloc (vapi_ctx_t * result) { @@ -420,16 +430,17 @@ vapi_send (vapi_ctx_t ctx, void *msg) vapi_msg_id_t id = ctx->vl_msg_id_to_vapi_msg_t[msgid]; if (id < __vapi_metadata.count) { - VAPI_DBG ("send msg %u[%s]", msgid, __vapi_metadata.msgs[id]->name); + VAPI_DBG ("send msg@%p:%u[%s]", msg, msgid, + __vapi_metadata.msgs[id]->name); } else { - VAPI_DBG ("send msg %u[UNKNOWN]", msgid); + VAPI_DBG ("send msg@%p:%u[UNKNOWN]", msg, msgid); } } else { - VAPI_DBG ("send msg %u[UNKNOWN]", msgid); + VAPI_DBG ("send msg@%p:%u[UNKNOWN]", msg, msgid); } #endif tmp = unix_shared_memory_queue_add (q, (u8 *) & msg, @@ -522,7 +533,26 @@ vapi_recv (vapi_ctx_t ctx, void **msg, size_t * msg_size) } *msg = (u8 *) data; *msg_size = ntohl (msgbuf->data_len); - VAPI_DBG ("recv msg %p", *msg); +#if VAPI_DEBUG + unsigned msgid = be16toh (*(u16 *) * msg); + if (msgid <= ctx->vl_msg_id_max) + { + vapi_msg_id_t id = ctx->vl_msg_id_to_vapi_msg_t[msgid]; + if (id < __vapi_metadata.count) + { + VAPI_DBG ("recv msg@%p:%u[%s]", *msg, msgid, + __vapi_metadata.msgs[id]->name); + } + else + { + VAPI_DBG ("recv msg@%p:%u[UNKNOWN]", *msg, msgid); + } + } + else + { + VAPI_DBG ("recv msg@%p:%u[UNKNOWN]", *msg, msgid); + } +#endif } else { @@ -534,7 +564,6 @@ vapi_recv (vapi_ctx_t ctx, void **msg, size_t * msg_size) vapi_error_e vapi_wait (vapi_ctx_t ctx, vapi_wait_mode_e mode) { - /* FIXME */ return VAPI_ENOTSUP; } @@ -657,7 +686,7 @@ vapi_dispatch_event (vapi_ctx_t ctx, vapi_msg_id_t id, void *msg) return VAPI_OK; } -static bool +bool vapi_msg_is_with_context (vapi_msg_id_t id) { assert (id <= __vapi_metadata.count); @@ -785,10 +814,6 @@ vapi_is_nonblocking (vapi_ctx_t ctx) return (VAPI_MODE_NONBLOCKING == ctx->mode); } -bool vapi_requests_full (vapi_ctx_t ctx); - -size_t vapi_get_request_count (vapi_ctx_t ctx); - size_t vapi_get_max_request_count (vapi_ctx_t ctx) { @@ -886,6 +911,18 @@ vapi_producer_unlock (vapi_ctx_t ctx) return VAPI_OK; } +size_t +vapi_get_message_count () +{ + return __vapi_metadata.count; +} + +const char * +vapi_get_msg_name (vapi_msg_id_t id) +{ + return __vapi_metadata.msgs[id]->name; +} + /* * fd.io coding-style-patch-verification: ON * diff --git a/src/vpp-api/vapi/vapi.h b/src/vpp-api/vapi/vapi.h index 1e1d567a..245bf654 100644 --- a/src/vpp-api/vapi/vapi.h +++ b/src/vpp-api/vapi/vapi.h @@ -21,6 +21,12 @@ #include #include #include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif /** * @file vapi.h @@ -36,39 +42,7 @@ * process). It's not recommended to mix the higher and lower level APIs. Due * to version issues, the higher-level APIs are not part of the shared library. */ - -typedef enum -{ - VAPI_OK = 0, /**< success */ - VAPI_EINVAL, /**< invalid value encountered */ - VAPI_EAGAIN, /**< operation would block */ - VAPI_ENOTSUP, /**< operation not supported */ - VAPI_ENOMEM, /**< out of memory */ - VAPI_ENORESP, /**< no response to request */ - VAPI_EMAP_FAIL, /**< failure while mapping api */ - VAPI_ECON_FAIL, /**< failure while connecting to vpp */ - VAPI_EINCOMPATIBLE, /**< fundamental incompatibility while connecting to vpp - (control ping/control ping reply mismatch) */ - VAPI_MUTEX_FAILURE, /**< failure manipulating internal mutex(es) */ - VAPI_EUSER, /**< user error used for breaking dispatch, - never used by VAPI */ -} vapi_error_e; - -typedef enum -{ - VAPI_MODE_BLOCKING = 1, /**< operations block until response received */ - VAPI_MODE_NONBLOCKING = 2, /**< operations never block */ -} vapi_mode_e; - -typedef enum -{ - VAPI_WAIT_FOR_READ, /**< wait until a message can be read */ - VAPI_WAIT_FOR_WRITE, /**< wait until a message can be written */ - VAPI_WAIT_FOR_READ_WRITE, /**< wait until a read or write can be done */ -} vapi_wait_mode_e; - -typedef int vapi_msg_id_t; -typedef struct vapi_ctx_s *vapi_ctx_t; + typedef struct vapi_ctx_s *vapi_ctx_t; /** * @brief allocate vapi message of given size @@ -80,7 +54,7 @@ typedef struct vapi_ctx_s *vapi_ctx_t; * * @return pointer to message or NULL if out of memory */ -void *vapi_msg_alloc (vapi_ctx_t ctx, size_t size); + void *vapi_msg_alloc (vapi_ctx_t ctx, size_t size); /** * @brief free a vapi message @@ -90,7 +64,7 @@ void *vapi_msg_alloc (vapi_ctx_t ctx, size_t size); * @param ctx opaque vapi context * @param msg message to be freed */ -void vapi_msg_free (vapi_ctx_t ctx, void *msg); + void vapi_msg_free (vapi_ctx_t ctx, void *msg); /** * @brief allocate vapi context @@ -99,18 +73,18 @@ void vapi_msg_free (vapi_ctx_t ctx, void *msg); * * @return VAPI_OK on success, other error code on error */ -vapi_error_e vapi_ctx_alloc (vapi_ctx_t * result); + vapi_error_e vapi_ctx_alloc (vapi_ctx_t * result); /** * @brief free vapi context */ -void vapi_ctx_free (vapi_ctx_t ctx); + void vapi_ctx_free (vapi_ctx_t ctx); /** * @brief check if message identified by it's message id is known by the vpp to * which the connection is open */ -bool vapi_is_msg_available (vapi_ctx_t ctx, vapi_msg_id_t type); + bool vapi_is_msg_available (vapi_ctx_t ctx, vapi_msg_id_t type); /** * @brief connect to vpp @@ -124,10 +98,10 @@ bool vapi_is_msg_available (vapi_ctx_t ctx, vapi_msg_id_t type); * * @return VAPI_OK on success, other error code on error */ -vapi_error_e vapi_connect (vapi_ctx_t ctx, const char *name, - const char *chroot_prefix, - int max_outstanding_requests, - int response_queue_size, vapi_mode_e mode); + vapi_error_e vapi_connect (vapi_ctx_t ctx, const char *name, + const char *chroot_prefix, + int max_outstanding_requests, + int response_queue_size, vapi_mode_e mode); /** * @brief disconnect from vpp @@ -136,7 +110,7 @@ vapi_error_e vapi_connect (vapi_ctx_t ctx, const char *name, * * @return VAPI_OK on success, other error code on error */ -vapi_error_e vapi_disconnect (vapi_ctx_t ctx); + vapi_error_e vapi_disconnect (vapi_ctx_t ctx); /** * @brief get event file descriptor @@ -149,7 +123,7 @@ vapi_error_e vapi_disconnect (vapi_ctx_t ctx); * * @return VAPI_OK on success, other error code on error */ -vapi_error_e vapi_get_fd (vapi_ctx_t ctx, int *fd); + vapi_error_e vapi_get_fd (vapi_ctx_t ctx, int *fd); /** * @brief low-level api for sending messages to vpp @@ -162,7 +136,7 @@ vapi_error_e vapi_get_fd (vapi_ctx_t ctx, int *fd); * * @return VAPI_OK on success, other error code on error */ -vapi_error_e vapi_send (vapi_ctx_t ctx, void *msg); + vapi_error_e vapi_send (vapi_ctx_t ctx, void *msg); /** * @brief low-level api for atomically sending two messages to vpp - either @@ -177,7 +151,7 @@ vapi_error_e vapi_send (vapi_ctx_t ctx, void *msg); * * @return VAPI_OK on success, other error code on error */ -vapi_error_e vapi_send2 (vapi_ctx_t ctx, void *msg1, void *msg2); + vapi_error_e vapi_send2 (vapi_ctx_t ctx, void *msg1, void *msg2); /** * @brief low-level api for reading messages from vpp @@ -191,7 +165,7 @@ vapi_error_e vapi_send2 (vapi_ctx_t ctx, void *msg1, void *msg2); * * @return VAPI_OK on success, other error code on error */ -vapi_error_e vapi_recv (vapi_ctx_t ctx, void **msg, size_t * msg_size); + vapi_error_e vapi_recv (vapi_ctx_t ctx, void **msg, size_t * msg_size); /** * @brief wait for connection to become readable or writable @@ -201,14 +175,14 @@ vapi_error_e vapi_recv (vapi_ctx_t ctx, void **msg, size_t * msg_size); * * @return VAPI_OK on success, other error code on error */ -vapi_error_e vapi_wait (vapi_ctx_t ctx, vapi_wait_mode_e mode); + vapi_error_e vapi_wait (vapi_ctx_t ctx, vapi_wait_mode_e mode); /** * @brief pick next message sent by vpp and call the appropriate callback * * @return VAPI_OK on success, other error code on error */ -vapi_error_e vapi_dispatch_one (vapi_ctx_t ctx); + vapi_error_e vapi_dispatch_one (vapi_ctx_t ctx); /** * @brief loop vapi_dispatch_one until responses to all currently outstanding @@ -224,11 +198,11 @@ vapi_error_e vapi_dispatch_one (vapi_ctx_t ctx); * * @return VAPI_OK on success, other error code on error */ -vapi_error_e vapi_dispatch (vapi_ctx_t ctx); + vapi_error_e vapi_dispatch (vapi_ctx_t ctx); /** generic vapi event callback */ -typedef vapi_error_e (*vapi_event_cb) (vapi_ctx_t ctx, void *callback_ctx, - void *payload); + typedef vapi_error_e (*vapi_event_cb) (vapi_ctx_t ctx, void *callback_ctx, + void *payload); /** * @brief set event callback to call when message with given id is dispatched @@ -238,8 +212,8 @@ typedef vapi_error_e (*vapi_event_cb) (vapi_ctx_t ctx, void *callback_ctx, * @param callback callback * @param callback_ctx context pointer stored and passed to callback */ -void vapi_set_event_cb (vapi_ctx_t ctx, vapi_msg_id_t id, - vapi_event_cb callback, void *callback_ctx); + void vapi_set_event_cb (vapi_ctx_t ctx, vapi_msg_id_t id, + vapi_event_cb callback, void *callback_ctx); /** * @brief clear event callback for given message id @@ -247,12 +221,12 @@ void vapi_set_event_cb (vapi_ctx_t ctx, vapi_msg_id_t id, * @param ctx opaque vapi context * @param id message id */ -void vapi_clear_event_cb (vapi_ctx_t ctx, vapi_msg_id_t id); + void vapi_clear_event_cb (vapi_ctx_t ctx, vapi_msg_id_t id); /** generic vapi event callback */ -typedef vapi_error_e (*vapi_generic_event_cb) (vapi_ctx_t ctx, - void *callback_ctx, - vapi_msg_id_t id, void *msg); + typedef vapi_error_e (*vapi_generic_event_cb) (vapi_ctx_t ctx, + void *callback_ctx, + vapi_msg_id_t id, void *msg); /** * @brief set generic event callback * @@ -263,16 +237,20 @@ typedef vapi_error_e (*vapi_generic_event_cb) (vapi_ctx_t ctx, * @param callback callback * @param callback_ctx context pointer stored and passed to callback */ -void vapi_set_generic_event_cb (vapi_ctx_t ctx, - vapi_generic_event_cb callback, - void *callback_ctx); + void vapi_set_generic_event_cb (vapi_ctx_t ctx, + vapi_generic_event_cb callback, + void *callback_ctx); /** * @brief clear generic event callback * * @param ctx opaque vapi context */ -void vapi_clear_generic_event_cb (vapi_ctx_t ctx); + void vapi_clear_generic_event_cb (vapi_ctx_t ctx); + +#ifdef __cplusplus +} +#endif #endif diff --git a/src/vpp-api/vapi/vapi.hpp b/src/vpp-api/vapi/vapi.hpp new file mode 100644 index 00000000..3be78b41 --- /dev/null +++ b/src/vpp-api/vapi/vapi.hpp @@ -0,0 +1,905 @@ +/* + *------------------------------------------------------------------ + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *------------------------------------------------------------------ + */ + +#ifndef vapi_hpp_included +#define vapi_hpp_included + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if VAPI_CPP_DEBUG_LEAKS +#include +#endif + +/** + * @file + * @brief C++ VPP API + */ + +namespace vapi +{ + +class Connection; + +template class Request; +template class Msg; +template void vapi_swap_to_be (M *msg); +template void vapi_swap_to_host (M *msg); +template +M *vapi_alloc (Connection &con, Args...); +template vapi_msg_id_t vapi_get_msg_id_t (); +template class Event_registration; + +class Unexpected_msg_id_exception : public std::exception +{ +public: + virtual const char *what () const throw () + { + return "unexpected message id"; + } +}; + +class Msg_not_available_exception : public std::exception +{ +public: + virtual const char *what () const throw () + { + return "message unavailable"; + } +}; + +typedef enum { + /** response not ready yet */ + RESPONSE_NOT_READY, + + /** response to request is ready */ + RESPONSE_READY, + + /** no response to request (will never come) */ + RESPONSE_NO_RESPONSE, +} vapi_response_state_e; + +/** + * Class representing common functionality of a request - response state + * and context + */ +class Common_req +{ +public: + virtual ~Common_req (){}; + + Connection &get_connection () + { + return con; + }; + + vapi_response_state_e get_response_state (void) const + { + return response_state; + } + +private: + Connection &con; + Common_req (Connection &con) : con{con}, response_state{RESPONSE_NOT_READY} + { + } + + void set_response_state (vapi_response_state_e state) + { + response_state = state; + } + + virtual std::tuple assign_response (vapi_msg_id_t id, + void *shm_data) = 0; + + void set_context (u32 context) + { + this->context = context; + } + + u32 get_context () + { + return context; + } + + u32 context; + vapi_response_state_e response_state; + + friend class Connection; + + template friend class Msg; + + template + friend class Request; + + template friend class Dump; + + template friend class Event_registration; +}; + +/** + * Class representing a connection to VPP + * + * After creating a Connection object, call connect() to actually connect + * to VPP. Use is_msg_available to discover whether a specific message is known + * and supported by the VPP connected to. + */ +class Connection +{ +public: + Connection (void) : vapi_ctx{0}, event_count{0} + { + + vapi_error_e rv = VAPI_OK; + if (!vapi_ctx) + { + if (VAPI_OK != (rv = vapi_ctx_alloc (&vapi_ctx))) + { + throw std::bad_alloc (); + } + } + events.reserve (vapi_get_message_count () + 1); + } + + Connection (const Connection &) = delete; + + ~Connection (void) + { + vapi_ctx_free (vapi_ctx); +#if VAPI_CPP_DEBUG_LEAKS + for (auto x : shm_data_set) + { + printf ("Leaked shm_data@%p!\n", x); + } +#endif + } + + /** + * @brief check if message identified by it's message id is known by the + * vpp to which the connection is open + */ + bool is_msg_available (vapi_msg_id_t type) + { + return vapi_is_msg_available (vapi_ctx, type); + } + + /** + * @brief connect to vpp + * + * @param name application name + * @param chroot_prefix shared memory prefix + * @param max_queued_request max number of outstanding requests queued + * + * @return VAPI_OK on success, other error code on error + */ + vapi_error_e connect (const char *name, const char *chroot_prefix, + int max_outstanding_requests, int response_queue_size) + { + return vapi_connect (vapi_ctx, name, chroot_prefix, + max_outstanding_requests, response_queue_size, + VAPI_MODE_BLOCKING); + } + + /** + * @brief disconnect from vpp + * + * @return VAPI_OK on success, other error code on error + */ + vapi_error_e disconnect () + { + auto x = requests.size (); + while (x > 0) + { + VAPI_DBG ("popping request @%p", requests.front ()); + requests.pop_front (); + --x; + } + return vapi_disconnect (vapi_ctx); + }; + + /** + * @brief get event file descriptor + * + * @note this file descriptor becomes readable when messages (from vpp) + * are waiting in queue + * + * @param[out] fd pointer to result variable + * + * @return VAPI_OK on success, other error code on error + */ + vapi_error_e get_fd (int *fd) + { + return vapi_get_fd (vapi_ctx, fd); + } + + /** + * @brief wait for responses from vpp and assign them to appropriate objects + * + * @param limit stop dispatch after the limit object received it's response + * + * @return VAPI_OK on success, other error code on error + */ + vapi_error_e dispatch (const Common_req *limit = nullptr) + { + std::lock_guard lock (dispatch_mutex); + vapi_error_e rv = VAPI_OK; + bool loop_again = true; + while (loop_again) + { + void *shm_data; + size_t shm_data_size; + rv = vapi_recv (vapi_ctx, &shm_data, &shm_data_size); + if (VAPI_OK != rv) + { + return rv; + } +#if VAPI_CPP_DEBUG_LEAKS + on_shm_data_alloc (shm_data); +#endif + std::lock_guard requests_lock (requests_mutex); + std::lock_guard events_lock (events_mutex); + vapi_msg_id_t id = vapi_lookup_vapi_msg_id_t ( + vapi_ctx, be16toh (*static_cast (shm_data))); + bool has_context = vapi_msg_is_with_context (id); + bool break_dispatch = false; + Common_req *matching_req = nullptr; + if (has_context) + { + u32 context = *reinterpret_cast ( + (static_cast (shm_data) + vapi_get_context_offset (id))); + const auto x = requests.front (); + matching_req = x; + if (context == x->context) + { + std::tie (rv, break_dispatch) = + x->assign_response (id, shm_data); + } + else + { + std::tie (rv, break_dispatch) = + x->assign_response (id, nullptr); + } + if (break_dispatch) + { + requests.pop_front (); + } + } + else + { + if (events[id]) + { + std::tie (rv, break_dispatch) = + events[id]->assign_response (id, shm_data); + matching_req = events[id]; + } + else + { + msg_free (shm_data); + } + } + if ((matching_req && matching_req == limit && break_dispatch) || + VAPI_OK != rv) + { + return rv; + } + loop_again = !requests.empty () || (event_count > 0); + } + return rv; + } + + /** + * @brief convenience wrapper function + */ + vapi_error_e dispatch (const Common_req &limit) + { + return dispatch (&limit); + } + + /** + * @brief wait for response to a specific request + * + * @param req request to wait for response for + * + * @return VAPI_OK on success, other error code on error + */ + vapi_error_e wait_for_response (const Common_req &req) + { + if (RESPONSE_READY == req.get_response_state ()) + { + return VAPI_OK; + } + return dispatch (req); + } + +private: + void msg_free (void *shm_data) + { +#if VAPI_CPP_DEBUG_LEAKS + on_shm_data_free (shm_data); +#endif + vapi_msg_free (vapi_ctx, shm_data); + } + + template