vici: Add a libvici low-level client library

This commit is contained in:
Martin Willi 2014-01-29 11:20:20 +01:00
parent 8383d626b9
commit eb4fd014b8
6 changed files with 1413 additions and 0 deletions

View 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 = \

View File

@ -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();
}

View File

@ -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_ @}*/

View File

@ -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;
}

View File

@ -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;
}

View 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)