From eb4fd014b82e8f187581437fc2f67d97684ba544 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Wed, 29 Jan 2014 11:20:20 +0100 Subject: [PATCH] vici: Add a libvici low-level client library --- src/libcharon/plugins/vici/Makefile.am | 15 + src/libcharon/plugins/vici/libvici.c | 665 ++++++++++++++++++ src/libcharon/plugins/vici/libvici.h | 344 +++++++++ .../plugins/vici/suites/test_event.c | 144 ++++ .../plugins/vici/suites/test_request.c | 243 +++++++ src/libcharon/plugins/vici/vici_tests.h | 2 + 6 files changed, 1413 insertions(+) create mode 100644 src/libcharon/plugins/vici/libvici.c create mode 100644 src/libcharon/plugins/vici/libvici.h create mode 100644 src/libcharon/plugins/vici/suites/test_event.c create mode 100644 src/libcharon/plugins/vici/suites/test_request.c diff --git a/src/libcharon/plugins/vici/Makefile.am b/src/libcharon/plugins/vici/Makefile.am index 3723966ff..6495c1807 100644 --- a/src/libcharon/plugins/vici/Makefile.am +++ b/src/libcharon/plugins/vici/Makefile.am @@ -22,6 +22,17 @@ libstrongswan_vici_la_SOURCES = \ libstrongswan_vici_la_LDFLAGS = -module -avoid-version + +lib_LTLIBRARIES = libvici.la + +libvici_la_SOURCES = \ + vici_message.c vici_message.h \ + vici_builder.c vici_builder.h \ + libvici.c libvici.h + +libvici_la_LIBADD = $(top_builddir)/src/libstrongswan/libstrongswan.la + + TESTS = vici_tests check_PROGRAMS = $(TESTS) @@ -29,9 +40,13 @@ check_PROGRAMS = $(TESTS) vici_tests_SOURCES = \ suites/test_socket.c \ suites/test_message.c \ + suites/test_request.c \ + suites/test_event.c \ vici_socket.c \ vici_message.c \ vici_builder.c \ + vici_dispatcher.c \ + libvici.c \ vici_tests.h vici_tests.c vici_tests_CFLAGS = \ diff --git a/src/libcharon/plugins/vici/libvici.c b/src/libcharon/plugins/vici/libvici.c new file mode 100644 index 000000000..0776b6b9c --- /dev/null +++ b/src/libcharon/plugins/vici/libvici.c @@ -0,0 +1,665 @@ +/* + * Copyright (C) 2014 Martin Willi + * Copyright (C) 2014 revosec AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "libvici.h" +#include "vici_builder.h" +#include "vici_dispatcher.h" + +#include +#include +#include +#include + +#include + +/** + * Event registration + */ +typedef struct { + /** name of event */ + char *name; + /** callback function */ + vici_event_cb_t cb; + /** user data for callback */ + void *user; +} event_t; + +/** + * Wait state signaled by asynchronous on_read callback + */ +typedef enum { + WAIT_IDLE = 0, + WAIT_SUCCESS, + WAIT_FAILED, + WAIT_READ_ERROR, +} wait_state_t; + +/** + * Private vici connection contex. + */ +struct vici_conn_t { + /** connection stream */ + stream_t *stream; + /** event registrations, as char* => event_t */ + hashtable_t *events; + /** connection lock */ + mutex_t *mutex; + /** condvar to signal incoming response */ + condvar_t *cond; + /** queued response message */ + chunk_t queue; + /** asynchronous read error */ + int error; + /** wait state */ + wait_state_t wait; +}; + +/** + * Private vici request message. + */ +struct vici_req_t { + /** connection context */ + vici_conn_t *conn; + /** name of request message */ + char *name; + /** message builder */ + vici_builder_t *b; +}; + +/** + * Private vici response/event message. + */ +struct vici_res_t { + /** response message */ + vici_message_t *message; + /** allocated strings */ + linked_list_t *strings; + /** item enumerator */ + enumerator_t *enumerator; + /** currently enumerating type */ + vici_type_t type; + /** currently enumerating name */ + char *name; + /** currently enumerating value */ + chunk_t value; +}; + +/** + * Signal wait result for waiting user thread + */ +static bool wait_result(vici_conn_t *conn, wait_state_t wait) +{ + conn->mutex->lock(conn->mutex); + conn->wait = wait; + conn->mutex->unlock(conn->mutex); + conn->cond->signal(conn->cond); + return FALSE; +} + +/** + * Signal wait error result for waiting user thread + */ +static bool read_error(vici_conn_t *conn, int err) +{ + conn->error = err; + return wait_result(conn, WAIT_READ_ERROR); +} + +/** + * Handle a command response message + */ +static bool handle_response(vici_conn_t *conn, u_int16_t len) +{ + chunk_t buf; + + buf = chunk_alloc(len); + if (!conn->stream->read_all(conn->stream, buf.ptr, buf.len)) + { + free(buf.ptr); + return read_error(conn, errno); + } + conn->queue = buf; + return wait_result(conn, WAIT_SUCCESS); +} + +/** + * Dispatch received event message + */ +static bool handle_event(vici_conn_t *conn, u_int16_t len) +{ + vici_message_t *message; + event_t *event; + u_int8_t namelen; + char name[257], *buf; + + if (len < sizeof(namelen)) + { + return read_error(conn, EBADMSG); + } + if (!conn->stream->read_all(conn->stream, &namelen, sizeof(namelen))) + { + return read_error(conn, errno); + } + if (namelen > len - sizeof(namelen)) + { + return read_error(conn, EBADMSG); + } + if (!conn->stream->read_all(conn->stream, name, namelen)) + { + return read_error(conn, errno); + } + name[namelen] = '\0'; + len -= sizeof(namelen) + namelen; + buf = malloc(len); + if (!conn->stream->read_all(conn->stream, buf, len)) + { + free(buf); + return read_error(conn, errno); + } + message = vici_message_create_from_data(chunk_create(buf, len), TRUE); + + conn->mutex->lock(conn->mutex); + event = conn->events->get(conn->events, name); + if (event) + { + vici_res_t res = { + .message = message, + .enumerator = message->create_enumerator(message), + .strings = linked_list_create(), + }; + + event->cb(event->user, name, &res); + + res.enumerator->destroy(res.enumerator); + res.strings->destroy_function(res.strings, free); + } + conn->mutex->unlock(conn->mutex); + + message->destroy(message); + + return TRUE; +} + +CALLBACK(on_read, bool, + vici_conn_t *conn, stream_t *stream) +{ + u_int16_t len; + u_int8_t op; + + if (!stream->read_all(stream, &len, sizeof(len))) + { + return read_error(conn, errno); + } + len = ntohs(len); + if (len-- < sizeof(op)) + { + return read_error(conn, EBADMSG); + } + if (!stream->read_all(stream, &op, sizeof(op))) + { + return read_error(conn, errno); + } + switch (op) + { + case VICI_EVENT: + return handle_event(conn, len); + case VICI_CMD_RESPONSE: + return handle_response(conn, len); + case VICI_EVENT_CONFIRM: + return wait_result(conn, WAIT_SUCCESS); + case VICI_CMD_UNKNOWN: + case VICI_EVENT_UNKNOWN: + return wait_result(conn, WAIT_FAILED); + case VICI_CMD_REQUEST: + case VICI_EVENT_REGISTER: + case VICI_EVENT_UNREGISTER: + default: + return read_error(conn, EBADMSG); + } +} + +vici_conn_t* vici_connect(char *uri) +{ + vici_conn_t *conn; + stream_t *stream; + + stream = lib->streams->connect(lib->streams, uri ?: VICI_DEFAULT_URI); + if (!stream) + { + return NULL; + } + + INIT(conn, + .stream = stream, + .events = hashtable_create(hashtable_hash_str, hashtable_equals_str, 1), + .mutex = mutex_create(MUTEX_TYPE_DEFAULT), + .cond = condvar_create(CONDVAR_TYPE_DEFAULT), + ); + + stream->on_read(stream, on_read, conn); + + return conn; +} + +void vici_disconnect(vici_conn_t *conn) +{ + enumerator_t *enumerator; + event_t *event; + + conn->stream->destroy(conn->stream); + enumerator = conn->events->create_enumerator(conn->events); + while (enumerator->enumerate(enumerator, NULL, &event)) + { + free(event->name); + free(event); + } + enumerator->destroy(enumerator); + conn->events->destroy(conn->events); + conn->mutex->destroy(conn->mutex); + conn->cond->destroy(conn->cond); + free(conn); +} + +vici_req_t* vici_begin(char *name) +{ + vici_req_t *req; + + INIT(req, + .name = strdup(name), + .b = vici_builder_create(), + ); + + return req; +} + +void vici_begin_section(vici_req_t *req, char *name) +{ + req->b->add(req->b, VICI_SECTION_START, name); +} + +void vici_end_section(vici_req_t *req) +{ + req->b->add(req->b, VICI_SECTION_END); +} + +void vici_add_key_value(vici_req_t *req, char *key, void *buf, int len) +{ + req->b->add(req->b, VICI_KEY_VALUE, key, chunk_create(buf, len)); +} + +void vici_add_key_valuef(vici_req_t *req, char *key, char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + req->b->vadd_kv(req->b, key, fmt, args); + va_end(args); +} + +void vici_begin_list(vici_req_t *req, char *name) +{ + req->b->add(req->b, VICI_LIST_START, name); +} + +void vici_add_list_item(vici_req_t *req, void *buf, int len) +{ + req->b->add(req->b, VICI_LIST_ITEM, chunk_create(buf, len)); +} + +void vici_add_list_itemf(vici_req_t *req, char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + req->b->vadd_li(req->b, fmt, args); + va_end(args); +} + +void vici_end_list(vici_req_t *req) +{ + req->b->add(req->b, VICI_LIST_END); +} + +vici_res_t* vici_submit(vici_req_t *req, vici_conn_t *conn) +{ + vici_message_t *message; + vici_res_t *res; + chunk_t data; + u_int16_t len; + u_int8_t namelen, op; + + message = req->b->finalize(req->b); + if (!message) + { + errno = EINVAL; + return NULL; + } + + op = VICI_CMD_REQUEST; + namelen = strlen(req->name); + data = message->get_encoding(message); + len = htons(sizeof(op) + sizeof(namelen) + namelen + data.len); + + if (!conn->stream->write_all(conn->stream, &len, sizeof(len)) || + !conn->stream->write_all(conn->stream, &op, sizeof(op)) || + !conn->stream->write_all(conn->stream, &namelen, sizeof(namelen)) || + !conn->stream->write_all(conn->stream, req->name, namelen) || + !conn->stream->write_all(conn->stream, data.ptr, data.len)) + { + free(req->name); + free(req); + message->destroy(message); + return NULL; + } + free(req->name); + free(req); + message->destroy(message); + + message = NULL; + conn->mutex->lock(conn->mutex); + while (conn->wait == WAIT_IDLE) + { + conn->cond->wait(conn->cond, conn->mutex); + } + switch (conn->wait) + { + case WAIT_SUCCESS: + message = vici_message_create_from_data(conn->queue, TRUE); + conn->queue = chunk_empty; + break; + case WAIT_READ_ERROR: + errno = conn->error; + break; + case WAIT_FAILED: + default: + errno = ENOENT; + break; + } + conn->wait = WAIT_IDLE; + conn->mutex->unlock(conn->mutex); + + conn->stream->on_read(conn->stream, on_read, conn); + + if (message) + { + INIT(res, + .message = message, + .enumerator = message->create_enumerator(message), + .strings = linked_list_create(), + ); + return res; + } + return NULL; +} + +void vici_free_req(vici_req_t *req) +{ + vici_message_t *message; + + free(req->name); + message = req->b->finalize(req->b); + if (message) + { + message->destroy(message); + } + free(req); +} + +int vici_dump(vici_res_t *res, FILE *out) +{ + enumerator_t *enumerator; + int ident = 0, delta = 2; + vici_type_t type; + char *name; + chunk_t value; + + enumerator = res->message->create_enumerator(res->message); + while (enumerator->enumerate(enumerator, &type, &name, &value)) + { + switch (type) + { + case VICI_SECTION_START: + fprintf(out, "%*s%s {\n", ident, "", name); + ident += delta; + break; + case VICI_SECTION_END: + ident -= delta; + fprintf(out, "%*s}\n", ident, ""); + break; + case VICI_KEY_VALUE: + if (chunk_printable(value, NULL, ' ')) + { + fprintf(out, "%*s%s = %.*s\n", + ident, "", name, (int)value.len, value.ptr); + } + else + { + fprintf(out, "%*s%s = 0x%+#B\n", + ident, "", name, &value); + } + break; + case VICI_LIST_START: + fprintf(out, "%*s%s = [\n", ident, "", name); + ident += delta; + break; + case VICI_LIST_END: + ident -= delta; + fprintf(out, "%*s]\n", ident, ""); + break; + case VICI_LIST_ITEM: + if (chunk_printable(value, NULL, ' ')) + { + fprintf(out, "%*s%.*s\n", + ident, "", (int)value.len, value.ptr); + } + else + { + fprintf(out, "%*s 0x%+#B\n", ident, "", &value); + } + break; + case VICI_END: + enumerator->destroy(enumerator); + return 0; + } + } + enumerator->destroy(enumerator); + errno = EBADMSG; + return 1; +} + +vici_parse_t vici_parse(vici_res_t *res) +{ + if (!res->enumerator->enumerate(res->enumerator, + &res->type, &res->name, &res->value)) + { + return VICI_PARSE_ERROR; + } + switch (res->type) + { + case VICI_END: + return VICI_PARSE_END; + case VICI_SECTION_START: + return VICI_PARSE_BEGIN_SECTION; + case VICI_SECTION_END: + return VICI_PARSE_END_SECTION; + case VICI_LIST_START: + return VICI_PARSE_BEGIN_LIST; + case VICI_LIST_ITEM: + return VICI_PARSE_LIST_ITEM; + case VICI_LIST_END: + return VICI_PARSE_END_LIST; + case VICI_KEY_VALUE: + return VICI_PARSE_KEY_VALUE; + default: + return VICI_PARSE_ERROR; + } +} + +char* vici_parse_name(vici_res_t *res) +{ + char *name; + + switch (res->type) + { + case VICI_SECTION_START: + case VICI_LIST_START: + case VICI_KEY_VALUE: + name = strdup(res->name); + res->strings->insert_last(res->strings, name); + return name; + default: + errno = EINVAL; + return NULL; + } +} + +int vici_parse_name_eq(vici_res_t *res, char *name) +{ + switch (res->type) + { + case VICI_SECTION_START: + case VICI_LIST_START: + case VICI_KEY_VALUE: + return streq(name, res->name) ? 1 : 0; + default: + return 0; + } +} + +void* vici_parse_value(vici_res_t *res, int *len) +{ + switch (res->type) + { + case VICI_LIST_ITEM: + case VICI_KEY_VALUE: + *len = res->value.len; + return res->value.ptr; + default: + errno = EINVAL; + return NULL; + } +} + +char* vici_parse_value_str(vici_res_t *res) +{ + char *val; + + switch (res->type) + { + case VICI_LIST_ITEM: + case VICI_KEY_VALUE: + if (!chunk_printable(res->value, NULL, 0)) + { + errno = EINVAL; + return NULL; + } + val = strndup(res->value.ptr, res->value.len); + res->strings->insert_last(res->strings, val); + return val; + default: + errno = EINVAL; + return NULL; + } +} + +void vici_free_res(vici_res_t *res) +{ + res->strings->destroy_function(res->strings, free); + res->message->destroy(res->message); + res->enumerator->destroy(res->enumerator); + free(res); +} + +int vici_register(vici_conn_t *conn, char *name, vici_event_cb_t cb, void *user) +{ + event_t *event; + u_int16_t len; + u_int8_t namelen, op; + int ret = 1; + + op = cb ? VICI_EVENT_REGISTER : VICI_EVENT_UNREGISTER; + namelen = strlen(name); + len = htons(sizeof(op) + sizeof(namelen) + namelen); + if (!conn->stream->write_all(conn->stream, &len, sizeof(len)) || + !conn->stream->write_all(conn->stream, &op, sizeof(op)) || + !conn->stream->write_all(conn->stream, &namelen, sizeof(namelen)) || + !conn->stream->write_all(conn->stream, name, namelen)) + { + return 1; + } + + conn->mutex->lock(conn->mutex); + while (conn->wait == WAIT_IDLE) + { + conn->cond->wait(conn->cond, conn->mutex); + } + switch (conn->wait) + { + case WAIT_SUCCESS: + ret = 0; + break; + case WAIT_READ_ERROR: + errno = conn->error; + break; + case WAIT_FAILED: + default: + errno = ENOENT; + break; + } + conn->wait = WAIT_IDLE; + conn->mutex->unlock(conn->mutex); + + conn->stream->on_read(conn->stream, on_read, conn); + + if (ret == 0) + { + conn->mutex->lock(conn->mutex); + if (cb) + { + INIT(event, + .name = strdup(name), + .cb = cb, + .user = user, + ); + event = conn->events->put(conn->events, event->name, event); + } + else + { + event = conn->events->remove(conn->events, name); + } + conn->mutex->unlock(conn->mutex); + + if (event) + { + free(event->name); + free(event); + } + } + return ret; +} + +void vici_init() +{ + library_init(NULL, "vici"); + if (lib->processor->get_total_threads(lib->processor) < 4) + { + lib->processor->set_threads(lib->processor, 4); + } +} + +void vici_deinit() +{ + library_deinit(); +} diff --git a/src/libcharon/plugins/vici/libvici.h b/src/libcharon/plugins/vici/libvici.h new file mode 100644 index 000000000..329c67c47 --- /dev/null +++ b/src/libcharon/plugins/vici/libvici.h @@ -0,0 +1,344 @@ +/* + * Copyright (C) 2014 Martin Willi + * Copyright (C) 2014 revosec AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +/** + * @defgroup libvici libvici + * @{ @ingroup vici + * + * libvici is a low-level client library for the "Versatile IKE Control + * Interface" protocol. While it uses libstrongswan and its thread-pool for + * asynchronous message delivery, this interface does not directly depend on + * libstrongswan interfaces and should be stable. + * + * This interface provides the following basic functions: + * + * - vici_init()/vici_deinit(): Library initialization functions + * - vici_connect(): Connect to a vici service + * - vici_disconnect(): Disconnect from a vici service + * + * Library initialization is basically required to set up libstrongswan and + * a small thread pool. Initialize libstrongswan manually instead. + * + * Connecting requires an uri, which is currently either a UNIX socket path + * prefixed with unix://, or a hostname:port touple prefixed with tcp://. + * Passing NULL takes the system default socket path. + * + * After the connection has been established, request messages can be sent. + * Only a single thread may operate on a single connection instance + * simultaneously. To construct request messages, use the following functions: + * + * - vici_add_key_value() / vici_add_key_valuef(): Add key/value pairs + * - vici_begin(): Start constructing a new request message + * - vici_begin_section(): Open a new section to add contents to + * - vici_end_section(): Close a previously opened session + * - vici_begin_list(): Open a new list to add list items to + * - vici_end_list(): Close a previously opened list + * - vici_add_list_item() / vici_add_list_itemf(): Add list item + * + * Once the request message is complete, it can be sent or cancelled with: + * + * - vici_submit() + * - vici_free_req() + * + * If submitting a message is successful, a response message is returned. It + * can be processed using the following functions: + * + * - vici_parse(): Parse content type + * - vici_parse_name(): Parse name if content type provides one + * - vici_parse_name_eq(): Parse name and check if matches string + * - vici_parse_value() / vici_parse_value_str(): Parse value for content type + * - vici_dump(): Dump a full response to a FILE stream + * - vici_free_res(): Free response after use + * + * Usually vici_parse() is called in a loop, and depending on the returned + * type the name and value can be inspected. + * + * To register or unregister for asynchronous event messages vici_register() is + * used. The registered callback gets invoked by an asynchronous thread. To + * parse the event message, the vici_parse*() functions can be used. + */ + +#ifndef LIBVICI_H_ +#define LIBVICI_H_ + +#include + +/** + * Opaque vici connection contex. + */ +typedef struct vici_conn_t vici_conn_t; + +/** + * Opaque vici request message. + */ +typedef struct vici_req_t vici_req_t; + +/** + * Opaque vici response/event message. + */ +typedef struct vici_res_t vici_res_t; + +/** + * Vici parse result, as returned by vici_parse(). + */ +typedef enum { + /** encountered a section start, has a name */ + VICI_PARSE_BEGIN_SECTION, + /** encountered a section end */ + VICI_PARSE_END_SECTION, + /** encountered a list start, has a name */ + VICI_PARSE_BEGIN_LIST, + /** encountered a list element, has a value */ + VICI_PARSE_LIST_ITEM, + /** encountered a list end */ + VICI_PARSE_END_LIST, + /** encountered a key/value pair, has a name and a value */ + VICI_PARSE_KEY_VALUE, + /** encountered valid end of message */ + VICI_PARSE_END, + /** parse error */ + VICI_PARSE_ERROR, +} vici_parse_t; + +/** + * Callback function invoked for received event messages. + * + * It is not allowed to call vici_submit() from this callback. + * + * @param user user data, as passed to vici_connect + * @param name name of received event + * @param msg associated event message, destroyed by libvici + */ +typedef void (*vici_event_cb_t)(void *user, char *name, vici_res_t *msg); + +/** + * Open a new vici connection. + * + * On error, NULL is returned and errno is set appropriately. + * + * @param uri URI to connect to, NULL to use system default + * @return opaque vici connection context, NULL on error + */ +vici_conn_t* vici_connect(char *uri); + +/** + * Close a vici connection. + * + * @param conn connection context + */ +void vici_disconnect(vici_conn_t *conn); + +/** + * Begin a new vici message request. + * + * This function always succeeds. + * + * @param name name of request command + * @return request message, to add contents + */ +vici_req_t* vici_begin(char *name); + +/** + * Begin a new section in a vici request message. + * + * @param req request message to create a new section in + * @param name name of section to create + */ +void vici_begin_section(vici_req_t *req, char *name); + +/** + * End a previously opened section. + * + * @param req request message to close an open section in + */ +void vici_end_section(vici_req_t *req); + +/** + * Add a key/value pair, using an as-is blob as value. + * + * @param req request message to add key/value pair to + * @param key key name of key/value pair + * @param buf pointer to blob to add as value + * @param len length of value blob to add + */ +void vici_add_key_value(vici_req_t *req, char *key, void *buf, int len); + +/** + * Add a key/value pair, setting value from a printf() format string. + * + * @param req request message to add key/value pair to + * @param key key name of key/value pair + * @param fmt format string for value + * @param ... arguments to format string + */ +void vici_add_key_valuef(vici_req_t *req, char *key, char *fmt, ...); + +/** + * Begin a list in a request message. + * + * After starting a list, only list items can be added until the list gets + * closed by vici_end_list(). + * + * @param req request message to begin list in + * @param name name of list to begin + */ +void vici_begin_list(vici_req_t *req, char *name); + +/** + * Add a list item to a currently open list, using an as-is blob. + * + * @param req request message to add list item to + * @param buf pointer to blob to add as value + * @param len length of value blob to add + */ +void vici_add_list_item(vici_req_t *req, void *buf, int len); + +/** + * Add a list item to a currently open list, using a printf() format string. + * + * @param req request message to add list item to + * @param fmt format string to create value from + * @param ... arguments to format string + */ +void vici_add_list_itemf(vici_req_t *req, char *fmt, ...); + +/** + * End a previously opened list in a request message. + * + * @param req request message to end list in + */ +void vici_end_list(vici_req_t *req); + +/** + * Submit a request message, and wait for response. + * + * On error, NULL is returned an errno is set appropriately. The request + * messages gets cleaned up by this call and gets invalid. + * + * @param req request message to send + * @param conn connection context to send message over + * @return response message, NULL on error + */ +vici_res_t* vici_submit(vici_req_t *req, vici_conn_t *conn); + +/** + * Cancel a request message started. + * + * If a request created by vici_begin() does not get submitted using + * vici_submit(), it has to get freed using this call. + * + * @param req request message to clean up + */ +void vici_free_req(vici_req_t *req); + +/** + * Dump a message text representation to a FILE stream. + * + * @param res response message to dump + * @param out FILE to dump to + * @return 0 if dumped complete message, 1 on error + */ +int vici_dump(vici_res_t *res, FILE *out); + +/** + * Parse next element from a vici response message. + * + * @param res response message to parse + * @return parse result + */ +vici_parse_t vici_parse(vici_res_t *res); + +/** + * Parse name tag / key of a previously parsed element. + * + * This call is valid only after vici_parse() returned VICI_PARSE_KEY_VALUE, + * VICI_PARSE_BEGIN_SECTION or VICI_PARSE_BEGIN_LIST. + * + * The string is valid until vici_free_res() is called. + * + * @param res response message to parse + * @return name tag / key, NULL on error + */ +char* vici_parse_name(vici_res_t *res); + +/** + * Compare name tag / key of a previusly parsed element. + * + * This call is valid only after vici_parse() returned VICI_PARSE_KEY_VALUE, + * VICI_PARSE_BEGIN_SECTION or VICI_PARSE_BEGIN_LIST. + * + * @param res response message to parse + * @param name string to compare + * @return 1 if name equals, 0 if not + */ +int vici_parse_name_eq(vici_res_t *res, char *name); + +/** + * Parse value of a previously parsed element, as a blob. + * + * This call is valid only after vici_parse() returned VICI_PARSE_KEY_VALUE or + * VICI_PARSE_LIST_ITEM. + * The string is valid until vici_free_res() is called. + */ +void* vici_parse_value(vici_res_t *res, int *len); + +/** + * Parse value of a previously parsed element, as a string. + * + * This call is valid only after vici_parse() returned VICI_PARSE_KEY_VALUE or + * VICI_PARSE_LIST_ITEM. + * This call is successful only if the value contains no non-printable + * characters. The string is valid until vici_free_res() is called. + * + * @param res response message to parse + * @return value as string, NULL on error + */ +char* vici_parse_value_str(vici_res_t *res); + +/** + * Clean up a received response message. + * + * Event messages get cleaned up by the library, it is not allowed to call + * vici_free_res() from within a vici_event_cb_t. + * + * @param res response message to free + */ +void vici_free_res(vici_res_t *res); + +/** + * (Un-)Register for events of a given kind. + * + * Events callbacks get invoked by a different thread from the libstrongswan + * thread pool. On failure, errno is set appropriately. + * + * @param conn connection context + * @param name name of event messages to register to + * @param cb callback function to register, NULL to unregister + * @param user user data passed to callback invocations + * @return 0 if registered successfully + */ +int vici_register(vici_conn_t *conn, char *name, vici_event_cb_t cb, void *user); + +/** + * Initialize libvici before first time use. + */ +void vici_init(); + +/** + * Deinitialize libvici after use. + */ +void vici_deinit(); + +#endif /** LIBVICI_H_ @}*/ diff --git a/src/libcharon/plugins/vici/suites/test_event.c b/src/libcharon/plugins/vici/suites/test_event.c new file mode 100644 index 000000000..771f12701 --- /dev/null +++ b/src/libcharon/plugins/vici/suites/test_event.c @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2014 Martin Willi + * Copyright (C) 2014 revosec AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include + +#include "../vici_dispatcher.h" +#include "../libvici.h" + +#include + +#define URI "unix:///tmp/strongswan-vici-event-test" + +static void event_cb(void *user, char *name, vici_res_t *ev) +{ + int *count = (int*)user; + + ck_assert_str_eq(name, "test"); + ck_assert(vici_parse(ev) == VICI_PARSE_KEY_VALUE); + ck_assert_str_eq(vici_parse_name(ev), "key1"); + ck_assert_str_eq(vici_parse_value_str(ev), "value1"); + ck_assert(vici_parse(ev) == VICI_PARSE_END); + + (*count)++; +} + +START_TEST(test_event) +{ + vici_dispatcher_t *dispatcher; + vici_conn_t *conn; + int count = 0; + + lib->processor->set_threads(lib->processor, 8); + + dispatcher = vici_dispatcher_create(URI); + ck_assert(dispatcher); + + dispatcher->manage_event(dispatcher, "test", TRUE); + + vici_init(); + conn = vici_connect(URI); + ck_assert(conn); + + ck_assert(vici_register(conn, "test", event_cb, &count) == 0); + ck_assert(vici_register(conn, "nonexistent", event_cb, &count) != 0); + + dispatcher->raise_event(dispatcher, "test", vici_message_create_from_args( + VICI_KEY_VALUE, "key1", chunk_from_str("value1"), + VICI_END)); + + while (count == 0) + { + usleep(1000); + } + + vici_disconnect(conn); + + dispatcher->manage_event(dispatcher, "test", FALSE); + + lib->processor->cancel(lib->processor); + dispatcher->destroy(dispatcher); + + vici_deinit(); +} +END_TEST + +START_TEST(test_stress) +{ + vici_dispatcher_t *dispatcher; + vici_conn_t *conn; + int count = 0, i, total = 50; + + lib->processor->set_threads(lib->processor, 8); + + dispatcher = vici_dispatcher_create(URI); + ck_assert(dispatcher); + + dispatcher->manage_event(dispatcher, "test", TRUE); + dispatcher->manage_event(dispatcher, "dummy", TRUE); + + vici_init(); + conn = vici_connect(URI); + ck_assert(conn); + + vici_register(conn, "test", event_cb, &count); + + for (i = 0; i < total; i++) + { + /* do some event re/deregistration in between */ + ck_assert(vici_register(conn, "dummy", event_cb, NULL) == 0); + + dispatcher->raise_event(dispatcher, "test", + vici_message_create_from_args( + VICI_KEY_VALUE, "key1", chunk_from_str("value1"), + VICI_END)); + + ck_assert(vici_register(conn, "dummy", NULL, NULL) == 0); + } + + while (count < total) + { + usleep(1000); + } + + vici_disconnect(conn); + + dispatcher->manage_event(dispatcher, "test", FALSE); + dispatcher->manage_event(dispatcher, "dummy", FALSE); + + lib->processor->cancel(lib->processor); + dispatcher->destroy(dispatcher); + + vici_deinit(); +} +END_TEST + +Suite *event_suite_create() +{ + Suite *s; + TCase *tc; + + s = suite_create("vici events"); + + tc = tcase_create("single"); + tcase_add_test(tc, test_event); + suite_add_tcase(s, tc); + + tc = tcase_create("stress"); + tcase_add_test(tc, test_stress); + suite_add_tcase(s, tc); + + return s; +} diff --git a/src/libcharon/plugins/vici/suites/test_request.c b/src/libcharon/plugins/vici/suites/test_request.c new file mode 100644 index 000000000..1f124d3dc --- /dev/null +++ b/src/libcharon/plugins/vici/suites/test_request.c @@ -0,0 +1,243 @@ +/* + * Copyright (C) 2014 Martin Willi + * Copyright (C) 2014 revosec AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include + +#include "../vici_dispatcher.h" +#include "../libvici.h" + +#include + +#define URI "unix:///tmp/strongswan-vici-request-test" + +static void encode_section(vici_req_t *req) +{ + vici_begin_section(req, "section1"); + vici_add_key_valuef(req, "key1", "value%u", 1); + vici_add_key_value(req, "key2", "value2", strlen("value2")); + vici_end_section(req); +} + +static void decode_section(vici_res_t *res) +{ + char *str; + int len; + + ck_assert(vici_parse(res) == VICI_PARSE_BEGIN_SECTION); + ck_assert_str_eq(vici_parse_name(res), "section1"); + ck_assert(vici_parse(res) == VICI_PARSE_KEY_VALUE); + ck_assert_str_eq(vici_parse_name(res), "key1"); + ck_assert_str_eq(vici_parse_value_str(res), "value1"); + ck_assert(vici_parse(res) == VICI_PARSE_KEY_VALUE); + ck_assert_str_eq(vici_parse_name(res), "key2"); + str = vici_parse_value(res, &len); + ck_assert(chunk_equals(chunk_from_str("value2"), chunk_create(str, len))); + ck_assert(vici_parse(res) == VICI_PARSE_END_SECTION); + ck_assert(vici_parse(res) == VICI_PARSE_END); +} + +static void encode_list(vici_req_t *req) +{ + vici_begin_list(req, "list1"); + vici_add_list_item(req, "item1", strlen("item1")); + vici_add_list_itemf(req, "item%u", 2); + vici_end_list(req); +} + +static void decode_list(vici_res_t *res) +{ + char *str; + int len; + + ck_assert(vici_parse(res) == VICI_PARSE_BEGIN_LIST); + ck_assert_str_eq(vici_parse_name(res), "list1"); + ck_assert(vici_parse(res) == VICI_PARSE_LIST_ITEM); + ck_assert_str_eq(vici_parse_value_str(res), "item1"); + ck_assert(vici_parse(res) == VICI_PARSE_LIST_ITEM); + str = vici_parse_value(res, &len); + ck_assert(chunk_equals(chunk_from_str("item2"), chunk_create(str, len))); + ck_assert(vici_parse(res) == VICI_PARSE_END_LIST); + ck_assert(vici_parse(res) == VICI_PARSE_END); +} + +static struct { + void (*encode)(vici_req_t* req); + void (*decode)(vici_res_t* res); +} echo_tests[] = { + { encode_section, decode_section }, + { encode_list, decode_list }, +}; + +static vici_message_t* echo_cb(void *user, char *name, + u_int id, vici_message_t *request) +{ + ck_assert_str_eq(name, "echo"); + ck_assert_int_eq((uintptr_t)user, 1); + + return vici_message_create_from_enumerator(request->create_enumerator(request)); +} + +START_TEST(test_echo) +{ + vici_dispatcher_t *dispatcher; + vici_conn_t *conn; + vici_req_t *req; + vici_res_t *res; + + lib->processor->set_threads(lib->processor, 8); + + dispatcher = vici_dispatcher_create(URI); + ck_assert(dispatcher); + + dispatcher->manage_command(dispatcher, "echo", echo_cb, (void*)(uintptr_t)1); + + vici_init(); + conn = vici_connect(URI); + ck_assert(conn); + + req = vici_begin("echo"); + echo_tests[_i].encode(req); + res = vici_submit(req, conn); + ck_assert(res); + echo_tests[_i].decode(res); + vici_free_res(res); + + vici_disconnect(conn); + + dispatcher->manage_command(dispatcher, "echo", NULL, NULL); + + lib->processor->cancel(lib->processor); + dispatcher->destroy(dispatcher); + + vici_deinit(); +} +END_TEST + +START_TEST(test_missing) +{ + vici_dispatcher_t *dispatcher; + vici_conn_t *conn; + vici_req_t *req; + vici_res_t *res; + + lib->processor->set_threads(lib->processor, 8); + + dispatcher = vici_dispatcher_create(URI); + ck_assert(dispatcher); + + vici_init(); + conn = vici_connect(URI); + ck_assert(conn); + + req = vici_begin("nonexistent"); + encode_section(req); + res = vici_submit(req, conn); + ck_assert(res == NULL); + + vici_disconnect(conn); + + dispatcher->manage_command(dispatcher, "echo", NULL, NULL); + + lib->processor->cancel(lib->processor); + dispatcher->destroy(dispatcher); + + vici_deinit(); +} +END_TEST + +static void event_cb(void *user, char *name, vici_res_t *ev) +{ + int *events = (int*)user; + + (*events)++; +} + +START_TEST(test_stress) +{ + vici_dispatcher_t *dispatcher; + vici_conn_t *conn; + vici_req_t *req; + vici_res_t *res; + int i, total = 50, events = 0; + + lib->processor->set_threads(lib->processor, 8); + + dispatcher = vici_dispatcher_create(URI); + ck_assert(dispatcher); + + dispatcher->manage_command(dispatcher, "echo", echo_cb, (void*)(uintptr_t)1); + dispatcher->manage_event(dispatcher, "dummy", TRUE); + + vici_init(); + conn = vici_connect(URI); + ck_assert(conn); + + for (i = 0; i < total; i++) + { + /* do some event management in between */ + ck_assert(vici_register(conn, "dummy", event_cb, &events) == 0); + dispatcher->raise_event(dispatcher, "dummy", + vici_message_create_from_args( + VICI_KEY_VALUE, "key1", chunk_from_str("value1"), + VICI_END)); + + req = vici_begin("echo"); + encode_section(req); + res = vici_submit(req, conn); + ck_assert(res); + decode_section(res); + vici_free_res(res); + + ck_assert(vici_register(conn, "dummy", NULL, NULL) == 0); + } + + while (events < total) + { + usleep(1000); + } + + vici_disconnect(conn); + + dispatcher->manage_command(dispatcher, "echo", NULL, NULL); + dispatcher->manage_event(dispatcher, "dummy", FALSE); + + lib->processor->cancel(lib->processor); + dispatcher->destroy(dispatcher); + + vici_deinit(); +} +END_TEST + +Suite *request_suite_create() +{ + Suite *s; + TCase *tc; + + s = suite_create("vici request"); + + tc = tcase_create("echo"); + tcase_add_loop_test(tc, test_echo, 0, countof(echo_tests)); + suite_add_tcase(s, tc); + + tc = tcase_create("missing"); + tcase_add_test(tc, test_missing); + suite_add_tcase(s, tc); + + tc = tcase_create("stress"); + tcase_add_test(tc, test_stress); + suite_add_tcase(s, tc); + + return s; +} diff --git a/src/libcharon/plugins/vici/vici_tests.h b/src/libcharon/plugins/vici/vici_tests.h index bdc296590..3e8f170e4 100644 --- a/src/libcharon/plugins/vici/vici_tests.h +++ b/src/libcharon/plugins/vici/vici_tests.h @@ -15,3 +15,5 @@ TEST_SUITE(socket_suite_create) TEST_SUITE(message_suite_create) +TEST_SUITE(request_suite_create) +TEST_SUITE(event_suite_create)