| /* |
| * 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 "vici_socket.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_FAILURE, |
| WAIT_READ_ERROR, |
| } wait_state_t; |
| |
| /** |
| * Private vici connection context. |
| */ |
| 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; |
| /** section nesting level of callback parser */ |
| int level; |
| }; |
| |
| /** |
| * 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, uint32_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, uint32_t len) |
| { |
| vici_message_t *message; |
| event_t *event; |
| uint8_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) |
| { |
| uint32_t len; |
| uint8_t op; |
| ssize_t hlen; |
| |
| hlen = stream->read(stream, &len, sizeof(len), FALSE); |
| if (hlen <= 0) |
| { |
| if (errno == EWOULDBLOCK) |
| { |
| return TRUE; |
| } |
| return read_error(conn, errno); |
| } |
| if (hlen < sizeof(len)) |
| { |
| if (!stream->read_all(stream, ((void*)&len) + hlen, sizeof(len) - hlen)) |
| { |
| return read_error(conn, errno); |
| } |
| } |
| |
| len = ntohl(len); |
| if (len > VICI_MESSAGE_SIZE_MAX) |
| { |
| return read_error(conn, EBADMSG); |
| } |
| 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_FAILURE); |
| 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; |
| uint32_t len; |
| uint8_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 = htonl(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_FAILURE: |
| 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) |
| { |
| free(req->name); |
| req->b->destroy(req->b); |
| free(req); |
| } |
| |
| int vici_dump(vici_res_t *res, char *label, int pretty, FILE *out) |
| { |
| if (res->message->dump(res->message, label, pretty, out)) |
| { |
| return 0; |
| } |
| 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: |
| *len = 0; |
| 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 = EBADMSG; |
| return NULL; |
| } |
| val = strndup(res->value.ptr, res->value.len); |
| res->strings->insert_last(res->strings, val); |
| return val; |
| default: |
| errno = EINVAL; |
| return NULL; |
| } |
| } |
| |
| int vici_parse_cb(vici_res_t *res, vici_parse_section_cb_t section, |
| vici_parse_value_cb_t kv, vici_parse_value_cb_t li, |
| void *user) |
| { |
| char *name, *list = NULL; |
| void *value; |
| int base, len, ret; |
| |
| base = res->level; |
| |
| while (TRUE) |
| { |
| switch (vici_parse(res)) |
| { |
| case VICI_PARSE_KEY_VALUE: |
| if (res->level == base) |
| { |
| if (kv) |
| { |
| name = vici_parse_name(res); |
| value = vici_parse_value(res, &len); |
| if (name && value) |
| { |
| ret = kv(user, res, name, value, len); |
| if (ret) |
| { |
| return ret; |
| } |
| } |
| } |
| } |
| break; |
| case VICI_PARSE_BEGIN_SECTION: |
| if (res->level++ == base) |
| { |
| if (section) |
| { |
| name = vici_parse_name(res); |
| if (name) |
| { |
| ret = section(user, res, name); |
| if (ret) |
| { |
| return ret; |
| } |
| } |
| } |
| } |
| break; |
| case VICI_PARSE_END_SECTION: |
| if (res->level-- == base) |
| { |
| return 0; |
| } |
| break; |
| case VICI_PARSE_END: |
| res->level = 0; |
| return 0; |
| case VICI_PARSE_BEGIN_LIST: |
| if (res->level == base) |
| { |
| list = vici_parse_name(res); |
| } |
| break; |
| case VICI_PARSE_LIST_ITEM: |
| if (list && li) |
| { |
| value = vici_parse_value(res, &len); |
| if (value) |
| { |
| ret = li(user, res, list, value, len); |
| if (ret) |
| { |
| return ret; |
| } |
| } |
| } |
| break; |
| case VICI_PARSE_END_LIST: |
| if (res->level == base) |
| { |
| list = NULL; |
| } |
| break; |
| case VICI_PARSE_ERROR: |
| res->level = 0; |
| errno = EBADMSG; |
| return 1; |
| } |
| } |
| } |
| |
| void* vici_find(vici_res_t *res, int *len, char *fmt, ...) |
| { |
| va_list args; |
| chunk_t value; |
| |
| va_start(args, fmt); |
| value = res->message->vget_value(res->message, chunk_empty, fmt, args); |
| va_end(args); |
| |
| *len = value.len; |
| return value.ptr; |
| } |
| |
| char* vici_find_str(vici_res_t *res, char *def, char *fmt, ...) |
| { |
| va_list args; |
| char *str; |
| |
| va_start(args, fmt); |
| str = res->message->vget_str(res->message, def, fmt, args); |
| va_end(args); |
| |
| return str; |
| } |
| |
| int vici_find_int(vici_res_t *res, int def, char *fmt, ...) |
| { |
| va_list args; |
| int val; |
| |
| va_start(args, fmt); |
| val = res->message->vget_int(res->message, def, fmt, args); |
| va_end(args); |
| |
| return val; |
| } |
| |
| 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; |
| uint32_t len; |
| uint8_t namelen, op; |
| int ret = 1; |
| |
| op = cb ? VICI_EVENT_REGISTER : VICI_EVENT_UNREGISTER; |
| namelen = strlen(name); |
| len = htonl(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_FAILURE: |
| 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) |
| { |
| dbg_default_set_level(0); |
| lib->processor->set_threads(lib->processor, 4); |
| dbg_default_set_level(1); |
| } |
| } |
| |
| void vici_deinit() |
| { |
| lib->processor->cancel(lib->processor); |
| library_deinit(); |
| } |