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)
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 = \
--- /dev/null
+/*
+ * 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 <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * 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 <library.h>
+#include <threading/mutex.h>
+#include <threading/condvar.h>
+#include <collections/hashtable.h>
+
+#include <errno.h>
+
+/**
+ * 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();
+}
--- /dev/null
+/*
+ * 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 <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * 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 <stdio.h>
+
+/**
+ * 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_ @}*/
--- /dev/null
+/*
+ * 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 <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * 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 <test_suite.h>
+
+#include "../vici_dispatcher.h"
+#include "../libvici.h"
+
+#include <unistd.h>
+
+#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;
+}
--- /dev/null
+/*
+ * 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 <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * 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 <test_suite.h>
+
+#include "../vici_dispatcher.h"
+#include "../libvici.h"
+
+#include <unistd.h>
+
+#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;
+}
TEST_SUITE(socket_suite_create)
TEST_SUITE(message_suite_create)
+TEST_SUITE(request_suite_create)
+TEST_SUITE(event_suite_create)