vici: Add a libvici low-level client library
authorMartin Willi <martin@revosec.ch>
Wed, 29 Jan 2014 10:20:20 +0000 (11:20 +0100)
committerMartin Willi <martin@revosec.ch>
Wed, 7 May 2014 12:13:35 +0000 (14:13 +0200)
src/libcharon/plugins/vici/Makefile.am
src/libcharon/plugins/vici/libvici.c [new file with mode: 0644]
src/libcharon/plugins/vici/libvici.h [new file with mode: 0644]
src/libcharon/plugins/vici/suites/test_event.c [new file with mode: 0644]
src/libcharon/plugins/vici/suites/test_request.c [new file with mode: 0644]
src/libcharon/plugins/vici/vici_tests.h

index 3723966..6495c18 100644 (file)
@@ -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 (file)
index 0000000..0776b6b
--- /dev/null
@@ -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 <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();
+}
diff --git a/src/libcharon/plugins/vici/libvici.h b/src/libcharon/plugins/vici/libvici.h
new file mode 100644 (file)
index 0000000..329c67c
--- /dev/null
@@ -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 <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_ @}*/
diff --git a/src/libcharon/plugins/vici/suites/test_event.c b/src/libcharon/plugins/vici/suites/test_event.c
new file mode 100644 (file)
index 0000000..771f127
--- /dev/null
@@ -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 <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;
+}
diff --git a/src/libcharon/plugins/vici/suites/test_request.c b/src/libcharon/plugins/vici/suites/test_request.c
new file mode 100644 (file)
index 0000000..1f124d3
--- /dev/null
@@ -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 <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;
+}
index bdc2965..3e8f170 100644 (file)
@@ -15,3 +15,5 @@
 
 TEST_SUITE(socket_suite_create)
 TEST_SUITE(message_suite_create)
+TEST_SUITE(request_suite_create)
+TEST_SUITE(event_suite_create)