# HG changeset patch # User Tero Marttila # Date 1243462656 -10800 # Node ID cefec18b8268668a90d1e759f5d8306b6ffec234 # Parent 5229a5d098b23dd1164eb9ec220000b4f49b9eeb some of the lib/transport stuff compiles diff -r 5229a5d098b2 -r cefec18b8268 src/CMakeLists.txt --- a/src/CMakeLists.txt Thu May 28 00:35:02 2009 +0300 +++ b/src/CMakeLists.txt Thu May 28 01:17:36 2009 +0300 @@ -14,13 +14,13 @@ # define our source code modules set (LIB_SOURCES lib/error.c lib/log.c lib/str.c lib/object.c) -set (IO_SOURCES transport.c service.c transport_fd.c sock.c resolve.c tcp.c tcp_transport.c tcp_client.c tcp_server.c ssl.c ssl_client.c fifo.c) -set (PROTO_SOURCES line_proto.c msg_proto.c) +set (IO_SOURCES lib/transport.c lib/service.c lib/transport_fd.c lib/sock.c lib/resolve.c lib/tcp.c lib/tcp_transport.c lib/tcp_client.c lib/tcp_server.c lib/ssl.c lib/ssl_client.c lib/fifo.c) +set (PROTO_SOURCES lib/line_proto.c lib/msg_proto.c) set (IRC_SOURCES irc_line.c irc_conn.c irc_net.c irc_chan.c chain.c irc_cmd.c irc_proto.c irc_client.c irc_user.c irc_queue.c irc_net_connect.c) -set (LUA_SOURCES nexus_lua.c lua_objs.c lua_config.c lua_irc.c lua_func.c lua_type.c lua_thread.c) -set (CONSOLE_SOURCES console.c lua_console.c) +set (LUA_SOURCES spbot/lua_objs.c spbot/lua_config.c lua_irc.c lib/lua_func.c lib/lua_type.c spbot/lua_thread.c) +set (CONSOLE_SOURCES lib/console.c spbot/lua_console.c) -set (SPBOT_SOURCES spbot/nexus.c spbot/signals.c spbot/module.c spbot/config.c) +set (SPBOT_SOURCES spbot/nexus.c spbot/signals.c spbot/module.c spbot/config.c spbot/nexus_lua.c) set (NEXUS_SOURCES ${SPBOT_SOURCES} ${LIB_SOURCES} ${IO_SOURCES} ${PROTO_SOURCES} ${IRC_SOURCES} ${LUA_SOURCES} ${CONSOLE_SOURCES}) set (IRC_LOG_SOURCES modules/irc_log.c) @@ -61,7 +61,7 @@ if (ENABLE_TEST) # build list of source files file (GLOB _TEST_SOURCES "test/*.c") - set (TEST_SOURCES ${_TEST_SOURCES} ${CORE_SOURCES} ${IO_SOURCES} ${PROTO_SOURCES} transport_test.c ${IRC_SOURCES}) + set (TEST_SOURCES ${_TEST_SOURCES} ${CORE_SOURCES} ${IO_SOURCES} ${PROTO_SOURCES} lib/transport_test.c ${IRC_SOURCES}) # add executable target and link against libs add_executable (test_harness EXCLUDE_FROM_ALL ${TEST_SOURCES}) diff -r 5229a5d098b2 -r cefec18b8268 src/fifo.c --- a/src/fifo.c Thu May 28 00:35:02 2009 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,157 +0,0 @@ - -#include "fifo.h" -#include "transport_fd.h" - -#include -#include -#include -#include -#include - -/** - * Our transport_type - */ -extern const struct transport_type fifo_type; - -/** - * The fifo state - */ -struct fifo { - /** The FD-based state */ - struct transport_fd base_fd; - - /** Path to the fifo */ - char *path; -}; - -/** - * (re)open the fifo, closing it if already open, and keeping any event callbacks registered. - */ -static err_t fifo_open (struct fifo *fifo, error_t *err) -{ - int _fd; - - // open(2) the path in non-blocking read-only mode - if ((_fd = open(fifo->path, O_RDONLY | O_NONBLOCK)) < 0) - RETURN_SET_ERROR_ERRNO(err, ERR_OPEN); - - // set the new fd - if ((ERROR_CODE(err) = transport_fd_set(&fifo->base_fd, _fd))) - return ERROR_CODE(err); - - // use default transport event-based behaviour - if ((ERROR_CODE(err) = transport_fd_defaults(&fifo->base_fd))) - return ERROR_CODE(err); - - // ok - return SUCCESS; -} - -/** - * Deinit the fifo, releasing all resources - */ -static void fifo_deinit (struct fifo *fifo) -{ - // deinit base - transport_fd_deinit(&fifo->base_fd); - - // release the path - free(fifo->path); - - fifo->path = NULL; -} - -/** - * sock_stream_methods::read implementation. - * - * Try and do a normal sock_fd_read call, but re-open with EAGAIN on EOF - */ -err_t fifo_read (transport_t *transport, void *buf, size_t *len, struct error_info *err) -{ - struct fifo *fifo = transport_check(transport, &fifo_type); - - // trap READ_EOF - if (transport_fd__read(transport, buf, len, err) != ERR_EOF) - return ERROR_CODE(err); - - // re-open it - // XXX: re-add events? - if (fifo_open(fifo, err)) - goto error; - - // ok, act as if it was EAGAIN - *len = 0; - - return SUCCESS; - -error: - return ERROR_CODE(err); -} - -/** - * sock_stream_methods::release implementation - */ -static void fifo__deinit (transport_t *transport) -{ - struct fifo *fifo = transport_check(transport, &fifo_type); - - fifo_deinit(fifo); -} - -/* - * Our sock_stream_type - */ -const struct transport_type fifo_type = { - .base_type = { - .parent = &transport_fd_type.base_type, - }, - .methods = { - .read = fifo_read, - .write = NULL, - .events = transport_fd__events, - .deinit = fifo__deinit, - }, -}; - -/** - * Deinit and free - */ -static void fifo_destroy (struct fifo *fifo) -{ - fifo_deinit(fifo); - - free(fifo); -} - -err_t fifo_open_read (struct transport_info *transport_info, transport_t **transport_ptr, struct event_base *ev_base, - const char *path, error_t *err) -{ - struct fifo *fifo; - - // alloc - if ((fifo = calloc(1, sizeof(*fifo))) == NULL) - return SET_ERROR(err, ERR_CALLOC); - - // copy the path - if ((fifo->path = strdup(path)) == NULL) - return SET_ERROR(err, ERR_STRDUP); - - // init - transport_init(&fifo->base_fd.base, &fifo_type, transport_info); - transport_fd_init(&fifo->base_fd, ev_base, TRANSPORT_FD_INVALID); - - // open the fifo - if (fifo_open(fifo, err)) - goto error; - - // ok - *transport_ptr = &fifo->base_fd.base; - - return SUCCESS; - -error: - // cleanup - fifo_destroy(fifo); - - return ERROR_CODE(err); -} diff -r 5229a5d098b2 -r cefec18b8268 src/fifo.h --- a/src/fifo.h Thu May 28 00:35:02 2009 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,22 +0,0 @@ -#ifndef FIFO_H -#define FIFO_H - -#include "transport.h" - -#include - -/** - * A read-only transport based on a FIFO, this provides nonblocking read operations by re-opening the FIFO on EOF. - * - * The transport will be ready for use right away, transport_callbacks::on_connect will never be called. - * - * @param transport_info the setup info required to create the transport - * @param transport_ptr returned transport - * @param path the path to the filesystem fifo object - * @param err returned error info - */ -err_t fifo_open_read (struct transport_info *transport_info, transport_t **transport_ptr, struct event_base *ev_base, - const char *path, error_t *err); - - -#endif diff -r 5229a5d098b2 -r cefec18b8268 src/irc_conn.h --- a/src/irc_conn.h Thu May 28 00:35:02 2009 +0300 +++ b/src/irc_conn.h Thu May 28 01:17:36 2009 +0300 @@ -14,8 +14,8 @@ struct irc_conn; #include "error.h" -#include "transport.h" -#include "line_proto.h" +#include +#include #include "irc_queue.h" #include "irc_line.h" #include "irc_cmd.h" diff -r 5229a5d098b2 -r cefec18b8268 src/irc_net.h --- a/src/irc_net.h Thu May 28 00:35:02 2009 +0300 +++ b/src/irc_net.h Thu May 28 01:17:36 2009 +0300 @@ -10,10 +10,10 @@ struct irc_net; -#include "error.h" #include "irc_conn.h" #include "irc_chan.h" -#include "ssl.h" +#include +#include #include /** diff -r 5229a5d098b2 -r cefec18b8268 src/irc_queue.h --- a/src/irc_queue.h Thu May 28 00:35:02 2009 +0300 +++ b/src/irc_queue.h Thu May 28 01:17:36 2009 +0300 @@ -9,8 +9,8 @@ * This implements the basic flood control algorithm as described in RFC1459 section 8.10. */ #include "irc_line.h" -#include "line_proto.h" -#include "error.h" +#include +#include #include #include diff -r 5229a5d098b2 -r cefec18b8268 src/lib/error.h --- a/src/lib/error.h Thu May 28 00:35:02 2009 +0300 +++ b/src/lib/error.h Thu May 28 01:17:36 2009 +0300 @@ -89,7 +89,7 @@ #define ERROR_TYPE_STRING(code, name) \ { (code), (name), &error_extra_string, NULL } -#define ERROR_TYPE_CUSTOM(code, name, type) \ +#define ERROR_TYPE_EXTRA(code, name, type) \ { (code), (name), (type), NULL } #define ERROR_TYPE_SUB(code, name, sub) \ @@ -239,7 +239,7 @@ /** Set the error with extra info as integer */ #define SET_ERROR_EXTRA(err_state, err_list, err_code, err_extra_type, err_extra_int) ({ \ - _ERROR_SET_EXTRA(err_state, err_list, err_code, err_extra_type, int_, err_extra); \ + _ERROR_SET_EXTRA(err_state, err_list, err_code, err_extra_type, int_, err_extra_int); \ err_code; }) /** Set the error with extra info as the libc errno */ diff -r 5229a5d098b2 -r cefec18b8268 src/lib/errors.c --- a/src/lib/errors.c Thu May 28 00:35:02 2009 +0300 +++ b/src/lib/errors.c Thu May 28 01:17:36 2009 +0300 @@ -9,7 +9,12 @@ ); const struct error_list libc_errors = ERROR_LIST("libc", - ERROR_TYPE_ERRNO( ERR_SIGACTION, "sigaction" ) + ERROR_TYPE_ERRNO( ERR_SIGACTION, "sigaction" ), + ERROR_TYPE_ERRNO( ERR_READ, "read" ), + ERROR_TYPE_ERRNO( ERR_WRITE, "write" ), + ERROR_TYPE_ERRNO( ERR_WRITE_EOF, "write: EOF" ), + ERROR_TYPE_ERRNO( ERR_FCNTL, "fcntl" ), + ERROR_TYPE_ERRNO( ERR_CLOSE, "close" ) ); const struct error_list libevent_errors = ERROR_LIST("libevent", @@ -18,3 +23,10 @@ ERROR_TYPE( ERR_EVENT_DEL, "event_del" ) ); +const struct error_list lua_errors = ERROR_LIST("lua", + ERROR_TYPE_STRING( ERR_LUA_MEM, "memory error" ), + ERROR_TYPE_STRING( ERR_LUA_SYNTAX, "syntax error" ), + ERROR_TYPE_STRING( ERR_LUA_RUN, "runtime erorr" ), + ERROR_TYPE_STRING( ERR_LUA_ERR, "error-handling error" ), + ERROR_TYPE_STRING( ERR_LUA_FILE, "filesystem error" ) +); diff -r 5229a5d098b2 -r cefec18b8268 src/lib/errors.h --- a/src/lib/errors.h Thu May 28 00:35:02 2009 +0300 +++ b/src/lib/errors.h Thu May 28 01:17:36 2009 +0300 @@ -34,6 +34,11 @@ enum libc_error_code { ERR_LIBC_NONE, ERR_SIGACTION, ///< sigaction: + ERR_READ, ///< read: + ERR_WRITE, ///< write: + ERR_WRITE_EOF, ///< write: EOF + ERR_FCNTL, ///< fcntl: + ERR_CLOSE, ///< close: }; const struct error_list libc_errors; @@ -42,6 +47,7 @@ * Errors for libevent */ enum libevent_error_code { + ERR_EVENT_NONE, ERR_EVENT_NEW, ///< event_new ERR_EVENT_ADD, ///< event_add ERR_EVENT_DEL, ///< event_del @@ -49,4 +55,18 @@ const struct error_list libevent_errors; +/** + * Errors for lua + */ +enum lua_error_code { + ERR_LUA_NONE, + ERR_LUA_MEM, ///< memory error + ERR_LUA_SYNTAX, ///< syntax error + ERR_LUA_RUN, ///< runtime erorr + ERR_LUA_ERR, ///< error-handling error + ERR_LUA_FILE, ///< filesystem error +}; + +const struct error_list lua_errors; + #endif /* LIBQMSK_ERRORS_H */ diff -r 5229a5d098b2 -r cefec18b8268 src/lib/fifo.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/fifo.c Thu May 28 01:17:36 2009 +0300 @@ -0,0 +1,157 @@ + +#include "fifo.h" +#include "transport_fd.h" + +#include +#include +#include +#include +#include + +/** + * Our transport_type + */ +extern const struct transport_type fifo_type; + +/** + * The fifo state + */ +struct fifo { + /** The FD-based state */ + struct transport_fd base_fd; + + /** Path to the fifo */ + char *path; +}; + +/** + * (re)open the fifo, closing it if already open, and keeping any event callbacks registered. + */ +static err_t fifo_open (struct fifo *fifo, error_t *err) +{ + int _fd; + + // open(2) the path in non-blocking read-only mode + if ((_fd = open(fifo->path, O_RDONLY | O_NONBLOCK)) < 0) + RETURN_SET_ERROR_ERRNO(err, ERR_OPEN); + + // set the new fd + if ((ERROR_CODE(err) = transport_fd_set(&fifo->base_fd, _fd))) + return ERROR_CODE(err); + + // use default transport event-based behaviour + if ((ERROR_CODE(err) = transport_fd_defaults(&fifo->base_fd))) + return ERROR_CODE(err); + + // ok + return SUCCESS; +} + +/** + * Deinit the fifo, releasing all resources + */ +static void fifo_deinit (struct fifo *fifo) +{ + // deinit base + transport_fd_deinit(&fifo->base_fd); + + // release the path + free(fifo->path); + + fifo->path = NULL; +} + +/** + * sock_stream_methods::read implementation. + * + * Try and do a normal sock_fd_read call, but re-open with EAGAIN on EOF + */ +err_t fifo_read (transport_t *transport, void *buf, size_t *len, struct error_info *err) +{ + struct fifo *fifo = transport_check(transport, &fifo_type); + + // trap READ_EOF + if (transport_fd__read(transport, buf, len, err) != ERR_EOF) + return ERROR_CODE(err); + + // re-open it + // XXX: re-add events? + if (fifo_open(fifo, err)) + goto error; + + // ok, act as if it was EAGAIN + *len = 0; + + return SUCCESS; + +error: + return ERROR_CODE(err); +} + +/** + * sock_stream_methods::release implementation + */ +static void fifo__deinit (transport_t *transport) +{ + struct fifo *fifo = transport_check(transport, &fifo_type); + + fifo_deinit(fifo); +} + +/* + * Our sock_stream_type + */ +const struct transport_type fifo_type = { + .base_type = { + .parent = &transport_fd_type.base_type, + }, + .methods = { + .read = fifo_read, + .write = NULL, + .events = transport_fd__events, + .deinit = fifo__deinit, + }, +}; + +/** + * Deinit and free + */ +static void fifo_destroy (struct fifo *fifo) +{ + fifo_deinit(fifo); + + free(fifo); +} + +err_t fifo_open_read (struct transport_info *transport_info, transport_t **transport_ptr, struct event_base *ev_base, + const char *path, error_t *err) +{ + struct fifo *fifo; + + // alloc + if ((fifo = calloc(1, sizeof(*fifo))) == NULL) + return SET_ERROR(err, ERR_CALLOC); + + // copy the path + if ((fifo->path = strdup(path)) == NULL) + return SET_ERROR(err, ERR_STRDUP); + + // init + transport_init(&fifo->base_fd.base, &fifo_type, transport_info); + transport_fd_init(&fifo->base_fd, ev_base, TRANSPORT_FD_INVALID); + + // open the fifo + if (fifo_open(fifo, err)) + goto error; + + // ok + *transport_ptr = &fifo->base_fd.base; + + return SUCCESS; + +error: + // cleanup + fifo_destroy(fifo); + + return ERROR_CODE(err); +} diff -r 5229a5d098b2 -r cefec18b8268 src/lib/fifo.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/fifo.h Thu May 28 01:17:36 2009 +0300 @@ -0,0 +1,22 @@ +#ifndef FIFO_H +#define FIFO_H + +#include "transport.h" + +#include + +/** + * A read-only transport based on a FIFO, this provides nonblocking read operations by re-opening the FIFO on EOF. + * + * The transport will be ready for use right away, transport_callbacks::on_connect will never be called. + * + * @param transport_info the setup info required to create the transport + * @param transport_ptr returned transport + * @param path the path to the filesystem fifo object + * @param err returned error info + */ +err_t fifo_open_read (struct transport_info *transport_info, transport_t **transport_ptr, struct event_base *ev_base, + const char *path, error_t *err); + + +#endif diff -r 5229a5d098b2 -r cefec18b8268 src/lib/line_proto.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/line_proto.c Thu May 28 01:17:36 2009 +0300 @@ -0,0 +1,336 @@ + +#include "line_proto.h" +#include "log.h" + +#include +#include +#include + +/* + * Our state + */ +struct line_proto { + /* The transport we read/write with */ + transport_t *transport; + + /* The incoming/outgoing line buffer */ + char *in_buf, *out_buf; + + /* Buffer size (same for both) */ + size_t buf_len; + + /* Offset of trailing data in buf */ + size_t tail_offset; + + /* Length of trailing data in buf, if any */ + size_t tail_len; + + /* Amount of data in the out buffer */ + size_t out_offset; + + /* Last error */ + struct error_info err; + + /* Callback info */ + struct line_proto_callbacks callbacks; + void *cb_arg; +}; + +/** + * An error occured which we could not recover from; the line_proto should now be considered corrupt. + * + * Notify the user callback, which will probably call line_proto_release(). + */ +static void line_proto_set_error (struct line_proto *lp) +{ + // copy error_info, as it might get free'd + struct error_info err = lp->err; + + // trigger callback + lp->callbacks.on_error(&err, lp->cb_arg); +} + +/** + * Our transport_callbacks::on_read handler + */ +static void line_proto_on_read (transport_t *transport, void *arg) +{ + struct line_proto *lp = arg; + char *line; + + (void) transport; + + // sanity-check + assert(lp->tail_offset < lp->buf_len); + + do { + // attempt to read a line + if (line_proto_recv(lp, &line)) + // faaail + return line_proto_set_error(lp); + + // got a line? + if (line) + lp->callbacks.on_line(line, lp->cb_arg); + + } while (line); +} + +/* + * Signal for write + */ +static void line_proto_on_write (transport_t *transport, void *arg) +{ + struct line_proto *lp = arg; + int ret; + + (void) transport; + + // just flush + if ((ret = line_proto_flush(lp)) < 0) + // faaail + return line_proto_set_error(lp); +} + +// XXX: implement on_error! +static const struct transport_callbacks line_proto_transport_callbacks = { + .on_read = &line_proto_on_read, + .on_write = &line_proto_on_write, +}; + +err_t line_proto_create (struct line_proto **lp_ptr, transport_t *transport, size_t buf_size, + const struct line_proto_callbacks *callbacks, void *cb_arg, error_t *err) +{ + struct line_proto *lp; + + // alloc + if ((lp = calloc(1, sizeof(*lp))) == NULL) + return SET_ERROR(err, ERR_CALLOC); + + // store + lp->transport = transport; + lp->buf_len = buf_size; + lp->callbacks = *callbacks; + lp->cb_arg = cb_arg; + + // allocate buffers + if ( + (lp->in_buf = malloc(buf_size)) == NULL + || (lp->out_buf = malloc(buf_size)) == NULL + ) + JUMP_SET_ERROR(err, ERR_CALLOC); + + // setup the transport + transport_set_callbacks(transport, &line_proto_transport_callbacks, lp); + + if ((ERROR_CODE(err) = transport_events(transport, TRANSPORT_READ | TRANSPORT_WRITE))) + goto error; + + // return + *lp_ptr = lp; + + return SUCCESS; + +error: + // cleanup the lp + line_proto_destroy(lp); + + return ERROR_CODE(err); +} + +/* + * This looks for a full '\r\n' terminated line at the beginning of the given buffer. If found, the \r\n will be + * replaced with a '\0', and the offset to the beginning of the next line returned. If not found, zero is returned + * (which is never a valid next-line offset). + * + * The given \a hint is an hint as to the offset at which to start scanning, used for incremental invocations of this + * on the same buffer. + * + */ +int _parse_line (char *buf, size_t len, size_t *hint) { + size_t i, next = 0; + + // empty buffer -> nothing + if (len == 0) + return 0; + + // look for terminating '\r\n' or '\n' sequence + for (i = *hint; i < len; i++) { + // match this + next char? + if (i < len - 1 && buf[i] == '\r' && buf[i + 1] == '\n') { + next = i + 2; + break; + + } else if (buf[i] == '\n') { + next = i + 1; + break; + } + } + + // searched the whole buffer? + if (i >= len) { + // do continue one char back, to keep any \r + *hint = len - 1; + return 0; + } + + // mangle the newline off + buf[i] = '\0'; + + // return offset to next line, as set in loop based on delim + return next; +} + +err_t line_proto_recv (struct line_proto *lp, char **line_ptr) +{ + // offset to recv() new data into, offset to _parse_line hint, offset to next line from _parse_line + size_t recv_offset = 0, peek_offset = 0, next_offset = 0; + int ret; + + // adjust offset to beyond previous data (as will be moved next) + recv_offset = lp->tail_len; + + // move trailing data from previous line to front of buffer + if (lp->tail_offset) { + // move to front, no-op if tail_len is zero + memmove(lp->in_buf, lp->in_buf + lp->tail_offset, lp->tail_len); + + // reset + lp->tail_offset = 0; + lp->tail_len = 0; + } + + // readline loop + do { + // parse any line at the beginning of the buffer + if ((next_offset = _parse_line(lp->in_buf, recv_offset, &peek_offset)) > 0) { + // store a valid *line_ptr + *line_ptr = lp->in_buf; + + // exit loop and return + break; + } + + // ensure there's enough space for the rest of the line + if (recv_offset >= lp->buf_len) + return ERR_LINE_TOO_LONG; + + // otherwise, read more data + if ((ret = transport_read(lp->transport, lp->in_buf + recv_offset, lp->buf_len - recv_offset, &lp->err)) < 0) + return ERROR_CODE(&lp->err); + + // EAGAIN? + if (ret == 0) { + // return a NULL *line_ptr + *line_ptr = NULL; + break; + } + + // update recv_offset + recv_offset += ret; + + } while (1); + + // update state for next call + lp->tail_offset = next_offset; + lp->tail_len = recv_offset - next_offset; + + // ok + return SUCCESS; +} + +int line_proto_send (struct line_proto *lp, const char *line) +{ + int ret; + size_t len = strlen(line), ret_len; + + // drop line if we already have output buffered + if (lp->out_offset) + return -ERR_LINE_TOO_LONG; + + // try and write the line + if ((ret = transport_write(lp->transport, line, len, &lp->err)) < 0) + return -ERROR_CODE(&lp->err); + + // length of the sent data + ret_len = ret; + + // EAGAIN or partial? + if (ret_len < len) { + size_t trailing = len - ret_len; + + // ensure it's not waaaay too long + if (trailing > lp->buf_len) + return -ERR_LINE_TOO_LONG; + + // copy remaining portion to buffer + memcpy(lp->out_buf, line + ret_len, trailing); + + // update offset + lp->out_offset = trailing; + + // buffered... transport should invoke on_write itself + return 1; + + } else { + // ok, no buffering needed + return SUCCESS; + + } +} + +int line_proto_flush (struct line_proto *lp) +{ + int ret; + size_t ret_len; + + assert(lp->out_offset); + + // try and write the line + if ((ret = transport_write(lp->transport, lp->out_buf, lp->out_offset, &lp->err)) < 0) + return -ERROR_CODE(&lp->err); + + ret_len = ret; + + // empty now? + if (ret_len == lp->out_offset) { + lp->out_offset = 0; + + return SUCCESS; + } + + // partial? + if (ret_len > 0) { + size_t remaining = lp->out_offset - ret_len; + + // move the rest up + memmove(lp->out_buf, lp->out_buf + ret_len, remaining); + + // update offset + lp->out_offset = remaining; + } + + // ok + return 1; +} + +const struct error_info* line_proto_error (struct line_proto *lp) +{ + // return pointer + return &lp->err; +} + +void line_proto_destroy (struct line_proto *lp) +{ + // free buffers + free(lp->in_buf); + free(lp->out_buf); + + // socket? + if (lp->transport) + transport_destroy(lp->transport); + + // free the state itself + free(lp); +} + diff -r 5229a5d098b2 -r cefec18b8268 src/lib/line_proto.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/line_proto.h Thu May 28 01:17:36 2009 +0300 @@ -0,0 +1,84 @@ +#ifndef LIBQMSK_LINE_PROTO_H +#define LIBQMSK_LINE_PROTO_H + +/** + * @file + * + * Support for protocols that send/receive lines + */ +#include "transport.h" +#include "error.h" + +/** + * The line_proto state handle + */ +struct line_proto; + +/** + * User callbacks for event-based line_proto behaviour + */ +struct line_proto_callbacks { + /** Handle received line */ + void (*on_line) (char *line, void *arg); + + /** Transport failed, the line_proto is corrupt, you should call line_proto_release next. */ + void (*on_error) (const error_t *err, void *arg); +}; + +/** + * Create a new line_proto off the the given sock_stream. The newly allocated line_proto will be returned via *lp_ptr. + * + * The incoming lines are buffered in a buffer of \a buf_size bytes. This imposes a maximum limit on the line length. + * + * In case of errors, \a transport will be destroyed in any case. + * + * @param lp_ptr a pointer to the new line_proto will be returned via this pointer + * @param transport the connected transport to use + * @param buf_size the incoming/outgoing buffer size, should be enough to hold the biggest possible line + * @param callbacks the callbacks to use, a copy is stored + * @param cb_arg the read_cb callback argument + * @param err error information is returned via this pointer + */ +err_t line_proto_create (struct line_proto **lp_ptr, transport_t *transport, size_t buf_size, + const struct line_proto_callbacks *callbacks, void *cb_arg, error_t *err); + +/** + * Runs transport_read() with our internal buffer. If a full line was received, a pointer to our internal bufffer is + * returned via *line_ptr, and we return SUCCESS. If we don't yet have a full line, and receiving more would block, + * NULL is returned via *line_ptr instead. Otherwise, nonzero error return code. + * + * @param line_ptr a pointer to the received line is returned via this pointer + */ +err_t line_proto_recv (struct line_proto *lp, char **line_ptr); + +/** + * Write a single line to the sock_stream, buffering any incomplete fragment that remains unsent. Returns zero if the + * line was succesfully sent, >0 if it was only partially sent, or -err on errors. + * + * The given line should already include the terminating '\r\n' character sequence. + * + * @param line pointer to buffer containing \r\n\0 terminated line + */ +int line_proto_send (struct line_proto *lp, const char *line); + +/** + * Flush out any buffered line fragment. Returns zero if the buffer was flushed empty, >0 if there's still fragments + * remaining, or -err on errors. + * + * It is a bug to call this if there is no data waiting to be sent. + */ +int line_proto_flush (struct line_proto *lp); + +/** + * Get current error_info* + */ +const error_t* line_proto_error (struct line_proto *lp); + +/** + * Destroy any buffers and the underlying transport. + * + * This does not close the connection cleanly, and is intended for use to abort after errors. + */ +void line_proto_destroy (struct line_proto *lp); + +#endif diff -r 5229a5d098b2 -r cefec18b8268 src/lib/lua_func.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/lua_func.c Thu May 28 01:17:36 2009 +0300 @@ -0,0 +1,207 @@ +#include "lua_func.h" +#include "error.h" + +#include + +/** + * Pushes onto the stack the value at t[i] + */ +static void lua_getindex (lua_State *L, int t, int i) +{ + lua_pushinteger(L, i); + lua_gettable(L, t); +} + +/** + * Pushes onto the stack either: + * * the value at t[name] + * * the value at t[index] + * + * Returns the new index, or 0, if neither could be found + */ +static int lua_arg_lookup (lua_State *L, int t, const char *name, int index) +{ + // try name + lua_getfield(L, t, name); + + if (!lua_isnil(L, -1)) + return lua_gettop(L); + else + lua_pop(L, 1); + + // try index + lua_getindex(L, t, index); + + if (!lua_isnil(L, -1)) + return lua_gettop(L); + + else + lua_pop(L, 1); + + // not found + return 0; +} + +static const char *_lua_arg_string (lua_State *L, int index, const char *name, const char *def) +{ + const char *value; + + // use default? + if (lua_isnoneornil(L, index) && def != (const char *) LUA_ARG_REQUIRED) + return def; + + // value given? + if ((value = lua_tostring(L, index))) + return value; + + // error + luaL_error(L, "missing value for required string argument <%d:%s>", index, name); return NULL; +} + +static bool _lua_arg_bool (lua_State *L, int index, const char *name, int def) +{ + (void) name; + + // use default? + if (lua_isnoneornil(L, index) && def != LUA_ARG_REQUIRED) + return def; + + // value given + return lua_toboolean(L, index); +} + +static long _lua_arg_int (lua_State *L, int index, const char *name, long def) +{ + (void) name; + + // use default? + if (lua_isnoneornil(L, index) && def != LUA_ARG_REQUIRED) + return def; + + // conver to integer + // XXX: check compatibility? + return lua_tointeger(L, index); +} + +static void * _lua_arg_obj (lua_State *L, int index, const struct lua_type *type, bool optional) +{ + // not given? + if (!lua_isnoneornil(L, index)) + return lua_type_get(L, type, index); + + if (optional) + return NULL; + + luaL_error(L, "missing value for required object argument <%d:%s>", index, type->name); + return NULL; +} + +/** + * Look up the arg index to use for the given index/name. + * + * If no value is found for the corresponding index, returns zero. + */ +static int lua_arg_index (lua_State *L, int nargs, int index, const char *name) +{ + // lookup from table? + if (nargs == 2 && lua_istable(L, 2) && name) { + // push the value from the named field onto the stack + lua_getfield(L, 2, name); + + // no named field? + if (lua_isnil(L, -1)) { + lua_pop(L, 1); + + lua_getindex(L, 2, index - 1); + } + + // no index field? + if (lua_isnil(L, -1)) { + lua_pop(L, 1); + + return 0; + } + + // found either a named or indexed arg + return lua_gettop(L); + + } else if (index <= nargs) { + // use the same index + return index; + + } else { + // no index + return 0; + } +} + +const char *lua_arg_string (lua_State *L, int nargs, int index, const char *name, const char *def) +{ + return _lua_arg_string(L, lua_arg_index(L, nargs, index, name), name, def); +} + +bool lua_arg_bool (lua_State *L, int nargs, int index, const char *name, int def) +{ + return _lua_arg_bool(L, lua_arg_index(L, nargs, index, name), name, def); +} + +void* lua_arg_obj (lua_State *L, int nargs, int index, const struct lua_type *type, bool optional) +{ + return _lua_arg_obj(L, lua_arg_index(L, nargs, index, NULL), type, optional); +} + +long lua_arg_int (lua_State *L, int nargs, int index, const char *name, long def) +{ + return _lua_arg_int(L, lua_arg_index(L, nargs, index, name), name, def); +} + +void lua_args_parse (lua_State *L, const struct lua_func *func, void **obj_ptr, ...) +{ + int argidx = 1, argtbl = 0, idx; + const struct lua_func_arg *arg; + va_list vargs; + + // first, the obj argument + if (func->type) + *obj_ptr = lua_type_get(L, func->type, argidx++); + + // were we given a table of arguments? + if (lua_istable(L, argidx)) + argtbl = argidx++; + + // parse the args + va_start(vargs, obj_ptr); + + for (arg = func->args, idx = 1; arg->name && arg->type; arg++, idx++) { + int index; + + // map index + if (!argtbl) + // direct + index = argidx++; + + else + // lookup from table + index = lua_arg_lookup(L, argtbl, arg->name, idx); + + // apply + switch (arg->type) { + case LUA_ARG_STRING: + *va_arg(vargs, const char **) = _lua_arg_string(L, index, arg->name, arg->def.string); + break; + + case LUA_ARG_BOOL: + *va_arg(vargs, bool *) = _lua_arg_bool(L, index, arg->name, arg->def.boolean); + break; + + case LUA_ARG_INT: + *va_arg(vargs, long *) = _lua_arg_int(L, index, arg->name, arg->def.integer); + break; + + default: + NOT_REACHED(); + }; + } + + va_end(vargs); +} diff -r 5229a5d098b2 -r cefec18b8268 src/lib/lua_func.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/lua_func.h Thu May 28 01:17:36 2009 +0300 @@ -0,0 +1,107 @@ +#ifndef LUA_FUNC_H +#define LUA_FUNC_H + +/** + * @file + * + * Convenience functions for working with lua C functions + */ +#include "lua_type.h" +#include + +/** + * Lua function argument types + */ +enum lua_arg_type { + LUA_ARG_INVALID, + + /** A `const char *` pointing to lua-GC'd memory */ + LUA_ARG_STRING, + + /** A c99 `bool` */ + LUA_ARG_BOOL, + + /** A `long signed int` */ + LUA_ARG_INT, +}; + +/** + * Function argument def + */ +struct lua_func_arg { + /** Argument name */ + const char *name; + + /** Expected type */ + enum lua_arg_type type; + + /** Default value */ + union { + const char *string; + int boolean; + long integer; + } def; +}; + +/** + * Function def + */ +struct lua_func { + /** Object type, or NULL */ + const struct lua_type *type; + + /** Function name */ + const char *name; + + /** Help string */ + const char *help; + + /** Arguments */ + const struct lua_func_arg args[]; +}; + +/** + * Used as the "invalid" default value + */ +#define LUA_ARG_REQUIRED (-1) +#define LUA_ARG_STRING_REQUIRED ((const char *) (-1)) + +/** + * Define a function argument + */ +#define LUA_FUNC_ARG_STRING(name, def) { (name), LUA_ARG_STRING, { .string = (def) } } +#define LUA_FUNC_ARG_BOOL(name, def) { (name), LUA_ARG_BOOL, { .boolean = (def) } } +#define LUA_FUNC_ARG_INT(name, def) { (name), LUA_ARG_INT, { .integer = (def) } } +#define LUA_FUNC_ARG_END { NULL, 0, { 0 } } + +/** + * Define a function + */ +#define LUA_FUNC(type, name, help, ...) { (type), (name), (help), { __VA_ARGS__, LUA_FUNC_ARG_END } } + +/** + * Parse and return a string argument + */ +const char *lua_arg_string (lua_State *L, int nargs, int index, const char *name, const char *def); + +/** + * Parse and return a boolean argument + */ +bool lua_arg_bool (lua_State *L, int nargs, int index, const char *name, int def); + +/** + * Parse and return an integer argument + */ +long lua_arg_int (lua_State *L, int nargs, int index, const char *name, long def); + +/** + * Return a userdata argument at the given fixed index + */ +void* lua_arg_obj (lua_State *L, int nargs, int index, const struct lua_type *type, bool optional); + +/** + * Parse function arguments as defined + */ +void lua_args_parse (lua_State *L, const struct lua_func *func, void **obj_ptr, ...); + +#endif diff -r 5229a5d098b2 -r cefec18b8268 src/lib/lua_type.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/lua_type.c Thu May 28 01:17:36 2009 +0300 @@ -0,0 +1,69 @@ +#include "lua_type.h" + +#include + +void lua_type_register (lua_State *L, const struct lua_type *type, const struct lua_method methods[]) +{ + const struct lua_method *method; + + // create the metatable + luaL_newmetatable(L, type->name); + + // set the metatable __index to itself + lua_pushvalue(L, -1); + lua_setfield(L, -1, "__index"); + + // add the methods to the metatable + for (method = methods; method->func; method++) { + lua_pushcfunction(L, method->func); + lua_setfield(L, -2, method->name); + } +} + +void* lua_type_create (lua_State *L, const struct lua_type *type, size_t size) +{ + // create the new userdata on the stack + void *ud = lua_newuserdata(L, size); + + // get the type and set it + luaL_getmetatable(L, type->name); + lua_setmetatable(L, -2); + + // ok + return ud; +} + +void* lua_type_register_global (lua_State *L, const struct lua_type *type, const struct lua_method methods[], + const char *global_name, size_t size) +{ + // allocate the global object + void *obj = lua_newuserdata(L, size); + + // create the type metatable + lua_type_register(L, type, methods); + + // set the userdata's metatable + lua_setmetatable(L, -2); + + // store it as a global + lua_setglobal(L, global_name); + + // ok + return obj; +} + +void* lua_type_get (lua_State *L, const struct lua_type *type, int index) +{ + void *ud; + + // validate the userdata arg + // XXX: the luaL_checkudata actually raises an error itself + if ((ud = luaL_checkudata(L, index, type->name)) == NULL) { + luaL_error(L, "bad type argument: `%s` expected", type->name); return NULL; + + } else { + // ok + return ud; + + } +} diff -r 5229a5d098b2 -r cefec18b8268 src/lib/lua_type.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/lua_type.h Thu May 28 01:17:36 2009 +0300 @@ -0,0 +1,74 @@ +#ifndef LUA_TYPE_H +#define LUA_TYPE_H + +/** + * @file + * + * Convenience functions for defining "types" in lua + */ +#include + +// XXX: remove +#include + +/** + * A type's method + * + * XXX: a name field? + */ +struct lua_method { + /** The name of the method */ + const char *name; + + /** The function pointer */ + lua_CFunction func; + + /** The function definition, optional */ + const struct lua_func *info; +}; + +#define LUA_METHOD(name, func, info) \ + { (name), (func), (info) } + +#define LUA_METHODS(...) \ + { __VA_ARGS__, { NULL, NULL, NULL } } + +/** + * A type + */ +struct lua_type { + /** The name of the type */ + const char *name; +}; + +#define LUA_TYPE(name) \ + { (name) } + +/** + * Register a new metadata table for the given type in the given lua state. + * + * This leaves the new type (metatable) on the stack. + */ +void lua_type_register (lua_State *L, const struct lua_type *type, const struct lua_method methods[]); + +/** + * Create a new instance of the given type. + * + * This leaves the new userdata object on the stack. + */ +void* lua_type_create (lua_State *L, const struct lua_type *type, size_t size); + +/** + * Create a new userdata type, and also create an instance of it, register it as a global, and return it. + * + * This leaves the new userdata object on the stack. + */ +void* lua_type_register_global (lua_State *L, const struct lua_type *type, const struct lua_method methods[], + const char *global_name, size_t size); + +/** + * Get an object of the given type from the given stack position + */ +void* lua_type_get (lua_State *L, const struct lua_type *type, int index); + +#endif diff -r 5229a5d098b2 -r cefec18b8268 src/lib/msg_proto.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/msg_proto.c Thu May 28 01:17:36 2009 +0300 @@ -0,0 +1,493 @@ +#include "msg_proto.h" + +#include +#include +#include + +/** + * I/O buffer + */ +struct msg_buf { + /** Buffer base pointer */ + char *base; + + /** Size of the buffer */ + size_t size; + + /** Current read/write offset */ + size_t off; +}; + +/** + * The minimum size used for any msg_buf::size related operation. + */ +#define MSG_BUF_MIN_SIZE 1024 + +/** + * Growth rate for size + */ +#define MSG_BUF_GROW_RATE 2 + +/** + * Initialize a message buffer at the given initial size + */ +err_t msg_buf_init (struct msg_buf *buf, size_t hint) +{ + // apply minimum size + if (hint < MSG_BUF_MIN_SIZE) + hint = MSG_BUF_MIN_SIZE; + + // allocate the initial buffer + if ((buf->base = malloc(hint)) == NULL) + return ERR_MEM; + + // set fields + buf->size = hint; + buf->off = 0; + + // ok + return SUCCESS; +} + +/** + * Grow the buffer if needed to fit the given capacity. + */ +err_t msg_buf_grow (struct msg_buf *buf, size_t size) +{ + char *tmp = buf->base; + + if (buf->size >= size) + // nothing to do + return SUCCESS; + + // calculate new size + while (buf->size < size) + buf->size *= MSG_BUF_GROW_RATE; + + // resize + if ((buf->base = realloc(buf->base, buf->size)) == NULL) { + buf->base = tmp; + + return ERR_MEM; + } + + // ok + return SUCCESS; +} + +/** + * Drain \a len bytes off the head of the buffer + */ +err_t msg_buf_drain (struct msg_buf *buf, size_t len) +{ + // simple memmove + memmove(buf->base, buf->base + len, buf->off - len); + + // update offfset + buf->off -= len; + + // ok + return SUCCESS; +} + +/** + * Read into the buffer from a transport_t. + * + * This will attempt to read \a len bytes onto the end of the buffer, growing it if needed to fit. + * + * This may read/return more data than the given len. Use msg_buf_drain the remove the data from the buffer once you + * have used it. + * + * Returns the number of new bytes read, zero for transport read buffer empty, -err_t for error. + */ +ssize_t msg_buf_read (struct msg_buf *buf, transport_t *transport, size_t len, error_t *err) +{ + ssize_t ret; + + // clamp size + if (len < MSG_BUF_MIN_SIZE) + len = MSG_BUF_MIN_SIZE; + + // ensure space + if ((ERROR_CODE(err) = msg_buf_grow(buf, buf->off + len))) + goto error; + + // read + if ((ret = transport_read(transport, buf->base + buf->off, len, err)) < 0) + goto error; + + // no data left? + if (!ret) + return 0; + + // update offset + buf->off += ret; + + // ok + return ret; + +error: + return -ERROR_CODE(err); +} + +/** + * Drives transport_write on the given data until all the given data is written, or zero is returned. + * + * @param transport transport to write to + * @param data input data + * @param len number of bytes to write from data + * @param err returned error info + * @return number of bytes written (which may be zero or less than len), or -err_t. + */ +static ssize_t _transport_write_all (transport_t *transport, const char *data, size_t len, error_t *err) +{ + ssize_t ret; + size_t written = 0; + + while (len) { + // try and write out remaining data + if ((ret = transport_write(transport, data, len, err)) < 0) + goto error; + + if (!ret) { + // write buffer full + break; + + } else { + // update and continue + written += ret; + data += ret; + len -= ret; + } + } + + // ok + return written; + +error: + return -ERROR_CODE(err); +} + +/** + * If the buffer is empty, this will attempt to write the given data directly using transport_write until either all + * the data is written (in which case nothing more needs to be done), or the transport won't accept any more writes, + * in which case the remaining data will be buffered. + * + * If the buffer is not empty, then the given data will be added to the end of the buffer, since otherwise the order of + * data would be broken. + * + * In either case, transport_write semantics garuntee that our buffer will either be empty, or an on_write will be + * pending on the transport. See msg_buf_flush() for how to handle transport_callbacks::on_write. + */ +err_t msg_buf_write (struct msg_buf *buf, transport_t *transport, const void *data_ptr, size_t len, error_t *err) +{ + ssize_t ret; + const char *data = data_ptr; + + if (!buf->off) { + // no data buffered, so we can try and write directly + if ((ret = _transport_write_all(transport, data, len, err)) < 0) + goto error; + + // update written + data += ret; + len -= ret; + + if (len == 0) + // wrote it all + return SUCCESS; + } + + // ensure space + if ((ERROR_CODE(err) = msg_buf_grow(buf, buf->off + len))) + goto error; + + // store + memcpy(buf->base + buf->off, data, len); + + // update + buf->off += len; + + // ok + return SUCCESS; + +error: + return ERROR_CODE(err); +} + +/** + * Flush buffered write data to the transport, driving transport_write() until either all of our bufferd data has been + * written, or the transport will not accept any more. + * + * In either case, transport_write semantics garuntee that our buffer will either be empty, or an on_write will be + * pending on the transport. + */ +err_t msg_buf_flush (struct msg_buf *buf, transport_t *transport, error_t *err) +{ + ssize_t ret; + + // write + if ((ret = _transport_write_all(transport, buf->base, buf->off, err)) < 0) + goto error; + + if (ret) + // unbuffer the written data + msg_buf_drain(buf, ret); + + // ok + return SUCCESS; + +error: + return ERROR_CODE(err); +} + +/** + * Deinitialize msg_buf to release allocated buffers + */ +void msg_buf_deinit (struct msg_buf *buf) +{ + // release + free(buf->base); + + // reset + buf->base = NULL; + buf->size = buf->off = 0; +} + +/** + * Message header + */ +struct msg_header { + /** Message length, including header */ + uint16_t len; +}; + +/** + * Message header size + */ +#define MSG_PROTO_HEADER_SIZE (sizeof(uint16_t)) + +/** + * Our state struct + */ +struct msg_proto { + /** The transport */ + transport_t *transport; + + /** User callbacks */ + const struct msg_proto_callbacks *cb_tbl; + + /** User callback argument */ + void *cb_arg; + + /** Input buffer */ + struct msg_buf in; + + /** Output buffer */ + struct msg_buf out; +}; + +/** + * Signal error to user + */ +static void msg_proto_error (struct msg_proto *proto, const error_t *err) +{ + // invoke user callback + proto->cb_tbl->on_error(proto, err, proto->cb_arg); +} + +/** + * Attempt to read the current header from our input buffer. + * + * Returns >0 for full header, 0 for incomplete header, -err_t for error. + */ +static int msg_proto_peek_header (struct msg_proto *proto, struct msg_header *header, error_t *err) +{ + if (proto->in.off < MSG_PROTO_HEADER_SIZE) + // not enough data for header + return 0; + + // read header + header->len = ntohs(*((uint16_t *) proto->in.base)); + + // bad header? + if (header->len < MSG_PROTO_HEADER_SIZE) + JUMP_SET_ERROR_STR(err, ERR_MISC, "message_header::len"); + + // ok, got header + return 1; + +error: + return -ERROR_CODE(err); +} + +/** + * Recieved a message with the given header, and a pointer to the message data + * + * XXX: what to do if the user callback destroys the msg_proto? + */ +static err_t msg_proto_on_msg (struct msg_proto *proto, struct msg_header *header, char *data, error_t *err) +{ + (void) err; + + // invoke user callback + proto->cb_tbl->on_msg(proto, data, header->len - MSG_PROTO_HEADER_SIZE, proto->cb_arg); + + // XXX: handle user errors + return SUCCESS; +} + +static void msg_proto_on_read (transport_t *transport, void *arg) +{ + struct msg_proto *proto = arg; + struct msg_header header; + ssize_t ret; + error_t err; + + // we might be able to read more than one message per event + do { + // try and read message length for incomplete message + if ((ret = msg_proto_peek_header(proto, &header, &err)) < 0) + goto error; + + // need to read more data? + if (!ret || header.len > proto->in.off) { + // msg_buf_read a minimum size, so passing a zero is OK + size_t to_read = ret ? header.len : 0; + + // read into our buffer + if ((ret = msg_buf_read(&proto->in, transport, to_read, &err)) < 0) + goto error; + + } else { + // handle full message + if (msg_proto_on_msg(proto, &header, proto->in.base + MSG_PROTO_HEADER_SIZE, &err)) + goto error; + + // remove the data from the buffer + msg_buf_drain(&proto->in, header.len); + } + } while (ret); + + // ok + return; + +error: + // notify user + msg_proto_error(proto, &err); +} + +static void msg_proto_on_write (transport_t *transport, void *arg) +{ + struct msg_proto *proto = arg; + error_t err; + + // flush + if (msg_buf_flush(&proto->out, transport, &err)) + // notify user on transport errors + msg_proto_error(proto, &err); +} + +static void msg_proto_on_error (transport_t *transport, const error_t *err, void *arg) +{ + struct msg_proto *proto = arg; + + (void) transport; + + // report to user + msg_proto_error(proto, err); +} + +static const struct transport_callbacks msg_proto_transport_callbacks = { + .on_read = msg_proto_on_read, + .on_write = msg_proto_on_write, + .on_error = msg_proto_on_error, +}; + +err_t msg_proto_create (struct msg_proto **proto_ptr, transport_t *transport, const struct msg_proto_callbacks *cb_tbl, void *cb_arg, error_t *err) +{ + struct msg_proto *proto; + + // alloc + if ((proto = calloc(1, sizeof(*proto))) == NULL) + return ERR_MEM; + + // store + proto->transport = transport; + proto->cb_tbl = cb_tbl; + proto->cb_arg = cb_arg; + + // init + if ( + (ERROR_CODE(err) = msg_buf_init(&proto->in, 0)) + || (ERROR_CODE(err) = msg_buf_init(&proto->out, 0)) + ) + goto error; + + // setup transport + if ((ERROR_CODE(err) = transport_events(transport, TRANSPORT_READ | TRANSPORT_WRITE))) + goto error; + + transport_set_callbacks(transport, &msg_proto_transport_callbacks, proto); + + // ok + *proto_ptr = proto; + + return SUCCESS; + +error: + // release + msg_proto_destroy(proto); + + return ERROR_CODE(err); +} + +/** + * Build and write out the data for the given header + */ +static err_t msg_proto_write_header (struct msg_proto *proto, const struct msg_header *header, error_t *err) +{ + char buf[MSG_PROTO_HEADER_SIZE]; + + // validate + if (header->len < MSG_PROTO_HEADER_SIZE) + return SET_ERROR(err, ERR_MISC); + + // build + *((uint16_t *) buf) = htons(header->len); + + // write + return msg_buf_write(&proto->out, proto->transport, buf, sizeof(buf), err); +} + +err_t msg_proto_send (struct msg_proto *proto, const void *data, size_t len, error_t *err) +{ + struct msg_header header; + + // build header + header.len = MSG_PROTO_HEADER_SIZE + len; + + // write it + if ( + msg_proto_write_header(proto, &header, err) + || msg_buf_write(&proto->out, proto->transport, data, len, err) + ) + return ERROR_CODE(err); + + // ok + return SUCCESS; +} + +void msg_proto_destroy (struct msg_proto *proto) +{ + // drop buffers + msg_buf_deinit(&proto->in); + msg_buf_deinit(&proto->out); + + // kill transport + transport_destroy(proto->transport); + + // release ourself + free(proto); +} + diff -r 5229a5d098b2 -r cefec18b8268 src/lib/msg_proto.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/msg_proto.h Thu May 28 01:17:36 2009 +0300 @@ -0,0 +1,54 @@ +#ifndef LIBQMSK_MSG_PROTO_H +#define LIBQMSK_MSG_PROTO_H + +/** + * @param + * + * Support for simple protocols that send/recieve length-prefixed messages over a transport stream. + * + * This implementation is mostly geared towards handling a reasonable number of reasonably sized messages in a + * reasonable way. Hence, + */ +#include "transport.h" + +/** + * Protocol state struct + */ +struct msg_proto; + +/** + * User callbacks + */ +struct msg_proto_callbacks { + /** + * Message recieved. + * + * XXX: currently you must not call msg_proto_destroy from within this callback + */ + void (*on_msg) (struct msg_proto *proto, void *data, size_t len, void *arg); + + /** + * Transport/protocol error occured in event handling. + */ + void (*on_error) (struct msg_proto *proto, const error_t *err, void *arg); +}; + +/** + * Create a msg_proto state using the given transport. + * + * This will install our callback handlers on the given transport. + */ +err_t msg_proto_create (struct msg_proto **proto_ptr, transport_t *transport, const struct msg_proto_callbacks *cb_tbl, void *cb_arg, error_t *err); + +/** + * Send a message to the other endpoint + */ +err_t msg_proto_send (struct msg_proto *proto, const void *data, size_t len, error_t *err); + +/** + * Destroy the protocol state and transport + */ +void msg_proto_destroy (struct msg_proto *proto); + + +#endif diff -r 5229a5d098b2 -r cefec18b8268 src/lib/resolve.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/resolve.c Thu May 28 01:17:36 2009 +0300 @@ -0,0 +1,109 @@ +#include "resolve.h" + +#include +#include +#include +#include + +static const char* _gai_error_msg (const struct error_extra_type *type, const union error_extra *extra) +{ + (void) type; + + return gai_strerror(extra->int_); +} + +static const struct error_extra_type _gai_error_type = { + .name = "gai", + .msg_func = _gai_error_msg +}; + + + +const struct error_list resolve_errors = ERROR_LIST("resolve", + ERROR_TYPE_EXTRA( ERR_RESOLVE_GETADDRINFO, "getaddrinfo", &_gai_error_type), + ERROR_TYPE_EXTRA( ERR_RESOLVE_GETADDRINFO_EMPTY, "getaddrinfo: no results", &_gai_error_type) + +); + +err_t resolve_addr (struct resolve_result *res, const char *node, const char *service, int socktype, int ai_flags, error_t *err) +{ + struct addrinfo hints, *ai; + int ret; + + // build hints + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = ai_flags; + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = socktype; + + // resolve (blocking) + if ((ret = getaddrinfo(node, service, &hints, &ai))) + return SET_ERROR_EXTRA(err, &resolve_errors, ERR_RESOLVE_GETADDRINFO, &_gai_error_type, ret); + + // no results? + if (!ai) + return SET_ERROR(err, &resolve_errors, ERR_RESOLVE_GETADDRINFO_EMPTY); + + // store + res->list = res->item = ai; + + // ok + return SUCCESS; +} + +void resolve_result_init (struct resolve_result *res) +{ + res->list = res->item = NULL; +} + +struct addrinfo* resolve_result_next (struct resolve_result *res) +{ + if (!res->item) { + // no items left + return NULL; + + } else { + // ...remember the current item + struct addrinfo *ai = res->item; + + if (res->item) + // advance item to the next one + res->item = res->item->ai_next; + + // return the current one + return ai; + } +} + +void resolve_result_deinit (struct resolve_result *res) +{ + if (res->list) + // free them all + freeaddrinfo(res->list); + + // invalidate + res->list = res->item = NULL; +} + +const char * resolve_addr_text (const struct addrinfo *addr) +{ + static char text[1024]; + char host[NI_MAXHOST], service[NI_MAXSERV]; + int ret; + + // lookup the reverse nameinfo + if ((ret = getnameinfo( + addr->ai_addr, addr->ai_addrlen, + host, sizeof(host), service, sizeof(service), + NI_NUMERICHOST | NI_NUMERICSERV + ))) { + strcpy(host, "???"); + strcpy(service, "???"); + } + + // format message + snprintf(text, sizeof(text), "[%s]:%s", host, service); + + // return static pointer + return text; +} diff -r 5229a5d098b2 -r cefec18b8268 src/lib/resolve.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/resolve.h Thu May 28 01:17:36 2009 +0300 @@ -0,0 +1,70 @@ +#ifndef LIBQMSK_RESOLVE_H +#define LIBQMSK_RESOLVE_H + +/** + * @file + * + * DNS resolver interface + */ +#include "error.h" +#include + +/** + * Errors + */ +enum resolve_error_code { + ERR_RESOLVE_NONE, + ERR_RESOLVE_GETADDRINFO, ///< getaddrinfo: + ERR_RESOLVE_GETADDRINFO_EMPTY, ///< getaddrinfo: no results +}; + +const struct error_list resolve_errors; + +/** + * Lookup result state + */ +struct resolve_result { + /** Head of the addrinfo list */ + struct addrinfo *list; + + /** Current addrinfo item */ + struct addrinfo *item; +}; + +/** + * Resolve the given node/service tuple as a series of addrinfos for the given socktype. + * + * This will never return an empty result. + * + * XXX: still blocking DNS stuff + * + * @param res where to store the result state + * @param node hostname/address to look up + * @param service service/port to look up + * @param socktype a SOCK_* value to return addrinfo's for that socktype + * @param ai_flags optional bitmask of AI_* flags to use + * @param err returned error info + */ +err_t resolve_addr (struct resolve_result *res, const char *node, const char *service, int socktype, int ai_flags, error_t *err); + +/** + * Initialize the given result to zero + */ +void resolve_result_init (struct resolve_result *res); + +/** + * Get the next address from a result, if any left + */ +struct addrinfo* resolve_result_next (struct resolve_result *res); + +/** + * Release the addrinfo resources associated with the given result + */ +void resolve_result_deinit (struct resolve_result *res); + +/** + * Returns a pointer to a static buffer containing a string description of the given addrinfo + */ +const char * resolve_addr_text (const struct addrinfo *addr); + +#endif diff -r 5229a5d098b2 -r cefec18b8268 src/lib/service.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/service.c Thu May 28 01:17:36 2009 +0300 @@ -0,0 +1,37 @@ +#include "service_internal.h" + +#include + +const struct object_type service_type_type = { + .parent = NULL, +}; + +void service_init (service_t *service, const struct service_type *type, const struct service_info *info) +{ + // init object + object_init(&service->base_obj, &type->base_type); + + // store user info + service->info = *info; +} + +void* service_check (service_t *service, const struct service_type *type) +{ + return object_cast(&service->base_obj, &type->base_type); +} + +void service_error (service_t *service, const error_t *err) +{ + // just call the user callback + service->info.cb_tbl->on_error(service, err, service->info.cb_arg); +} + +void service_destroy (service_t *service) +{ + const struct service_type *type = object_type(&service->base_obj, &service_type_type); + + // invoke method + type->methods.deinit(service); + + free(service); +} diff -r 5229a5d098b2 -r cefec18b8268 src/lib/service.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/service.h Thu May 28 01:17:36 2009 +0300 @@ -0,0 +1,50 @@ +#ifndef LIBQMSK_SERVICE_H +#define LIBQMSK_SERVICE_H + +/** + * @file + * + * Defines a simple interface for creating services, which listen for connections and create transport_t's. + */ +#include "transport.h" + +/** + * Opaque state struct. + */ +typedef struct service service_t; + +/** + * User callbacks for services. + */ +struct service_callbacks { + /** + * The service broke. + * + * This is only called for errors which occur when called directly from the event loop, and never for errors that + * occur inside of calls to service_*. + */ + void (*on_error) (service_t *service, const error_t *err, void *arg); +}; + +/** + * User info required to build a service + */ +struct service_info { + /** Callback table */ + const struct service_callbacks *cb_tbl; + + /** Callback context arg */ + void *cb_arg; + + /** Settings for the service's client transports */ + struct transport_info trans_info; +}; + +/** + * Destroy a service to stop accepting any connections and release all resources. + * + * Any connected client transports should stay intact (?) + */ +void service_destroy (service_t *service); + +#endif diff -r 5229a5d098b2 -r cefec18b8268 src/lib/service_internal.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/service_internal.h Thu May 28 01:17:36 2009 +0300 @@ -0,0 +1,60 @@ +#ifndef SERVICE_INTERNAL_H +#define SERVICE_INTERNAL_H + +/** + * @file + * + * Internal interface for implementations of service_t + */ +#include "service.h" +#include "transport.h" +#include "object.h" + +/** + * The object_type of service_type + */ +extern const struct object_type service_type_type; + +/** + * Type definition with method table + */ +struct service_type { + struct object_type base_type; + + /** Method table */ + struct service_methods { + /** + * Release internal state, but not the service_t itself + */ + void (*deinit) (service_t *service); + } methods; +}; + +/** + * Base service_t state + */ +struct service { + struct object base_obj; + + /** User info */ + struct service_info info; +}; + +/** + * Initialize a service by binding it to a specific type, with the given user info for this service, and for spawned transports. + */ +void service_init (service_t *service, const struct service_type *type, const struct service_info *info); + +/** + * Used to up-cast a generic service_t pointer to an implementation of the given service_type (or subtype). + * + * It is a bug to call this with a service of a different type. + */ +void* service_check (service_t *service, const struct service_type *type); + +/** + * The service failed, call the user callback + */ +void service_error (service_t *service, const error_t *err); + +#endif /* SERVICE_INTERNAL_H */ diff -r 5229a5d098b2 -r cefec18b8268 src/lib/sock.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/sock.c Thu May 28 01:17:36 2009 +0300 @@ -0,0 +1,22 @@ + +#include "sock_internal.h" +#include "ssl_internal.h" + +#include + +// global sock_stream_ctx instance +struct sock_stream_ctx _sock_stream_ctx; + +err_t sock_init (struct event_base *ev_base, error_t *err) +{ + // store ev_base + _sock_stream_ctx.ev_base = ev_base; + + // XXX: just call these all directly for now + if (ssl_global_init(err)) + return error_code(err); + + // done + return SUCCESS; +} + diff -r 5229a5d098b2 -r cefec18b8268 src/lib/sock.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/sock.h Thu May 28 01:17:36 2009 +0300 @@ -0,0 +1,25 @@ +#ifndef LIBQMSK_SOCK_H +#define LIBQMSK_SOCK_H + +/** + * @file + * + * Legacy sock_* interface for global state. + * + * XXX: replace with separate event_base per transport + */ +#include "error.h" +#include +#include + +/** + * Initialize the socket module's global state. Call this before calling any other sock_* functions. + * + * The given \a ev_base is the libevent base to use for nonblocking operation. + * + * @param ev_base the libevent base to use for events + * @param err returned error info + */ +err_t sock_init (struct event_base *ev_base, error_t *err); + +#endif diff -r 5229a5d098b2 -r cefec18b8268 src/lib/sock_internal.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/sock_internal.h Thu May 28 01:17:36 2009 +0300 @@ -0,0 +1,20 @@ +#ifndef SOCK_INTERNAL_H +#define SOCK_INTERNAL_H + +/** + * @file + * + * internal sock_* interface + */ +#include "sock.h" + +/** + * Global sock_stream_ctx used for sock_init() and all sock_stream's + */ +extern struct sock_stream_ctx { + /** libevent core */ + struct event_base *ev_base; + +} _sock_stream_ctx; + +#endif /* SOCK_INTERNAL_H */ diff -r 5229a5d098b2 -r cefec18b8268 src/lib/ssl.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/ssl.c Thu May 28 01:17:36 2009 +0300 @@ -0,0 +1,129 @@ +#include "ssl_internal.h" + +#include + +static const char* _gnutls_error_msg (const struct error_extra_type *type, const union error_extra *extra) +{ + (void) type; + + return gnutls_strerror(extra->int_); +} + +static const struct error_extra_type _gnutls_error_type = { + .name = "gnutls", + .msg_func = _gnutls_error_msg +}; + +const struct error_list ssl_errors = ERROR_LIST("gnutls", + ERROR_TYPE_EXTRA( ERR_GNUTLS_CERT_ALLOC_CRED, "gnutls_certificate_allocate_credentials", &_gnutls_error_type ), + ERROR_TYPE_EXTRA( ERR_GNUTLS_GLOBAL_INIT, "gnutls_global_init", &_gnutls_error_type ), + ERROR_TYPE_EXTRA( ERR_GNUTLS_SET_DEFAULT_PRIORITY, "gnutls_set_default_priority", &_gnutls_error_type ), + ERROR_TYPE_EXTRA( ERR_GNUTLS_CRED_SET, "gnutls_credentials_set", &_gnutls_error_type ), + ERROR_TYPE_EXTRA( ERR_GNUTLS_HANDSHAKE, "gnutls_handshake", &_gnutls_error_type ), + ERROR_TYPE_EXTRA( ERR_GNUTLS_RECORD_SEND, "gnutls_record_send", &_gnutls_error_type ), + ERROR_TYPE_EXTRA( ERR_GNUTLS_RECORD_RECV, "gnutls_record_recv", &_gnutls_error_type ), + ERROR_TYPE_EXTRA( ERR_GNUTLS_RECORD_GET_DIRECTION, "gnutls_record_get_direction", &_gnutls_error_type ), + ERROR_TYPE_EXTRA( ERR_GNUTLS_CERT_VERIFY_PEERS2, "gnutls_certificate_verify_peers2", &_gnutls_error_type ), + ERROR_TYPE_STRING( ERR_GNUTLS_CERT_VERIFY, "X.509 Certificate verification failed" ), + ERROR_TYPE_EXTRA( ERR_GNUTLS_CERT_SET_X509_TRUST_FILE,"gnutls_certificate_set_x509_trust_file", &_gnutls_error_type ), + ERROR_TYPE_EXTRA( ERR_GNUTLS_CERT_SET_X509_KEY_FILE, "gnutls_certificate_set_x509_key_file", &_gnutls_error_type ) +); + +/* + * Global shared anonymous client credentials + */ +struct ssl_client_cred ssl_client_cred_anon = { .x509 = NULL, .verify = false, .refcount = 0 }; + + +// XXX: GnuTLS log func +void _log (int level, const char *msg) +{ + printf("gnutls: %d: %s", level, msg); +} + +err_t ssl_global_init (error_t *err) +{ + // global init + if ((ERROR_EXTRA(err) = gnutls_global_init()) < 0) + return SET_ERROR(err, ERR_GNUTLS_GLOBAL_INIT); + + // initialize the anon client credentials + if ((ERROR_EXTRA(err) = gnutls_certificate_allocate_credentials(&ssl_client_cred_anon.x509)) < 0) + return SET_ERROR(err, ERR_GNUTLS_CERT_ALLOC_CRED); + + // XXX: debug +// gnutls_global_set_log_function(&_log); +// gnutls_global_set_log_level(11); + + // done + return SUCCESS; +} + +static void ssl_client_cred_destroy (struct ssl_client_cred *cred) +{ + // simple + gnutls_certificate_free_credentials(cred->x509); + + free(cred); +} + +err_t ssl_client_cred_create (struct ssl_client_cred **ctx_cred, + const char *cafile_path, bool verify, + const char *cert_path, const char *pkey_path, + error_t *err +) { + struct ssl_client_cred *cred; + + // alloc it + if ((cred = calloc(1, sizeof(*cred))) == NULL) + return SET_ERROR(err, ERR_CALLOC); + + // create the cert + if ((ERROR_EXTRA(err) = gnutls_certificate_allocate_credentials(&cred->x509)) < 0) + JUMP_SET_ERROR(err, ERR_GNUTLS_CERT_ALLOC_CRED); + + // load the trusted ca certs? + if (cafile_path) { + // load them + if ((ERROR_EXTRA(err) = gnutls_certificate_set_x509_trust_file(cred->x509, cafile_path, GNUTLS_X509_FMT_PEM)) < 0) + JUMP_SET_ERROR(err, ERR_GNUTLS_CERT_SET_X509_TRUST_FILE); + + } + + // set the verify flags? + cred->verify = verify; + gnutls_certificate_set_verify_flags(cred->x509, 0); + + // load the client cert? + if (cert_path || pkey_path) { + // need both... + assert(cert_path && pkey_path); + + // load + if ((ERROR_EXTRA(err) = gnutls_certificate_set_x509_key_file(cred->x509, cert_path, pkey_path, GNUTLS_X509_FMT_PEM))) + JUMP_SET_ERROR(err, ERR_GNUTLS_CERT_SET_X509_KEY_FILE); + } + + // ok + cred->refcount = 1; + *ctx_cred = cred; + + return SUCCESS; + +error: + // release + ssl_client_cred_destroy(cred); + + return ERROR_CODE(err); +} + +void ssl_client_cred_get (struct ssl_client_cred *cred) +{ + cred->refcount++; +} + +void ssl_client_cred_put (struct ssl_client_cred *cred) +{ + if (--cred->refcount == 0) + ssl_client_cred_destroy(cred); +} diff -r 5229a5d098b2 -r cefec18b8268 src/lib/ssl.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/ssl.h Thu May 28 01:17:36 2009 +0300 @@ -0,0 +1,75 @@ +#ifndef LIBQMSK_SSL_H +#define LIBQMSK_SSL_H + +/** + * @file + * + * SSL transport implementation. + */ +#include "transport.h" +#include + +/** + * SSL client credentials for use with ssl_client_credentials/sock_ssl_connect + */ +struct ssl_client_cred; + +/** + * Set up SSL client credentials for use with sock_ssl_connect. This includes information both required to identify + * ourselves to the server, as well as to verify the server. + * + * To verify the server's certificate, pass in a path to a file containing the CA certificate(s) that should be used to + * verify the server's certificate, and then either give `verify` as true to force verification, or false to simply + * warn. XXX: not entirely true + * + * To supply a client certificate to the server, pass in the paths to the cert/pkey files. If given as NULL, an + * anonymous client certificate will be used. Both must be supplied if given. + * + * The newly created SSL client credential will initially have a refcount of one, and can then be used with sock_ssl_connect. + * + * @param ctx_cred the newly created client credentials are returned via this + * @param cafile_path given as non-NULL to load trusted certs for verification from the given path + * @param verify force verification of the peer cert + * @param cert_path path to the client certificate file, or NULL + * @param pkey_path path to the client private key, or NULL + * @param err returned error info + */ +err_t ssl_client_cred_create (struct ssl_client_cred **ctx_cred, + const char *cafile_path, bool verify, + const char *cert_path, const char *pkey_path, + error_t *err +); + +/** + * Aquire a referenec for the given cred. + */ +void ssl_client_cred_get (struct ssl_client_cred *cred); + +/** + * Release a reference allocated for the given cred. + */ +void ssl_client_cred_put (struct ssl_client_cred *cred); + +/** + * Start a non-blocking SSL connect/handshake to the given host/service. The socket will not yet be connected when the + * function returns, but rather, the eventual redyness/failure of the connect/handshake will be indicated later using + * the given \a cb_func. + * + * The given ssl_client_cred should either be NULL to use an anonymous client cert and not verify the server cert, + * or a ssl_client_cred allocated using ssl_client_cred_create(). The contexts are reference-counted, so once + * a cred context has been released, it will be destroyed once the last connection using it is destroyed. + * + * @param info the transport setup info + * @param transport_ptr returned transport + * @param hostname the hostname to connect to + * @param service the TCP service name (i.e. port) to connect to + * @param cred the SSL client credentials to use, if not NULL + * @param err returned error info + */ +err_t ssl_connect (const struct transport_info *info, transport_t **transport_ptr, + const char *hostname, const char *service, + struct ssl_client_cred *cred, + error_t *err + ); + +#endif diff -r 5229a5d098b2 -r cefec18b8268 src/lib/ssl_client.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/ssl_client.c Thu May 28 01:17:36 2009 +0300 @@ -0,0 +1,454 @@ +#include "ssl_internal.h" + +#include + +#include +#include +#include + +// XXX: remove +#include "log.h" +#include + + +/** + * Cast a ssl_client to a sock_fd. + */ +#define SSL_CLIENT_FD(client_ptr) (&(client_ptr)->base_tcp.base_trans.base_fd) + +/** + * Cast a ssl_client to a sock_stream. + */ +#define SSL_CLIENT_TRANSPORT(client_ptr) (&(client_ptr)->base_tcp.base_trans.base_fd.base) + + + +/** + * Enable the TCP events based on the session's gnutls_record_get_direction(). + */ +static err_t ssl_client_ev_enable (struct ssl_client *client, error_t *err) +{ + int ret; + short mask; + + // gnutls_record_get_direction tells us what I/O operation gnutls would have required for the last + // operation, so we can use that to determine what events to register + switch ((ret = gnutls_record_get_direction(client->session))) { + case 0: + // read more data + mask = TRANSPORT_READ; + break; + + case 1: + // write buffer full + mask = TRANSPORT_WRITE; + break; + + default: + // random error + RETURN_SET_ERROR_EXTRA(err, ERR_GNUTLS_RECORD_GET_DIRECTION, ret); + } + + // do the enabling + if ((ERROR_CODE(err) = transport_fd_enable(SSL_CLIENT_FD(client), mask))) + return ERROR_CODE(err); + + + return SUCCESS; +} + +/** + * Translate a set of gnutls_certificate_status_t values to a constant error message + */ +static const char* ssl_client_verify_error (unsigned int status) +{ + if (status & GNUTLS_CERT_REVOKED) + return "certificate was revoked"; + + else if (status & GNUTLS_CERT_INVALID) { + if (status & GNUTLS_CERT_SIGNER_NOT_FOUND) + return "certificate signer was not found"; + + else if (status & GNUTLS_CERT_SIGNER_NOT_CA) + return "certificate signer is not a Certificate Authority"; + + else if (status & GNUTLS_CERT_INSECURE_ALGORITHM) + return "certificate signed using an insecure algorithm"; + + else + return "certificate could not be verified"; + + } else + return "unknown error"; + +} + +/** + * Perform the certificate validation procedure on the peer cert. + * + * Based on the GnuTLS examples/ex-rfc2818.c + */ +static err_t ssl_client_verify (struct ssl_client *client, error_t *err) +{ + unsigned int status; + const gnutls_datum_t *cert_list; + unsigned int cert_list_size; + gnutls_x509_crt_t cert = NULL; + time_t t, now; + + // init + RESET_ERROR(err); + now = time(NULL); + + // inspect the peer's cert chain using the installed trusted CAs + if ((ERROR_EXTRA(err) = gnutls_certificate_verify_peers2(client->session, &status))) + JUMP_SET_ERROR(err, ERR_GNUTLS_CERT_VERIFY_PEERS2); + + // verify errors? + if (status) + JUMP_SET_ERROR_STR(err, ERR_GNUTLS_CERT_VERIFY, ssl_client_verify_error(status)); + + // import the main cert + assert(gnutls_certificate_type_get(client->session) == GNUTLS_CRT_X509); + + if ((ERROR_EXTRA(err) = gnutls_x509_crt_init(&cert))) + JUMP_SET_ERROR_STR(err, ERR_GNUTLS_CERT_VERIFY, "gnutls_x509_crt_init"); + + if ((cert_list = gnutls_certificate_get_peers(client->session, &cert_list_size)) == NULL) + JUMP_SET_ERROR_STR(err, ERR_GNUTLS_CERT_VERIFY, "gnutls_certificate_get_peers"); + + if (!cert_list_size) + JUMP_SET_ERROR_STR(err, ERR_GNUTLS_CERT_VERIFY, "cert_list_size"); + + if ((ERROR_EXTRA(err) = gnutls_x509_crt_import(cert, &cert_list[0], GNUTLS_X509_FMT_DER))) + JUMP_SET_ERROR_STR(err, ERR_GNUTLS_CERT_VERIFY, "gnutls_x509_crt_import"); + + // check expire/activate... not sure if we need to do this + if ((t = gnutls_x509_crt_get_expiration_time(cert)) == ((time_t) -1) || t < now) + JUMP_SET_ERROR_STR(err, ERR_GNUTLS_CERT_VERIFY, "gnutls_x509_crt_get_expiration_time"); + + if ((t = gnutls_x509_crt_get_activation_time(cert)) == ((time_t) -1) || t > now) + JUMP_SET_ERROR_STR(err, ERR_GNUTLS_CERT_VERIFY, "gnutls_x509_crt_get_activation_time"); + + // check hostname + if (!gnutls_x509_crt_check_hostname(cert, client->hostname)) + JUMP_SET_ERROR_STR(err, ERR_GNUTLS_CERT_VERIFY, "gnutls_x509_crt_check_hostname"); + +error: + // cleanup + if (cert) + gnutls_x509_crt_deinit(cert); + + // should be SUCCESS + return ERROR_CODE(err); +} + + +/** + * Our handshake driver. This will execute the next gnutls_handshake step, handling E_AGAIN. + * + * This updates the ssl_client::handshake state internally, as used by ssl_client_event_handler. + * + * If the client is marked as verify, this will perform the verification, returning on any errors, and then unset the + * verify flag - this ensures that the peer cert is only verified once per connection... + * + * @return >0 for finished handshake, 0 for handshake-in-progress, -err_t for errors. + */ +static int ssl_client_handshake (struct ssl_client *client, error_t *err) +{ + int ret; + + // perform the handshake + if ((ret = gnutls_handshake(client->session)) < 0 && ret != GNUTLS_E_AGAIN) + JUMP_SET_ERROR_EXTRA(err, ERR_GNUTLS_HANDSHAKE, ret); + + // complete? + if (ret == 0) { + // update state + client->handshake = false; + + // verify? + if (client->verify) { + // perform the validation + if (ssl_client_verify(client, err)) + goto error; + + // unmark + client->verify = false; + } + + // handshake done + return 1; + + } else { + // set state, isn't really needed every time, but easier this way + client->handshake = true; + + // re-enable the event for the next iteration + return ssl_client_ev_enable(client, err); + } + +error: + return -ERROR_CODE(err); +} + +/** + * Our transport_fd event handler. Drive the handshake if that's current, otherwise, invoke user callbacks. + */ +static void ssl_client_on_event (struct transport_fd *fd, short what, void *arg) +{ + struct ssl_client *client = arg; + error_t err; + + (void) fd; + + // XXX: timeouts + (void) what; + + // are we in the handshake cycle? + if (client->handshake) { + RESET_ERROR(&err); + + // perform the next handshake step + // this returns zero when the handshake is not yet done, errors/completion then trigger the else-if-else below + if (ssl_client_handshake(client, &err) == 0) { + // handshake continues + + } else if (!SSL_CLIENT_TRANSPORT(client)->connected) { + // the async connect+handshake process has completed + // invoke the user connect callback directly with appropriate error + transport_connected(SSL_CLIENT_TRANSPORT(client), ERROR_CODE(&err) ? &err : NULL, true); + + } else { + // in-connection re-handshake completed + if (ERROR_CODE(&err)) + // the re-handshake failed, so this transport is dead + transport_error(SSL_CLIENT_TRANSPORT(client), &err); + + else + // re-handshake completed, so continue with the transport_callbacks + transport_invoke(SSL_CLIENT_TRANSPORT(client), what); + } + + } else { + // normal transport operation + // gnutls might be able to proceed now, so invoke user callbacks + transport_invoke(SSL_CLIENT_TRANSPORT(client), what); + } +} + +static err_t ssl_client__read (transport_t *transport, void *buf, size_t *len, error_t *err) +{ + struct ssl_client *client = transport_check(transport, &ssl_client_type); + int ret; + + // read gnutls record + do { + ret = gnutls_record_recv(client->session, buf, *len); + + } while (ret == GNUTLS_E_INTERRUPTED); + + // errors + // XXX: E_REHANDSHAKE? + if (ret < 0 && ret != GNUTLS_E_AGAIN) + RETURN_SET_ERROR_EXTRA(err, ERR_GNUTLS_RECORD_RECV, ret); + + else if (ret == 0) + return SET_ERROR(err, ERR_EOF); + + + // EAGAIN? + if (ret < 0) { + *len = 0; + + } else { + // updated length + *len = ret; + + } + + return SUCCESS; +} + +static err_t ssl_client__write (transport_t *transport, const void *buf, size_t *len, error_t *err) +{ + struct ssl_client *client = transport_check(transport, &ssl_client_type); + int ret; + + // read gnutls record + do { + ret = gnutls_record_send(client->session, buf, *len); + + } while (ret == GNUTLS_E_INTERRUPTED); + + // errors + if (ret < 0 && ret != GNUTLS_E_AGAIN) + RETURN_SET_ERROR_EXTRA(err, ERR_GNUTLS_RECORD_SEND, ret); + + else if (ret == 0) + return SET_ERROR(err, ERR_WRITE_EOF); + + + // eagain? + if (ret < 0) { + *len = 0; + + } else { + // updated length + *len = ret; + } + + return SUCCESS; +} + +void ssl_client_deinit (struct ssl_client *client) +{ + // close the session rudely + gnutls_deinit(client->session); + client->session = NULL; + + // terminate the TCP transport + tcp_client_deinit(&client->base_tcp); + + if (client->cred) { + // drop the cred ref + ssl_client_cred_put(client->cred); + + client->cred = NULL; + } + + // free + free(client->hostname); + client->hostname = NULL; +} + + +static void ssl_client__deinit (transport_t *transport) +{ + struct ssl_client *client = transport_check(transport, &ssl_client_type); + + // die + ssl_client_deinit(client); +} + +/** + * Our tcp_client-invoked connect handler + */ +static void ssl_client__connected (transport_t *transport, const error_t *tcp_err) +{ + struct ssl_client *client = transport_check(transport, &ssl_client_type); + error_t err; + + // trap errors to let the user handle them directly + if (tcp_err) + JUMP_SET_ERROR_INFO(&err, tcp_err); + + // bind default transport functions (recv/send) to use the TCP fd + gnutls_transport_set_ptr(client->session, (gnutls_transport_ptr_t) (long int) SSL_CLIENT_FD(client)->fd); + + // add ourselves as the event handler + if ((ERROR_CODE(&err) = transport_fd_setup(SSL_CLIENT_FD(client), ssl_client_on_event, client))) + goto error; + + // start handshake + if (ssl_client_handshake(client, &err)) + // this should complete with SUCCESS if it returns >0 + goto error; + + // ok, so we wait... + return; + +error: + // tell the user + transport_connected(transport, &err, true); +} + +struct transport_type ssl_client_type = { + .base_type = { + .parent = &tcp_client_type.base_type, + }, + .methods = { + .read = ssl_client__read, + .write = ssl_client__write, + .deinit = ssl_client__deinit, + ._connected = ssl_client__connected, + }, +}; + + + +static void ssl_client_destroy (struct ssl_client *client) +{ + ssl_client_deinit(client); + + free(client); +} + +err_t ssl_connect (const struct transport_info *info, transport_t **transport_ptr, + const char *hostname, const char *service, + struct ssl_client_cred *cred, + error_t *err + ) +{ + struct ssl_client *client = NULL; + + // alloc + if ((client = calloc(1, sizeof(*client))) == NULL) + return SET_ERROR(err, ERR_CALLOC); + + // initialize base + transport_init(SSL_CLIENT_TRANSPORT(client), &ssl_client_type, info); + + if (!cred) { + // default credentials + cred = &ssl_client_cred_anon; + + } else { + // take a ref + client->cred = cred; + cred->refcount++; + }; + + // do verify? + if (cred->verify) + client->verify = true; + + // init + if ((client->hostname = strdup(hostname)) == NULL) + JUMP_SET_ERROR(err, ERR_STRDUP); + + // initialize TCP + tcp_client_init(&client->base_tcp); + + // initialize client session + if ((ERROR_EXTRA(err) = gnutls_init(&client->session, GNUTLS_CLIENT)) < 0) + JUMP_SET_ERROR(err, ERR_GNUTLS_INIT); + + // ...default priority stuff + if ((ERROR_EXTRA(err) = gnutls_set_default_priority(client->session))) + JUMP_SET_ERROR(err, ERR_GNUTLS_SET_DEFAULT_PRIORITY); + + // XXX: silly hack for OpenSSL interop + gnutls_dh_set_prime_bits(client->session, 512); + + // bind credentials + if ((ERROR_EXTRA(err) = gnutls_credentials_set(client->session, GNUTLS_CRD_CERTIFICATE, cred->x509))) + JUMP_SET_ERROR(err, ERR_GNUTLS_CRED_SET); + + // TCP connect + if (tcp_client_connect_async(&client->base_tcp, hostname, service, err)) + goto error; + + // done, wait for the connect to complete + *transport_ptr = SSL_CLIENT_TRANSPORT(client); + + return SUCCESS; + +error: + // cleanup + ssl_client_destroy(client); + + return ERROR_CODE(err); +} + + diff -r 5229a5d098b2 -r cefec18b8268 src/lib/ssl_internal.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/ssl_internal.h Thu May 28 01:17:36 2009 +0300 @@ -0,0 +1,88 @@ +#ifndef LIBQMSK_SSL_INTERNAL_H +#define LIBQMSK_SSL_INTERNAL_H + +/** + * @file + * + * A sock_stream implementation using GnuTLS for SSL + */ +#include "ssl.h" +#include "tcp_internal.h" + +#include + +/** + * GnuTLS library error codes + */ +enum ssl_error_code { + ERR_GNUTLS_NONE, + ERR_GNUTLS_CERT_ALLOC_CRED, + ERR_GNUTLS_GLOBAL_INIT, + ERR_GNUTLS_INIT, + ERR_GNUTLS_SET_DEFAULT_PRIORITY, + ERR_GNUTLS_CRED_SET, + ERR_GNUTLS_HANDSHAKE, + ERR_GNUTLS_RECORD_SEND, + ERR_GNUTLS_RECORD_RECV, + ERR_GNUTLS_RECORD_GET_DIRECTION, + ERR_GNUTLS_CERT_VERIFY_PEERS2, + ERR_GNUTLS_CERT_VERIFY, + ERR_GNUTLS_CERT_SET_X509_TRUST_FILE, + ERR_GNUTLS_CERT_SET_X509_KEY_FILE, +}; + +const struct error_list ssl_errors; + +/** + * GnuTLS credentials for client sockets. + */ +struct ssl_client_cred { + /** Our client certificate */ + gnutls_certificate_credentials_t x509; + + /** Should we verify? */ + bool verify; + + /** Refcount from ssl_client */ + int refcount; +}; + +/** + * Global anonymous x509 credentials + */ +extern struct ssl_client_cred ssl_client_cred_anon; + +/* + * Our transport_type + */ +extern struct transport_type ssl_client_type; + +/** + * An SSL-encrypted TCP connection, using libgnutls + */ +struct ssl_client { + /** The underlying TCP connection */ + struct tcp_client base_tcp; + + /** The hostname we connected to, for verification */ + char *hostname; + + /** The credentials we are using, unless anon */ + struct ssl_client_cred *cred; + + /** The GnuTLS session for this connection */ + gnutls_session_t session; + + /** Should we verify the peer cert? */ + bool verify; + + /** Are we running a handshake? */ + bool handshake; +}; + +/** + * Initialize the global gnutls state + */ +err_t ssl_global_init (error_t *err); + +#endif diff -r 5229a5d098b2 -r cefec18b8268 src/lib/tcp.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/tcp.c Thu May 28 01:17:36 2009 +0300 @@ -0,0 +1,43 @@ +#include "tcp_internal.h" + +int tcp_sock_create (const struct addrinfo *addr, error_t *err) +{ + int sock; + + // create a new socket using addr->ai_family/socktype/protocol + if ((sock = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol)) < 0) + JUMP_SET_ERROR_ERRNO(err, ERR_SOCKET); + + return sock; + +error: + return -ERROR_CODE(err); +} + +err_t tcp_sock_error (evutil_socket_t sock, error_t *err) +{ + int optval; + socklen_t optlen; + + RESET_ERROR(err); + + // init params + optval = 0; + optlen = sizeof(optval); + + // read error code + if (getsockopt(sock, SOL_SOCKET, SO_ERROR, &optval, &optlen)) + RETURN_SET_ERROR_ERRNO(err, ERR_GETSOCKOPT); + + // sanity-check optlen... not sure if this is sensible + if (optlen != sizeof(optval)) + RETURN_SET_ERROR_EXTRA(err, ERR_GETSOCKOPT, EINVAL); + + // then store the system error code + ERROR_EXTRA(err) = optval; + + // ok + return SUCCESS; +} + + diff -r 5229a5d098b2 -r cefec18b8268 src/lib/tcp.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/tcp.h Thu May 28 01:17:36 2009 +0300 @@ -0,0 +1,35 @@ +#ifndef LIBQMSK_TCP_H +#define LIBQMSK_TCP_H + +/** + * @file + * + * TCP transport/service implementation. + * + * XXX: provide some TCP-specific type/functions? + */ +#include "transport.h" +#include "service.h" + +/** + * Connect the given transport via TCP to the given host/service. The transport will not be ready for use until the + * transport_callbacks::on_connect callback has been invoked. + * + * XXX: blocking DNS resolution + * + * @param info the transport setup info + * @param transport_ptr returned transport + * @param host the hostname to connect to + * @param service the service name (i.e. port) to connect to + * @param err returned error info + */ +err_t tcp_connect (const struct transport_info *info, transport_t **transport_ptr, + const char *host, const char *service, error_t *err); + +/** + * Create a passive/listening TCP socket on the given interface/port (NULL to pick automatically). + */ +err_t tcp_listen (const struct service_info *info, service_t **service_ptr, + const char *interface, const char *service, error_t *err); + +#endif diff -r 5229a5d098b2 -r cefec18b8268 src/lib/tcp_client.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/tcp_client.c Thu May 28 01:17:36 2009 +0300 @@ -0,0 +1,278 @@ +#include "tcp_internal.h" +#include "log.h" + +/* + * Our transport methods + */ +static void tcp_client__deinit (transport_t *transport) +{ + struct tcp_client *client = transport_check(transport, &tcp_client_type); + + // proxy + tcp_client_deinit(client); +} + +/* + * Our transport_type + */ +const struct transport_type tcp_client_type = { + .base_type = { + .parent = &tcp_transport_type.base_type, + }, + .methods = { + .read = transport_fd__read, + .write = transport_fd__write, + .events = transport_fd__events, + .deinit = tcp_client__deinit, + }, +}; + +/* + * Forward-declare + */ +static void tcp_client_on_connect (struct transport_fd *fd, short what, void *arg); + +/* + * Function implementations + */ +void tcp_client_init (struct tcp_client *client) +{ + tcp_transport_init(&client->base_trans, -1); + + resolve_result_init(&client->rr); +} + +/* + * Start connecting to the given address in a non-blocking fashion. Returns any errors that immediately crop up, + * otherwise eventually calls tcp_client_connect_done(). + */ +static err_t tcp_client_connect_addr (struct tcp_client *client, struct addrinfo *addr, error_t *err) +{ + struct transport_fd *_fd = &client->base_trans.base_fd; + int ret; + evutil_socket_t sock; + err_t tmp; + + // first, create the socket + if ((sock = tcp_sock_create(addr, err)) < 0) + return ERROR_CODE(err); + + // set it as our sock + if ((ERROR_CODE(err) = transport_fd_set(_fd, sock))) + goto error; + + // then, set it up as nonblocking + if ((ERROR_CODE(err) = transport_fd_nonblock(_fd, true))) + goto error; + + // then, initiate the connect + if ((ret = connect(sock, addr->ai_addr, addr->ai_addrlen)) < 0 && errno != EINPROGRESS) + JUMP_SET_ERROR_ERRNO(err, ERR_CONNECT); + + if (ret < 0) { + // ok, connect started, setup our completion callback + if ((ERROR_CODE(err) = transport_fd_setup(_fd, tcp_client_on_connect, client))) + goto error; + + // enable for write + if ((ERROR_CODE(err) = transport_fd_enable(_fd, TRANSPORT_WRITE))) + goto error; + + } else { + // oops... blocking connect - fail to avoid confusion + // XXX: come up with a better error name to use + // XXX: support non-async connects as well + JUMP_SET_ERROR_EXTRA(err, ERR_CONNECT, EINPROGRESS); + } + + // ok + return SUCCESS; + +error: + // close the stuff we did open + if ((tmp = transport_fd_close(_fd))) + log_warn("error closing socket after connect error: %s", error_name(tmp)); + + return ERROR_CODE(err); +} + + +/* + * Attempt to connect to the next addrinfo, or the next one, if that fails, etc. + * + * This does not call transport_connected(). + */ +static err_t tcp_client_connect_continue (struct tcp_client *client, error_t *err) +{ + struct addrinfo *addr; + + // try and connect to each one until we find one that works + while ((addr = resolve_result_next(&client->rr))) { + // attempt to start connect + if (tcp_client_connect_addr(client, addr, err) == SUCCESS) + break; + + // log a warning on the failed connect + log_warn_error(err, "%s", resolve_addr_text(addr)); + } + + + if (addr) + // we succesfully did a tcp_client_connect_addr on valid address + return SUCCESS; + + else + // all of the connect_async_addr's failed, return the last error + return ERROR_CODE(err); +} + +/* + * Cleanup our resolver state and any connect callbacks after a connect + */ +static void tcp_client_connect_cleanup (struct tcp_client *client) +{ + // drop the resolver stuff + resolve_result_deinit(&client->rr); + + // remove our event handler + transport_fd_clear(&client->base_trans.base_fd); +} + +/* + * Our async connect operation has completed, clean up, set up state for event-based operation with user callbacks, and + * invoke transport_connected(). + * + * The given \a err should be NULL for successful completion, or the error for failures. + */ +static void tcp_client_connect_done (struct tcp_client *client, error_t *conn_err) +{ + error_t err; + + // cleanup + tcp_client_connect_cleanup(client); + + if (conn_err) + JUMP_SET_ERROR_INFO(&err, conn_err); + + // let the transport handle the rest + if (tcp_transport_connected(&client->base_trans, &err)) + goto error; + + // ok + return; + +error: + // pass the error on to transport + transport_connected(&client->base_trans.base_fd.base, &err, false); +} + +/* + * Our async connect callback + */ +static void tcp_client_on_connect (struct transport_fd *fd, short what, void *arg) +{ + struct tcp_client *client = arg; + error_t err; + err_t tmp; + + // XXX: timeouts + (void) what; + + // read socket error code + if (tcp_sock_error(client->base_trans.base_fd.fd, &err)) + goto error; + + // did the connect fail? + if (ERROR_EXTRA(&err)) + JUMP_SET_ERROR(&err, ERR_CONNECT); + + // done, success + return tcp_client_connect_done(client, NULL); + +error: + // close the socket + if ((tmp = transport_fd_close(fd))) + log_warn("error closing socket after connect error: %s", error_name(tmp)); + + // log a warning + log_warn_error(&err, "connect to %s failed", "???"); + + // try the next one or fail completely + if (tcp_client_connect_continue(client, &err)) + tcp_client_connect_done(client, &err); +} + +err_t tcp_client_connect_async (struct tcp_client *client, const char *hostname, const char *service, error_t *err) +{ + // do the resolving + if (resolve_addr(&client->rr, hostname, service, SOCK_STREAM, 0, err)) + return ERROR_CODE(err); + + // start connecting with the first result + if (tcp_client_connect_continue(client, err)) + goto error; + + // ok + return SUCCESS; + +error: + // cleanup + resolve_result_deinit(&client->rr); + + return ERROR_CODE(err); +} + +void tcp_client_deinit (struct tcp_client *client) +{ + // cleanup our stuff + resolve_result_deinit(&client->rr); + + // deinit lower transport + tcp_transport_deinit(&client->base_trans); +} + +/* + * Deinit and free, not using the transport interface + */ +static void tcp_client_destroy (struct tcp_client *client) +{ + tcp_client_deinit(client); + + free(client); +} + +/* + * Public interface + */ +err_t tcp_connect (const struct transport_info *info, transport_t **transport_ptr, + const char *host, const char *service, error_t *err) +{ + struct tcp_client *client; + + // alloc + if ((client = calloc(1, sizeof(*client))) == NULL) + return ERR_CALLOC; + + // init transport + transport_init(&client->base_trans.base_fd.base, &tcp_client_type, info); + + // init our state + tcp_client_init(client); + + // begin connect + if (tcp_client_connect_async(client, host, service, err)) + goto error; + + // good + *transport_ptr = &client->base_trans.base_fd.base; + + return 0; + +error: + // cleanup + tcp_client_destroy(client); + + // return error code + return ERROR_CODE(err); +} + diff -r 5229a5d098b2 -r cefec18b8268 src/lib/tcp_internal.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/tcp_internal.h Thu May 28 01:17:36 2009 +0300 @@ -0,0 +1,170 @@ +#ifndef LIBQMSK_TCP_INTERNAL_H +#define LIBQMSK_TCP_INTERNAL_H + +/** + * @file + * + * Internal TCP interface for implementations + */ +#include "tcp.h" +#include "resolve.h" +#include "transport_fd.h" +#include "transport_internal.h" +#include "service_internal.h" +#include "error.h" + +#include +#include + +/** + * Create a new socket() using the given addr's family/socktype/protocol and return it. + * + * In case of errors, this returns -err_t + * + * @param addr the addrinfo to create the socket for + * @param err returned error info + * @return new fd on success, -err_t on error + */ +int tcp_sock_create (const struct addrinfo *addr, error_t *err); + +/** + * Return the socket's current error code via err->extra. + * + * In case getting the socket error code itself fails, this will return normal error code/info. + * + * Otherwise, this will return SUCCESS, with the errno value stored in err->extra. + */ +err_t tcp_sock_error (evutil_socket_t sock, error_t *err); + + +/** + * TCP transport type + */ +extern const struct transport_type tcp_transport_type; + +/** + * Base TCP transport + * + * XXX: currently just the same as transport_fd, but this will probably change + */ +struct tcp_transport { + /** Base FD-based implementation */ + struct transport_fd base_fd; +}; + +/** + * Initialize the tcp_transport state. + * + * This initializes the transport_fd base using the global sock_ctx::ev_base and the given socket. + */ +void tcp_transport_init (struct tcp_transport *trans, evutil_socket_t sock); + +/** + * Create a new tcp_transport with the given sock. + * + * For convenience, this will also make the sock nonblocking. + * + * In case of errors, this will the socket. + * + * @param trans_ptr returned tcp_transport + * @param info the transport user settings + * @param sock the unused TCP socket + * @param err returned error info + */ +err_t tcp_transport_create (struct tcp_transport **trans_ptr, const struct transport_info *info, evutil_socket_t sock, error_t *err); + +/** + * The transport as now connected, this sets up the intitial user settings, and invokes the callback. + * + * XXX: this does an 'indirect' call to transport_connected(). + * + * @param err returned error info + */ +err_t tcp_transport_connected (struct tcp_transport *trans, error_t *err); + +/** + * Deinitialize the transport state, terminating the connection and releasing resources. + */ +void tcp_transport_deinit (struct tcp_transport *trans); + +/** + * Deinitialize and free the given tcp_transport + */ +void tcp_transport_destroy (struct tcp_transport *trans); + +/** + * TCP client transport type + */ +extern const struct transport_type tcp_client_type; + +/** + * TCP client state + */ +struct tcp_client { + /** Base transport stuff */ + struct tcp_transport base_trans; + + /** The resolver lookup result for the async connect process */ + struct resolve_result rr; +}; + +/** + * Initialize the tcp_client state + */ +void tcp_client_init (struct tcp_client *client); + +/** + * Attempt to connect asyncronously to the given hostname/service. Once a connection has been established, this will + * call transport_connected(), so you can register transport_methods::_connected if layering on top of TCP. + * + * In case of errors while starting the async connect process, an error code will be returned. If the connect fails + * later on, transport_connected() will be called with the error code. + * + * The sock must have been initialized using sock_tcp_init(). + * + * @param client the unconnected TCP client socket to connect with + * @param hostname the hostname to resolve + * @param service the service to connect to + * @param err returned error info for immediate errors + */ +err_t tcp_client_connect_async (struct tcp_client *client, const char *hostname, const char *service, error_t *err); + +/** + * Deinitialize the tcp_client's state, including the tcp_transport state. + */ +void tcp_client_deinit (struct tcp_client *client); + + + +/** + * TCP service type + */ +extern const struct service_type tcp_server_type; + +/** + * TCP service state + */ +struct tcp_server { + /** Base service state */ + struct service base_service; + + /** The input event with our listen() socket */ + struct event *ev; +}; + +/** + * The listen() backlog + */ +#define TCP_SERVER_BACKLOG 5 + +/** + * Open the listening socket on the given interface/service. + */ +err_t tcp_server_listen (struct tcp_server *serv, const char *interface, const char *service, error_t *err); + +/** + * Release the tcp_server's state, and cleanup the struct. + */ +void tcp_server_deinit (struct tcp_server *serv); + +#endif /* TCP_INTERNAL_H */ diff -r 5229a5d098b2 -r cefec18b8268 src/lib/tcp_server.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/tcp_server.c Thu May 28 01:17:36 2009 +0300 @@ -0,0 +1,260 @@ +#include "tcp_internal.h" +#include "sock_internal.h" +#include "log.h" + +#include + +/* + * Service methods + */ +void tcp_server__deinit (service_t *service) +{ + struct tcp_server *serv = service_check(service, &tcp_server_type); + + tcp_server_deinit(serv); +} + +/* + * Service type + */ +const struct service_type tcp_server_type = { + .base_type = { + .parent = &service_type_type, + }, + .methods = { + .deinit = tcp_server__deinit, + }, +}; + +/* + * We got a new client, build a transport for it and give it to the user + */ +static err_t tcp_server_client (struct tcp_server *serv, evutil_socket_t sock, error_t *err) +{ + struct tcp_transport *trans; + + // create a new transport for it, this also makes it nonblocking + if (tcp_transport_create(&trans, &serv->base_service.info.trans_info, sock, err)) + goto error; + + // make it connected + // this will call transport_callbacks::on_connect, which is all the user needs + if (tcp_transport_connected(trans, err)) + goto error; + + // ok + return SUCCESS; + +error: + // cleanup + if (trans) + tcp_transport_destroy(trans); + + return ERROR_CODE(err); +} + +/* + * Libevent callback + */ +static void tcp_server_on_accept (evutil_socket_t sock, short what, void *arg) +{ + struct tcp_server *serv = arg; + evutil_socket_t client_sock; + error_t err; + + (void) what; + + // accept as a new client connection + if ((client_sock = accept(sock, NULL, NULL)) < 0 && errno != EAGAIN) + JUMP_SET_ERROR_ERRNO(&err, ERR_ACCEPT); + + // spurious read event? + if (client_sock < 0) + return; + + // handle it + if (tcp_server_client(serv, client_sock, &err)) + goto error; + + // ok + return; + +error: + if (client_sock >= 0) + EVUTIL_CLOSESOCKET(client_sock); + + // faaail + service_error(&serv->base_service, &err); +} + +/* + * Attempts to construct a listen()'d socket with the given addr, and return it + * + * @param addr the addrinfo to try and create a socket for + * @param err returned error info + * @return listening socket, or -err_t on error + */ +static int tcp_server_sock_addr (struct addrinfo *addr, error_t *err) +{ + evutil_socket_t sock; + + // create the sock + if ((sock = tcp_sock_create(addr, err)) < 0) + goto error; + + // bind it + if (bind(sock, addr->ai_addr, addr->ai_addrlen) < 0) + JUMP_SET_ERROR_ERRNO(err, ERR_BIND); + + // listen + if (listen(sock, TCP_SERVER_BACKLOG) < 0) + JUMP_SET_ERROR_ERRNO(err, ERR_LISTEN); + + // ok, valid socket + return sock; + +error: + if (sock >= 0) + // cleanup + EVUTIL_CLOSESOCKET(sock); + + return -ERROR_CODE(err); +} + +/* + * Construct a listen()'d socket with the given resolver result, and return it. + * + * @param rr the resolver lookup result to create a socket for + * @param err returned error info + * @return listening socket, or -err_t on error + */ +static int tcp_server_sock (struct resolve_result *rr, error_t *err) +{ + struct addrinfo *addr; + evutil_socket_t sock; + + // try each addrinfo + while ((addr = resolve_result_next(rr))) { + // attempt to construct given socket + if ((sock = tcp_server_sock_addr(addr, err)) < 0) + // log an informative error warning + log_warn_error(err, "%s", resolve_addr_text(addr)); + + else + // got a valid socket + break; + } + + if (sock >= 0) + // valid socket + return sock; + + else + // some error occured + return -ERROR_CODE(err); +} + +err_t tcp_server_listen (struct tcp_server *serv, const char *interface, const char *service, error_t *err) +{ + struct resolve_result rr; + evutil_socket_t sock; + + // get the global event_base + struct event_base *ev_base = _sock_stream_ctx.ev_base; + + // init the resolver + resolve_result_init(&rr); + + // resolve the interface/service + if (resolve_addr(&rr, interface, service, SOCK_STREAM, AI_PASSIVE, err)) + return ERROR_CODE(err); + + // create the socket + if ((sock = tcp_server_sock(&rr, err)) < 0) + goto error; + + // deinit lookup results + resolve_result_deinit(&rr); + + // make it nonblocking + if (evutil_make_socket_nonblocking(sock)) + JUMP_SET_ERROR_STR(err, ERR_MISC, "evutil_make_socket_nonblocking"); + + // construct event for the sock + if ((serv->ev = event_new(ev_base, sock, EV_READ | EV_PERSIST, tcp_server_on_accept, serv)) == NULL) + JUMP_SET_ERROR(err, ERR_EVENT_NEW); + + // add it + if (event_add(serv->ev, NULL)) + JUMP_SET_ERROR(err, ERR_EVENT_ADD); + + // ok + return SUCCESS; + +error: + // deinit results just to be sure + resolve_result_deinit(&rr); + + if (sock >= 0 && !serv->ev) + // need to close socket ourselves, because we couldn't register our event for it + EVUTIL_CLOSESOCKET(sock); + + // general cleanup + tcp_server_deinit(serv); + + return ERROR_CODE(err); +} + +void tcp_server_deinit (struct tcp_server *serv) +{ + if (serv->ev) { + // ignore errors + event_del(serv->ev); + + // ignore errors + close(event_get_fd(serv->ev)); + + // release event + event_free(serv->ev); + + // invalidate + serv->ev = NULL; + } +} + +static void tcp_server_destroy (struct tcp_server *serv) +{ + tcp_server_deinit(serv); + + free(serv); +} + +/* + * Public interface + */ +err_t tcp_listen (const struct service_info *info, service_t **service_ptr, + const char *interface, const char *service, error_t *err) +{ + struct tcp_server *serv; + + // alloc + if ((serv = calloc(1, sizeof(*serv))) == NULL) + return SET_ERROR(err, ERR_MEM); + + // init service + service_init(&serv->base_service, &tcp_server_type, info); + + // init ourselves + if (tcp_server_listen(serv, interface, service, err)) + goto error; + + // ok + *service_ptr = &serv->base_service; + + return SUCCESS; + +error: + tcp_server_destroy(serv); + + return ERROR_CODE(err); +} diff -r 5229a5d098b2 -r cefec18b8268 src/lib/tcp_transport.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/tcp_transport.c Thu May 28 01:17:36 2009 +0300 @@ -0,0 +1,86 @@ +#include "tcp_internal.h" +#include "sock_internal.h" + +#include + +/* + * Our transport_type + */ +const struct transport_type tcp_transport_type = { + .base_type = { + .parent = &transport_fd_type.base_type, + }, + .methods = { + .read = transport_fd__read, + .write = transport_fd__write, + .events = transport_fd__events, + .deinit = transport_fd__deinit, + }, +}; + +void tcp_transport_init (struct tcp_transport *trans, evutil_socket_t sock) +{ + struct event_base *ev_base = _sock_stream_ctx.ev_base; + + transport_fd_init(&trans->base_fd, ev_base, sock); +} + +err_t tcp_transport_create (struct tcp_transport **trans_ptr, const struct transport_info *info, evutil_socket_t sock, error_t *err) +{ + struct tcp_transport *trans; + + // alloc + if ((trans = calloc(1, sizeof(*trans))) == NULL) + JUMP_SET_ERROR(err, ERR_MEM); + + // init transport + transport_init(&trans->base_fd.base, &tcp_transport_type, info); + + // init ourselves + tcp_transport_init(trans, sock); + + // setup the socket? + if (sock >= 0) { + // make it non-blocking + if ((ERROR_CODE(err) = transport_fd_nonblock(&trans->base_fd, true))) + goto error; + } + + // ok + *trans_ptr = trans; + + return SUCCESS; + +error: + // cleanup + if (trans) + tcp_transport_deinit(trans); + else + EVUTIL_CLOSESOCKET(sock); + + return ERROR_CODE(err); +} + +err_t tcp_transport_connected (struct tcp_transport *trans, error_t *err) +{ + // set up for default transport event-based operation + if ((ERROR_CODE(err) = transport_fd_defaults(&trans->base_fd))) + return ERROR_CODE(err); + + // ok + transport_connected(&trans->base_fd.base, NULL, false); + + return SUCCESS; +} + +void tcp_transport_deinit (struct tcp_transport *trans) +{ + transport_fd_deinit(&trans->base_fd); +} + +void tcp_transport_destroy (struct tcp_transport *trans) +{ + tcp_transport_deinit(trans); + + free(trans); +} diff -r 5229a5d098b2 -r cefec18b8268 src/lib/transport.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/transport.c Thu May 28 01:17:36 2009 +0300 @@ -0,0 +1,160 @@ +#include "transport_internal.h" + +#include +#include + +const struct error_list transport_errors = ERROR_LIST("transport", + ERROR_TYPE( ERR_TRANSPORT_EOF, "EOF" ), + ERROR_TYPE( ERR_TRANSPORT_READABLE, "transport not readable" ), + ERROR_TYPE( ERR_TRANSPORT_WRITEABLE, "transport not writeable" ) +); + +/** + * Our own object_type + */ +const struct object_type transport_type_type = { + .parent = NULL, +}; + +/* + * Internal API + */ +void transport_init (transport_t *transport, const struct transport_type *type, const struct transport_info *info) +{ + // init object + object_init(&transport->base_obj, &type->base_type); + + // store + if (info) + transport->info = *info; +} + +void* transport_check (transport_t *transport, const struct transport_type *type) +{ + // trip as a bug + assert(object_check(&transport->base_obj, &type->base_type)); + + // ok, cast via void* + return transport; +} + +void transport_connected (transport_t *transport, const error_t *err, bool direct) +{ + const struct transport_type *type = object_type(&transport->base_obj, &transport_type_type); + + if (direct || !type->methods._connected) { + // user callback + if (err) { + // connect failed + transport->info.cb_tbl->on_error(transport, err, transport->info.cb_arg); + + } else { + // update state + transport->connected = true; + + // connect succesfull + transport->info.cb_tbl->on_connect(transport, transport->info.cb_arg); + } + + } else { + // wrapper method + type->methods._connected(transport, err); + + } +} + +void transport_invoke (transport_t *transport, short what) +{ + // on_ready + if (what & TRANSPORT_READ && transport->info.cb_tbl->on_read) + transport->info.cb_tbl->on_read(transport, transport->info.cb_arg); + + // on_write + if (what & TRANSPORT_WRITE && transport->info.cb_tbl->on_write) + transport->info.cb_tbl->on_write(transport, transport->info.cb_arg); + +} + +void transport_error (transport_t *transport, const error_t *err) +{ + // update state + transport->connected = false; + + // invoke callback + transport->info.cb_tbl->on_error(transport, err, transport->info.cb_arg); +} + +/* + * Public API + */ +int transport_read (transport_t *transport, void *buf, size_t len, error_t *err) +{ + const struct transport_type *type = object_type(&transport->base_obj, &transport_type_type); + + // not readable + if (!type->methods.read) + return SET_ERROR(err, &transport_errors, ERR_TRANSPORT_READABLE); + + // proxy off to method handler + if (type->methods.read(transport, buf, &len, err)) + return -error_code(err); + + // return updated 'bytes-read' len + return len; +} + +int transport_write (transport_t *transport, const void *buf, size_t len, error_t *err) +{ + const struct transport_type *type = object_type(&transport->base_obj, &transport_type_type); + + // not writeable + if (!type->methods.write) + return SET_ERROR(err, &transport_errors, ERR_TRANSPORT_WRITEABLE); + + // proxy off to method handler + if (type->methods.write(transport, buf, &len, err)) + return -error_code(err); + + // return updated 'bytes-written' len + return len; +} + +err_t transport_events (transport_t *transport, short mask) +{ + const struct transport_type *type = object_type(&transport->base_obj, &transport_type_type); + error_t err; + + // notify transport + if (type->methods.events) { + if (type->methods.events(transport, mask, &err)) + goto error; + } + + // update the event mask + transport->info.ev_mask = mask; + + // ok + return SUCCESS; + +error: + return ERROR_CODE(&err); +} + +void transport_set_callbacks (transport_t *transport, const struct transport_callbacks *cb_tbl, void *cb_arg) +{ + transport->info.cb_tbl = cb_tbl; + transport->info.cb_arg = cb_arg; +} + +void transport_destroy (transport_t *transport) +{ + const struct transport_type *type = object_type(&transport->base_obj, &transport_type_type); + + // destroy the transport-specific stuff + if (type->methods.deinit) + type->methods.deinit(transport); + + // then the transport itself + free(transport); +} + diff -r 5229a5d098b2 -r cefec18b8268 src/lib/transport.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/transport.h Thu May 28 01:17:36 2009 +0300 @@ -0,0 +1,179 @@ +#ifndef LIBQMSK_TRANSPORT_H +#define LIBQMSK_TRANSPORT_H + +/** + * @file + * + * Defines a intermediate-level (as opposed to high-level or low-level) API for connected streams of data, presumeably + * non-blocking ones. + */ +#include "error.h" +#include + +/** + * Opaque transport state handle. + * + * Transports are reliable byte streams, connected with some endpoint over some medium. Common implementations are + * e.g. TCP, SSL or fifo transports (using the OS file/socket API). + * + * Transports can be connected or unconnected. For synchronous opens (e.g. fifo_open_read()), the transport returned + * will already be connected, meaning that the transport_callbacks::on_connect callback is unused. For async connects + * such as sock_tcp_connect()/sock_ssl_connect(), the transport returned is *not* connected, and you must wait for + * transport_callbacks::on_connect to be called before being able to send/recieve data on the transport. + * + * Once you have an opened transport, sending and receiving data is simple - just call transport_read()/transport_write(). + * These implement unbuffered I/O, so they may do partial reads/writes. In terms of the system read/write calls, the + * main difference is in the error return codes. On EOF, instead of returning zero, they return ERR_TRANSPORT_EOF (or + * ERR_WRITE_EOF for transport_write, for whoever knows what that means...). This means that when the underlying + * transport is unable to fufill the request due to lack of data/buffer space, these can return zero to signifiy + * something simliar to EAGAIN. + * + * The transport API also implements non-blocking/event-based operation (usually on top of libevent), although at a + * slightly different level than the normal select/poll API. Instead of the user asking the transport to notify for + * read/write after transport_read/transport_write return zero, the transport will take care of this itself. + * + * Specifically, the user can supply a mask of events they are currently interested in. By default, this should be the + * full TRANSPORT_READ | TRANSPORT_WRITE, as the transport will take care of managing events by itself. If you wish to + * e.g. throttle read/write, you may set a different event mask using transport_events(), which will prevent the + * relevant callback from being triggered. + * + * For reads, the transport maintains a persistent read event, and will always call on_read when data is available on + * the socket (i.e. normal select() semantics). If masked out using transport_events(), there should be no event + * activity on the transport (i.e. the fd read event is removed). + * + * For writes, the transport maintains a write event that is disabled by default. If transport_write() returns zero, it will + * become enabled *once*, and consequently trigger transport_callbacks::on_write *once*, after which you must call + * transport_write() to possibly enable it again. If masked out using transport_events(), transport_write() will not + * enable the write event, and any pending write event is cancelled. If masked back in using transport_events(), the + * write event will *not* be registered, so if you have pending data, do a transport_write() after enabling + * TRANSPORT_WRITE. + * + * Note that transport_write() returning fewer bytes than given will *not* enable the write event! You must call + * transport_write() until you have either written all of your data, or it returns zero! + */ +struct transport; + +/** + * @see transport + */ +typedef struct transport transport_t; + +/** + * Errors + */ +enum transport_error_code { + ERR_TRANSPORT_NONE, + ERR_TRANSPORT_EOF, ///< EOF + ERR_TRANSPORT_READABLE, ///< transport not readable + ERR_TRANSPORT_WRITEABLE, ///< transport not writeable +}; + +const struct error_list transport_errors; + +/** + * User callbacks for transports + * + * @see transport + */ +struct transport_callbacks { + /** + * The transport is now connected + */ + void (*on_connect) (transport_t *transport, void *arg); + + /** + * Data is now available for reading from the transport + */ + void (*on_read) (transport_t *transport, void *arg); + + /** + * The transport has become writeable + */ + void (*on_write) (transport_t *transport, void *arg); + + /** + * An asynchronous error has occured. This is only called for errors that occur while being called directly from + * the underlying event loop, and never from inside an API function. + * + * You must call transport_destroy to release the transport. + */ + void (*on_error) (transport_t *transport, const error_t *err, void *arg); +}; + +/** + * Bitmask of available events + * + * @see transport + */ +enum transport_event { + TRANSPORT_READ = 0x01, + TRANSPORT_WRITE = 0x02, +}; + +/** + * User info required to build a transport + * + * @see transport + */ +struct transport_info { + /** The callbacks table */ + const struct transport_callbacks *cb_tbl; + + /** The callback context argument */ + void *cb_arg; + + /** Initial event mask using transport_event flags */ + short ev_mask; +}; + +/** + * Read a series of bytes from the transport into the given \a buf (up to \a len bytes). If succesfull, this returns + * the number of bytes read (which will be less than or equal to \a len). If the transport is nonblocking, and there is + * no data available, this returns zero, and need not be called again until transport_callbacks::on_read is invoked. + * + * On errors, this returns the negative error code, and more info via \a err. Note that as opposed to read(2), EOF is + * handled as an error, returning ERR_EOF. + * + * @param transport the transport state + * @param buf the buffer to read the bytes into + * @param len the number of bytes to read into the buffer + * @param err returned error info + * @return bytes read, zero if none available, -err_t + */ +int transport_read (transport_t *transport, void *buf, size_t len, error_t *err); + +/** + * Write a series of bytes from the given \a buf (containing \a len bytes) to the transport. If succesfull, this + * returns the number of bytes written (which may be less than \a len). If the transport is nonblocking, and the + * operation would have blocked, no data will be written, and zero is returned; in this case, the transport's write + * event is enabled (unless TRANSPORT_WRITE is masked out). + * + * On errors, this returns the negative error code, along with extended info via \a err. + * + * @param transport the transport state + * @param buf the buffer to write the bytes from + * @param len number of bytes to write + * @param err returned error info + * @return bytes written, zero if would have blocked, -err_t + */ +int transport_write (transport_t *transport, const void *buf, size_t len, error_t *err); + +/** + * Change the mask of enabled events. + */ +err_t transport_events (transport_t *transport, short mask); + +/** + * Install a new set of callback handlers, replacing the old ones. + */ +void transport_set_callbacks (transport_t *transport, const struct transport_callbacks *cb_tbl, void *cb_arg); + +/** + * Close and destroy the transport immediately, severing any established connection rudely. + * + * This will release all resources associated with the transport, including the transport itself, which must not be + * used anymore. + */ +void transport_destroy (transport_t *transport); + +#endif diff -r 5229a5d098b2 -r cefec18b8268 src/lib/transport_fd.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/transport_fd.c Thu May 28 01:17:36 2009 +0300 @@ -0,0 +1,368 @@ +#include "transport_fd.h" + +#include "log.h" + +#include +#include +#include + +/** + * Our libevent callback + */ +static void transport_fd_on_event (evutil_socket_t _fd, short ev_what, void *arg) +{ + struct transport_fd *fd = arg; + + (void) _fd; + + short what = 0; + + // build flags + if (ev_what & EV_READ) + what |= TRANSPORT_READ; + + if (ev_what & EV_WRITE) + what |= TRANSPORT_WRITE; + + // invoke user callback + fd->cb_func(fd, what, fd->cb_arg); +} + +/** + * Our transport_methods implementations + */ +err_t transport_fd__read (transport_t *transport, void *buf, size_t *len, error_t *err) +{ + struct transport_fd *fd = transport_check(transport, &transport_fd_type); + int ret; + + error_reset(err); + + // read(), and detect non-EAGAIN or EOF + if ((ret = read(fd->fd, buf, *len)) < 0 && errno != EAGAIN) + // unexpected error + return SET_ERROR_ERRNO(err, &libc_errors, ERR_READ); + + else if (ret == 0) + // EOF + return SET_ERROR(err, &transport_errors, ERR_TRANSPORT_EOF); + + + if (ret < 0) { + // EAGAIN -> zero bytes + *len = 0; + + } else { + // normal -> bytes read + *len = ret; + } + + // ok + return SUCCESS; +} + +err_t transport_fd__write (transport_t *transport, const void *buf, size_t *len, error_t *err) +{ + struct transport_fd *fd = transport_check(transport, &transport_fd_type); + int ret; + + error_reset(err); + + // write(), and detect non-EAGAIN or EOF + if ((ret = write(fd->fd, buf, *len)) < 0 && errno != EAGAIN) + // unexpected error + return SET_ERROR_ERRNO(err, &libc_errors, ERR_WRITE); + + else if (ret == 0) + // EOF + return SET_ERROR(err, &libc_errors, ERR_WRITE_EOF); + + + if (ret < 0) { + // EAGAIN -> zero bytes + *len = 0; + + if (transport->info.ev_mask & TRANSPORT_WRITE) + // enable the write event + if ((ERROR_CODE(err) = transport_fd_enable(fd, TRANSPORT_WRITE))) + return ERROR_CODE(err); + + } else { + // normal -> bytes read + *len = ret; + } + + return SUCCESS; +} + +err_t transport_fd__events (transport_t *transport, short ev_mask, error_t *err) +{ + struct transport_fd *fd = transport_check(transport, &transport_fd_type); + + short mask = 0; + + // enable read as requested + if (ev_mask & TRANSPORT_READ) + mask |= TRANSPORT_READ; + + // enable write if requested and it's currently enabled + if ((ev_mask & TRANSPORT_WRITE) && event_pending(fd->ev_write, EV_WRITE, NULL)) + mask |= TRANSPORT_WRITE; + + // set + return (ERROR_CODE(err) = transport_fd_events(fd, mask)); +} + +void transport_fd__deinit (transport_t *transport) +{ + struct transport_fd *fd = transport_check(transport, &transport_fd_type); + + transport_fd_deinit(fd); +} + +const struct transport_type transport_fd_type = { + .base_type = { + .parent = &transport_type_type, + }, + .methods = { + .read = transport_fd__read, + .write = transport_fd__write, + .events = transport_fd__events, + .deinit = transport_fd__deinit + } +}; + +/** + * Dummy callbacks + */ +void transport_fd_callback_user (struct transport_fd *fd, short what, void *arg) +{ + (void) arg; + + // proxy + transport_invoke(TRANSPORT_FD_BASE(fd), what); +} + +/** + * Function implementations + */ +void transport_fd_init (struct transport_fd *fd, struct event_base *ev_base, int _fd) +{ + // sanity-check + assert(!fd->fd); + assert(!fd->ev_read && !fd->ev_write); + assert(_fd == TRANSPORT_FD_INVALID || _fd >= 0); + + // initialize + fd->ev_base = ev_base; + fd->fd = _fd; + fd->cb_func = fd->cb_arg = NULL; +} + +err_t transport_fd_nonblock (struct transport_fd *fd, bool nonblock) +{ + assert(fd->fd != TRANSPORT_FD_INVALID); + + // XXX: maintain old flags? + + + // set new flags + if (fcntl(fd->fd, F_SETFL, nonblock ? O_NONBLOCK : 0) < 0) + return ERR_FCNTL; + + return SUCCESS; +} + +/** + * Install our internal event handler. + * + * The events should not already be set up. + * + * Cleans up partial events on errors + */ +err_t transport_fd_install (struct transport_fd *fd) +{ + assert(fd->fd != TRANSPORT_FD_INVALID); + assert(!fd->ev_read && !fd->ev_write); + + // create new events + if ((fd->ev_read = event_new(fd->ev_base, fd->fd, EV_READ | EV_PERSIST, transport_fd_on_event, fd)) == NULL) + goto err_event_add; + + if ((fd->ev_write = event_new(fd->ev_base, fd->fd, EV_WRITE, transport_fd_on_event, fd)) == NULL) + goto err_event_add; + + // ok + return SUCCESS; + +err_event_add: + // remove partial events + transport_fd_clear(fd); + + return ERR_EVENT_NEW; +} + +err_t transport_fd_setup (struct transport_fd *fd, transport_fd_callback_func cb_func, void *cb_arg) +{ + // requires a valid fd + assert(fd->fd != TRANSPORT_FD_INVALID); + + // store + fd->cb_func = cb_func; + fd->cb_arg = cb_arg; + + // install the event handlers? + if (!fd->ev_read || !fd->ev_write) + return transport_fd_install(fd); + else + return SUCCESS; +} + +err_t transport_fd_enable (struct transport_fd *fd, short mask) +{ + // just add the appropriate events + if (mask & TRANSPORT_READ && event_add(fd->ev_read, NULL)) + return ERR_EVENT_ADD; + + if (mask & TRANSPORT_WRITE && event_add(fd->ev_write, NULL)) + return ERR_EVENT_ADD; + + + return SUCCESS; +} + +err_t transport_fd_disable (struct transport_fd *fd, short mask) +{ + if (mask & TRANSPORT_READ && event_del(fd->ev_read)) + return ERR_EVENT_DEL; + + if (mask & TRANSPORT_WRITE && event_del(fd->ev_write)) + return ERR_EVENT_DEL; + + + return SUCCESS; +} + +err_t transport_fd_events (struct transport_fd *fd, short mask) +{ + err_t err; + + // enable/disable read + if (mask & TRANSPORT_READ) + err = event_add(fd->ev_read, NULL); + else + err = event_del(fd->ev_read); + + if (err) + return err; + + // enable/disable write + if (mask & TRANSPORT_WRITE) + err = event_add(fd->ev_write, NULL); + else + err = event_del(fd->ev_write); + + if (err) + return err; + + // ok + return SUCCESS; +} + +/** + * Remove our current ev_* events, but leave the cb_* intact. + */ +static void transport_fd_remove (struct transport_fd *fd) +{ + if (fd->ev_read) + event_free(fd->ev_read); + + if (fd->ev_write) + event_free(fd->ev_write); + + fd->ev_read = NULL; + fd->ev_write = NULL; +} + +void transport_fd_clear (struct transport_fd *fd) +{ + // remove the events + transport_fd_remove(fd); + + // clear the callbacks + fd->cb_func = fd->cb_arg = NULL; +} + +err_t transport_fd_defaults (struct transport_fd *fd) +{ + error_t err; + + // install the transport_invoke callback handler + if ((ERROR_CODE(&err) = transport_fd_setup(fd, transport_fd_callback_user, NULL))) + goto error; + + // enable read unless masked out + if (TRANSPORT_FD_BASE(fd)->info.ev_mask & TRANSPORT_READ) { + if ((ERROR_CODE(&err) = transport_fd_enable(fd, TRANSPORT_READ))) + goto error; + } + + // ok + return SUCCESS; + +error: + return ERROR_CODE(&err); +} + +err_t transport_fd_set (struct transport_fd *fd, int _fd, error_t *err) +{ + assert(_fd == TRANSPORT_FD_INVALID || _fd >= 0); + + // close the old stuff + if (transport_fd_close(fd, err)) + log_warn_error(err, "close"); + + // set the new one + fd->fd = _fd; + + // do we have callbacks that we need to setup? + if (fd->cb_func) + return transport_fd_install(fd); + + else + return SUCCESS; +} + +void transport_fd_invoke (struct transport_fd *fd, short what) +{ + // invoke + transport_invoke(TRANSPORT_FD_BASE(fd), what); +} + +err_t transport_fd_close (struct transport_fd *fd, error_t *err) +{ + int _fd = fd->fd; + + // remove any installed events + transport_fd_remove(fd); + + // invalidate fd + fd->fd = TRANSPORT_FD_INVALID; + + // close the fd + if (_fd != TRANSPORT_FD_INVALID && close(_fd)) + return SET_ERROR_ERRNO(err, &libc_errors, ERR_CLOSE); + + return SUCCESS; +} + +void transport_fd_deinit (struct transport_fd *fd) +{ + error_t err; + + // XXX: this might block + if (transport_fd_close(fd, &err)) + log_warn_error(&err, "close"); + +} + diff -r 5229a5d098b2 -r cefec18b8268 src/lib/transport_fd.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/transport_fd.h Thu May 28 01:17:36 2009 +0300 @@ -0,0 +1,173 @@ +#ifndef LIBQMSK_TRANSPORT_FD_H +#define LIBQMSK_TRANSPORT_FD_H + +/** + * @file + * + * Support for transport implementations that use POSIX file descriptor streams. + * + * This provides the read/write methods, as well as functions to implement the event-based behaviour. + */ +#include "transport_internal.h" + +#include +#include + +// forward-declare +struct transport_fd; + +/** + * Our transport_type + */ +extern const struct transport_type transport_fd_type; + +/** + * Low-level callback + */ +typedef void (*transport_fd_callback_func) (struct transport_fd *fd, short what, void *arg); + +/** + * The fd-based transport implementation + */ +struct transport_fd { + /** Base transport state */ + struct transport base; + + /** Libevent base to use */ + struct event_base *ev_base; + + /** OS file descriptor */ + evutil_socket_t fd; + + /** IO events */ + struct event *ev_read, *ev_write; + + /** Low-level callback */ + transport_fd_callback_func cb_func; + + /** Callback context argument */ + void *cb_arg; + +}; + +/** + * Get a transport_t pointer from a transport_fd + */ +#define TRANSPORT_FD_BASE(tp_ptr) (&(tp_ptr)->base) + +/** + * Invalid OS FD + */ +#define TRANSPORT_FD_INVALID ((evutil_socket_t) -1) + +/** + * Implementation of transport_methods::read + */ +err_t transport_fd__read (transport_t *transport, void *buf, size_t *len, error_t *err); + +/** + * Implementation of transport_methods::write. + * + * If this gets EAGAIN, it will automatically enable the write event, unless masked out. + */ +err_t transport_fd__write (transport_t *transport, const void *buf, size_t *len, error_t *err); + +/** + * Implementation of transport_methods::events. + * + * For TRANSPORT_READ, this will simply apply enable/disable as given. + * + * For TRANSPORT_WRITE, the write event will only be enabled if given in the mask, *and* the ev_write event is currently + * active (via transport_fd_methods_write()); otherwise, the write event will not be enabled. + */ +err_t transport_fd__events (transport_t *transport, short mask, error_t *err); + +/** + * Implementation of transport_methods::deinit. + * + * This simply calls transport_fd_deinit(). + */ +void transport_fd__deinit (transport_t *transport); + +/** + * A transport_fd_callback_func that simply invokes the transport_callback user functions. + * + * Register with a NULL cb_arg. + */ +void transport_fd_callback_user (struct transport_fd *fd, short what, void *arg); + + + + +/** + * Initialize the transport_fd to use the given, connected fd, or TRANSPORT_FD_INVALID if we don't yet have an fd. + * + * It is an error to call this if the transport_fd already has an fd set + * + * @param fd the transport_fd state + * @param ev_base the libevent base to use + * @param _fd the OS file descriptor, or TRANSPORT_FD_INVALID + */ +void transport_fd_init (struct transport_fd *fd, struct event_base *ev_base, int _fd); + +/** + * Set the fd's nonblocking mode using fcntl. + */ +err_t transport_fd_nonblock (struct transport_fd *fd, bool nonblock); + +/** + * Set or replace the fd's event callback. The callback will not be enabled. + * + * The transport must have a valid fd bound to it. + */ +err_t transport_fd_setup (struct transport_fd *fd, transport_fd_callback_func cb_func, void *cb_arg); + +/** + * Enable the specified events, any of { TRANSPORT_READ, TRANSPORT_WRITE }. + */ +err_t transport_fd_enable (struct transport_fd *fd, short mask); + +/** + * Disable the specified events, any of { TRANSPORT_READ, TRANSPORT_WRITE }. + */ +err_t transport_fd_disable (struct transport_fd *fd, short mask); + +/** + * Set the enable/disable state of our events to the given mask. + */ +err_t transport_fd_events (struct transport_fd *fd, short mask); + +/** + * Remove any old event callback present, so it will not be called anymore. + * + * It is perfectly safe to call this without any callbacks installed. + */ +void transport_fd_clear (struct transport_fd *fd); + +/** + * Setup and enable the default event handlers for transport operation, that is, use transport_fd_callback_user as the + * callback and enable TRANSPORT_READ, unless masked out. + */ +err_t transport_fd_defaults (struct transport_fd *fd); + +/** + * Replace the old fd with a new one, maintaining any event callbacks set with transport_fd_callback. If any events were + * enabled before, they are not enabled anymore. + */ +err_t transport_fd_set (struct transport_fd *fd, int _fd, error_t *err); + +/** + * Close an opened fd, releasing all resources within our state. + */ +err_t transport_fd_close (struct transport_fd *fd, error_t *err); + +/** + * Deinitialize the transport_fd. + * + * This logs a warning if the close() fails. + * + * XXX: this may actually block, I think? SO_LINGER? + */ +void transport_fd_deinit (struct transport_fd *fd); + +#endif diff -r 5229a5d098b2 -r cefec18b8268 src/lib/transport_internal.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/transport_internal.h Thu May 28 01:17:36 2009 +0300 @@ -0,0 +1,126 @@ +#ifndef LIBQMSK_TRANSPORT_INTERNAL_H +#define LIBQMSK_TRANSPORT_INTERNAL_H + +/** + * @file + * + * The internal interface for transport implementations. + */ +#include "transport.h" +#include "object.h" + +#include + +/** + * The object_type for a transport_type + */ +extern const struct object_type transport_type_type; + +/** + * The definition of a transport type + */ +struct transport_type { + struct object_type base_type; + + /** + * Method table for implementation stuff. + * + * Note that it is the transport's resposibility to implement the behaviour described in transport.h + */ + struct transport_methods { + /** For transport_read() */ + err_t (*read) (transport_t *transport, void *buf, size_t *len, error_t *err); + + /** For transport_write() */ + err_t (*write) (transport_t *transport, const void *buf, size_t *len, error_t *err); + + /** + * The mask of event flags will be set to the given mask if this method is succesfull. + * + * The old mask is still available in transport::info::ev_mask. + */ + err_t (*events) (transport_t *transport, short mask, error_t *err); + + /** + * Release the transport's internal state, but not the transport itself. + * + * In other words, this should release everything inside the transport_t, but not free() the transport_t itself. + */ + void (*deinit) (transport_t *transport); + + /** + * Used by layered transports to handle transport_connected. + * + * If this is NULL, transport_connected will call the user callback directly, otherwise, it will proxy through this. + * + * The \a err param follows the same rules as for transport_connected() - NULL for success, error info otherwise. + * + * @param transport the transport state + * @param err error info if the connect failed + */ + void (*_connected) (transport_t *transport, const error_t *err); + } methods; +}; + +/** + * The base transport type + */ +struct transport { + struct object base_obj; + + /** User info */ + struct transport_info info; + + /** Are we connected? */ + bool connected; +}; + +/** + * Bind the given transport to the given type with the given user info. + * + * \a info may be given as NULL to not have any callbacks, but this will crash if any transport_* is called before + * transport_set_callbacks(). + * + * It is a bug to call this with a transport that is already bound. + */ +void transport_init (transport_t *transport, const struct transport_type *type, const struct transport_info *info); + +/** + * Check the type of the transport, and return the transport as a void* suitable for casting to the appropriate struct + * for the type, or any of its children. + * + * It is a bug to call this with a transport of a different type. + */ +void* transport_check (transport_t *transport, const struct transport_type *type); + +/** + * Mark the transport as connected, calling transport_methods::_connected if it exists and \a direct is not given, + * transport_callbacks::on_connected/transport_callbacks::on_error otherwise. + * + * If the connect succeeded, \a err should be given as NULL. If the connect failed, \a err should contain the error + * info. + * + * If called from the transport_methods::_connected method, pass in direct to avoid recursion. + * + * This sets the transport::connected flag before calling transport_callbacks::on_connected (i.e. directly) without any + * error set. + * + * XXX: implement proper layering of types by taking a transport_type arg and chaining down from there. + * + * @param transport the transport state + * @param err NULL for success, otherwise connect error code + * @param direct call the user callback directly, ignoring any method + */ +void transport_connected (transport_t *transport, const error_t *err, bool direct); + +/** + * Invoke the user callbacks based on the given TRANSPORT_* flags + */ +void transport_invoke (transport_t *transport, short what); + +/** + * Mark the transport as failed, calling transport_methods::on_error with the given error code. + */ +void transport_error (transport_t *transport, const error_t *err); + +#endif diff -r 5229a5d098b2 -r cefec18b8268 src/lib/transport_test.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/transport_test.c Thu May 28 01:17:36 2009 +0300 @@ -0,0 +1,366 @@ +#include "transport_test.h" +#include "transport_internal.h" + +#include +#include +#include +#include +#include + +/** + * Simple IO vector + */ +struct io_vec { + /** The buffer */ + char *buf; + + /** Buffer size */ + size_t len; +}; + +/** + * Simple vectored IO-buffer + */ +struct io_buf { + /** The array of buffer-vectors, {NULL}-terminated */ + struct io_vec *vecs; + + /** The number of io_vecs */ + size_t count; + + /** Current read/write vector */ + struct io_vec *read_vec, *write_vec; + + /** Offset into current vector */ + size_t off; +}; + +/** + * Forward-declare our transport_type + */ +extern const struct transport_type transport_test_type; + + +/** + * A dummy sock_stream implementation intended for testing purposes. + */ +struct transport_test { + /** The base transport stuff */ + struct transport base; + + /** The send/recieve buffers */ + struct io_buf send_buf, recv_buf; + + /** No more data is going to be added, return EOF once all the rest is consumed */ + bool eof; +}; + +/** + * Get a transport pointer from a transport_test pointer + */ +#define TRANSPORT_TEST_BASE(tp_ptr) (&(tp_ptr)->base) + +/** + * Grow buf->vecs if needed to ensure that buf->write_vec points to a valid io_vec + */ +static err_t io_buf_grow (struct io_buf *buf) +{ + size_t read_vec_offset = buf->read_vec ? (buf->read_vec - buf->vecs) : 0; + size_t write_vec_offset = buf->write_vec ? (buf->write_vec - buf->vecs) : 0; + struct io_vec *v; + struct io_vec *vecs_tmp = buf->vecs; + + // don't grow if not full + if (buf->vecs && buf->write_vec < buf->vecs + buf->count) + return SUCCESS; + + // new size + buf->count = buf->count * 2 + 1; + + // grow + if ((buf->vecs = realloc(buf->vecs, buf->count * sizeof(struct io_vec))) == NULL) { + // restore old value + buf->vecs = vecs_tmp; + + return ERR_CALLOC; + } + + // restore vec positions + buf->write_vec = buf->vecs + write_vec_offset; + buf->read_vec = buf->vecs + read_vec_offset; + + // zero new vecs + for (v = buf->write_vec; v < buf->vecs + buf->count; v++) + memset(v, 0, sizeof(*v)); + + // ok + return SUCCESS; +} + +/** + * Write some data to an io_buf, copying it. + */ +static err_t io_buf_write (struct io_buf *buf, const char *data, size_t len) +{ + error_t err; + + // ensure there's room + if ((ERROR_CODE(&err) = io_buf_grow(buf))) + goto error; + + // the vector to use + struct io_vec *vec = buf->write_vec; + + // allocate + if ((vec->buf = malloc(len)) == NULL) + JUMP_SET_ERROR(&err, ERR_MEM); + + // store + vec->len = len; + memcpy(vec->buf, data, len); + + // vec consumed + buf->write_vec++; + + // ok + return SUCCESS; + +error: + return ERROR_CODE(&err); +} + +/** + * Destroy the io_buf, freeing all resources. + * + * The io_buf must not be used anymore. + */ +static void io_buf_destroy (struct io_buf *buf) +{ + size_t i; + + // free the io_vec buffers + for (i = 0; i < buf->count; i++) { + free(buf->vecs[i].buf); + } + + // free the vector list + free(buf->vecs); +} + +/** + * transport_methods::read implementation. + */ +static err_t transport_test__read (transport_t *transport, void *buf_ptr, size_t *len, error_t *err) +{ + struct transport_test *tp = transport_check(transport, &transport_test_type); + struct io_buf *buf = &tp->recv_buf; + struct io_vec *vec = buf->read_vec; + + // EOF/nonblock if we're past the end of the last vector + if (!vec || vec == buf->vecs + buf->count || buf->off >= vec->len) { + if (!tp->eof) { + // wait for more to be fed in + *len = 0; + return SUCCESS; + + } else { + // EOF! + return SET_ERROR(err, ERR_EOF); + } + } + + // amount of data available in this iovec + size_t available = vec->len - buf->off; + + // amount to read + size_t to_read = *len; + + // trim down? + if (to_read > available) + to_read = available; + + // copy + memcpy(buf_ptr, vec->buf + buf->off, to_read); + + + if (to_read < available) { + // bytes still left in the vector + buf->off += to_read; + + } else { + // consumed the whole vector + // XXX: release data? + buf->read_vec++; + buf->off = 0; + } + + // update len + *len = to_read; + + // ok + return SUCCESS; +} + +/** + * transport_methods::write implementation. + */ +static err_t transport_test__write (transport_t *transport, const void *data, size_t *len, error_t *err) +{ + struct transport_test *tp = transport_check(transport, &transport_test_type); + + // write it out + // XXX: partial writes? + if ((ERROR_CODE(err) = io_buf_write(&tp->send_buf, data, *len))) + goto error; + + // ok + return SUCCESS; + +error: + return ERROR_CODE(err); +} + +static err_t transport_test__events (transport_t *transport, short mask, error_t *err) +{ + struct transport_test *tp = transport_check(transport, &transport_test_type); + + (void) tp; + (void) mask; + (void) err; + + // XXX: don't re-trigger anything + + return SUCCESS; +} + +static void transport_test__deinit (transport_t *transport) +{ + struct transport_test *tp = transport_check(transport, &transport_test_type); + + transport_test_destroy(tp); +} + +/* + * Our sock_stream_type + */ +const struct transport_type transport_test_type = { + .base_type = { + .parent = &transport_type_type, + }, + .methods = { + .read = transport_test__read, + .write = transport_test__write, + .events = transport_test__events, + .deinit = transport_test__deinit + }, +}; + +struct transport_test* transport_test_create (struct transport_info *info) +{ + struct transport_test *tp; + + // allocate + assert((tp = calloc(1, sizeof(*tp)))); + + // initialize base with our transport_type + transport_init(TRANSPORT_TEST_BASE(tp), &transport_test_type, info); + + // ok + return tp; +} + +transport_t* transport_test_cast (struct transport_test *tp) +{ + return TRANSPORT_TEST_BASE(tp); +} + +void transport_test_event (struct transport_test *tp, short what) +{ + // invoke, masking out as needed + // this won't do anything if all the bits are masked out + transport_invoke(TRANSPORT_TEST_BASE(tp), what & TRANSPORT_TEST_BASE(tp)->info.ev_mask); +} + +void transport_test_push_buf (struct transport_test *tp, const char *data, size_t len) +{ + // push it + assert(io_buf_write(&tp->recv_buf, data, len) == SUCCESS); + + // notify + transport_test_event(tp, TRANSPORT_READ); +} + +void transport_test_push_str (struct transport_test *tp, const char *str) +{ + // push it + transport_test_push_buf(tp, str, strlen(str)); +} + +void transport_test_push_fmt (struct transport_test *tp, const char *fmt, ...) +{ + char buf[TRANSPORT_TEST_FMT_MAX]; + size_t ret; + + // format + va_list vargs; va_start(vargs, fmt); + assert((ret = vsnprintf(buf, sizeof(buf), fmt, vargs)) <= sizeof(buf)); + va_end(vargs); + + // push it + transport_test_push_buf(tp, buf, ret); +} + +void transport_test_push_eof (struct transport_test *tp) +{ + // update state + tp->eof = true; + + transport_test_event(tp, TRANSPORT_READ); +} + +void transport_test_pull_buf (struct transport_test *tp, char **buf_ptr, size_t *len_ptr) +{ + struct io_buf *buf = &tp->send_buf; + size_t len = 0, i, off = 0; + char *out; + + // calculate total size + for (i = 0; i < buf->count; i++) { + len += buf->vecs[i].len; + } + + // alloc + assert((out = malloc(len))); + + // copy + for (i = 0; i < buf->count; i++) { + struct io_vec *vec = buf->vecs + i; + + memcpy(out + off, vec->buf, vec->len); + off += vec->len; + + // zero + free(vec->buf); vec->buf = NULL; + vec->len = 0; + } + + // update return + *buf_ptr = out; + *len_ptr = len; + + // update write_vec + buf->write_vec = buf->vecs; +} + +void transport_test_async_error (struct transport_test *tp, const error_t *err) +{ + transport_error(&tp->base, err); +} + +void transport_test_destroy (struct transport_test *tp) +{ + // free the buffers + io_buf_destroy(&tp->send_buf); + io_buf_destroy(&tp->recv_buf); +} + diff -r 5229a5d098b2 -r cefec18b8268 src/lib/transport_test.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/transport_test.h Thu May 28 01:17:36 2009 +0300 @@ -0,0 +1,79 @@ +#ifndef LIBQMSK_TRANSPORT_TEST_H +#define LIBQMSK_TRANSPORT_TEST_H + +/** + * @file + * + * Dummy transport implemention for local testing. + */ +#include "transport.h" + +/** + * The opaque transport state + */ +struct transport_test; + +/** + * Construct a new, empty, connected transport_test. + */ +struct transport_test* transport_test_create (struct transport_info *info); + +/** + * A transport_test is a valid transport, this performs the cast + */ +transport_t* transport_test_cast (struct transport_test *tp); + +/** + * Invoke the transport's user callbacks for the given event mask, unless masked out. + */ +void transport_test_event (struct transport_test *tp, short what); + +/** + * Adds a data buffer to the recieve buffer. + * + * The given data is copied. + * + * If events are enabled, they are triggered. + */ +void transport_test_push_buf (struct transport_test *tp, const char *buf, size_t len); + +/** + * Add a string to the recieve buffer using transport_test_push_buf() + */ +void transport_test_push_str (struct transport_test *tp, const char *str); + +/** + * Maximum length of a formatted string pushed + */ +#define TRANSPORT_TEST_FMT_MAX 4096 + +/** + * Add a formatted string to the recieve buffer + * + * @see TRANSPORT_TEST_FMT_MAX + */ +void transport_test_push_fmt (struct transport_test *tp, const char *fmt, ...); + +/** + * Set EOF on recv. + */ +void transport_test_push_eof (struct transport_test *tp); + +/** + * Get the send buffer contents as a single buffer, free() after use. + * + * This clears the send buffer, so this doesn't return the same data twice. + */ +void transport_test_pull_buf (struct transport_test *tp, char **buf_ptr, size_t *len_ptr); + +/** + * Send async error + */ +void transport_test_async_error (struct transport_test *tp, const error_t *err); + +/** + * Destroy the transport buffer, releasing any buffers we allocated ourself + */ +void transport_test_destroy (struct transport_test *tp); + +#endif /* TRANSPORT_TEST_H */ diff -r 5229a5d098b2 -r cefec18b8268 src/line_proto.c --- a/src/line_proto.c Thu May 28 00:35:02 2009 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,336 +0,0 @@ - -#include "line_proto.h" -#include "log.h" - -#include -#include -#include - -/* - * Our state - */ -struct line_proto { - /* The transport we read/write with */ - transport_t *transport; - - /* The incoming/outgoing line buffer */ - char *in_buf, *out_buf; - - /* Buffer size (same for both) */ - size_t buf_len; - - /* Offset of trailing data in buf */ - size_t tail_offset; - - /* Length of trailing data in buf, if any */ - size_t tail_len; - - /* Amount of data in the out buffer */ - size_t out_offset; - - /* Last error */ - struct error_info err; - - /* Callback info */ - struct line_proto_callbacks callbacks; - void *cb_arg; -}; - -/** - * An error occured which we could not recover from; the line_proto should now be considered corrupt. - * - * Notify the user callback, which will probably call line_proto_release(). - */ -static void line_proto_set_error (struct line_proto *lp) -{ - // copy error_info, as it might get free'd - struct error_info err = lp->err; - - // trigger callback - lp->callbacks.on_error(&err, lp->cb_arg); -} - -/** - * Our transport_callbacks::on_read handler - */ -static void line_proto_on_read (transport_t *transport, void *arg) -{ - struct line_proto *lp = arg; - char *line; - - (void) transport; - - // sanity-check - assert(lp->tail_offset < lp->buf_len); - - do { - // attempt to read a line - if (line_proto_recv(lp, &line)) - // faaail - return line_proto_set_error(lp); - - // got a line? - if (line) - lp->callbacks.on_line(line, lp->cb_arg); - - } while (line); -} - -/* - * Signal for write - */ -static void line_proto_on_write (transport_t *transport, void *arg) -{ - struct line_proto *lp = arg; - int ret; - - (void) transport; - - // just flush - if ((ret = line_proto_flush(lp)) < 0) - // faaail - return line_proto_set_error(lp); -} - -// XXX: implement on_error! -static const struct transport_callbacks line_proto_transport_callbacks = { - .on_read = &line_proto_on_read, - .on_write = &line_proto_on_write, -}; - -err_t line_proto_create (struct line_proto **lp_ptr, transport_t *transport, size_t buf_size, - const struct line_proto_callbacks *callbacks, void *cb_arg, error_t *err) -{ - struct line_proto *lp; - - // alloc - if ((lp = calloc(1, sizeof(*lp))) == NULL) - return SET_ERROR(err, ERR_CALLOC); - - // store - lp->transport = transport; - lp->buf_len = buf_size; - lp->callbacks = *callbacks; - lp->cb_arg = cb_arg; - - // allocate buffers - if ( - (lp->in_buf = malloc(buf_size)) == NULL - || (lp->out_buf = malloc(buf_size)) == NULL - ) - JUMP_SET_ERROR(err, ERR_CALLOC); - - // setup the transport - transport_set_callbacks(transport, &line_proto_transport_callbacks, lp); - - if ((ERROR_CODE(err) = transport_events(transport, TRANSPORT_READ | TRANSPORT_WRITE))) - goto error; - - // return - *lp_ptr = lp; - - return SUCCESS; - -error: - // cleanup the lp - line_proto_destroy(lp); - - return ERROR_CODE(err); -} - -/* - * This looks for a full '\r\n' terminated line at the beginning of the given buffer. If found, the \r\n will be - * replaced with a '\0', and the offset to the beginning of the next line returned. If not found, zero is returned - * (which is never a valid next-line offset). - * - * The given \a hint is an hint as to the offset at which to start scanning, used for incremental invocations of this - * on the same buffer. - * - */ -int _parse_line (char *buf, size_t len, size_t *hint) { - size_t i, next = 0; - - // empty buffer -> nothing - if (len == 0) - return 0; - - // look for terminating '\r\n' or '\n' sequence - for (i = *hint; i < len; i++) { - // match this + next char? - if (i < len - 1 && buf[i] == '\r' && buf[i + 1] == '\n') { - next = i + 2; - break; - - } else if (buf[i] == '\n') { - next = i + 1; - break; - } - } - - // searched the whole buffer? - if (i >= len) { - // do continue one char back, to keep any \r - *hint = len - 1; - return 0; - } - - // mangle the newline off - buf[i] = '\0'; - - // return offset to next line, as set in loop based on delim - return next; -} - -err_t line_proto_recv (struct line_proto *lp, char **line_ptr) -{ - // offset to recv() new data into, offset to _parse_line hint, offset to next line from _parse_line - size_t recv_offset = 0, peek_offset = 0, next_offset = 0; - int ret; - - // adjust offset to beyond previous data (as will be moved next) - recv_offset = lp->tail_len; - - // move trailing data from previous line to front of buffer - if (lp->tail_offset) { - // move to front, no-op if tail_len is zero - memmove(lp->in_buf, lp->in_buf + lp->tail_offset, lp->tail_len); - - // reset - lp->tail_offset = 0; - lp->tail_len = 0; - } - - // readline loop - do { - // parse any line at the beginning of the buffer - if ((next_offset = _parse_line(lp->in_buf, recv_offset, &peek_offset)) > 0) { - // store a valid *line_ptr - *line_ptr = lp->in_buf; - - // exit loop and return - break; - } - - // ensure there's enough space for the rest of the line - if (recv_offset >= lp->buf_len) - return ERR_LINE_TOO_LONG; - - // otherwise, read more data - if ((ret = transport_read(lp->transport, lp->in_buf + recv_offset, lp->buf_len - recv_offset, &lp->err)) < 0) - return ERROR_CODE(&lp->err); - - // EAGAIN? - if (ret == 0) { - // return a NULL *line_ptr - *line_ptr = NULL; - break; - } - - // update recv_offset - recv_offset += ret; - - } while (1); - - // update state for next call - lp->tail_offset = next_offset; - lp->tail_len = recv_offset - next_offset; - - // ok - return SUCCESS; -} - -int line_proto_send (struct line_proto *lp, const char *line) -{ - int ret; - size_t len = strlen(line), ret_len; - - // drop line if we already have output buffered - if (lp->out_offset) - return -ERR_LINE_TOO_LONG; - - // try and write the line - if ((ret = transport_write(lp->transport, line, len, &lp->err)) < 0) - return -ERROR_CODE(&lp->err); - - // length of the sent data - ret_len = ret; - - // EAGAIN or partial? - if (ret_len < len) { - size_t trailing = len - ret_len; - - // ensure it's not waaaay too long - if (trailing > lp->buf_len) - return -ERR_LINE_TOO_LONG; - - // copy remaining portion to buffer - memcpy(lp->out_buf, line + ret_len, trailing); - - // update offset - lp->out_offset = trailing; - - // buffered... transport should invoke on_write itself - return 1; - - } else { - // ok, no buffering needed - return SUCCESS; - - } -} - -int line_proto_flush (struct line_proto *lp) -{ - int ret; - size_t ret_len; - - assert(lp->out_offset); - - // try and write the line - if ((ret = transport_write(lp->transport, lp->out_buf, lp->out_offset, &lp->err)) < 0) - return -ERROR_CODE(&lp->err); - - ret_len = ret; - - // empty now? - if (ret_len == lp->out_offset) { - lp->out_offset = 0; - - return SUCCESS; - } - - // partial? - if (ret_len > 0) { - size_t remaining = lp->out_offset - ret_len; - - // move the rest up - memmove(lp->out_buf, lp->out_buf + ret_len, remaining); - - // update offset - lp->out_offset = remaining; - } - - // ok - return 1; -} - -const struct error_info* line_proto_error (struct line_proto *lp) -{ - // return pointer - return &lp->err; -} - -void line_proto_destroy (struct line_proto *lp) -{ - // free buffers - free(lp->in_buf); - free(lp->out_buf); - - // socket? - if (lp->transport) - transport_destroy(lp->transport); - - // free the state itself - free(lp); -} - diff -r 5229a5d098b2 -r cefec18b8268 src/line_proto.h --- a/src/line_proto.h Thu May 28 00:35:02 2009 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,84 +0,0 @@ -#ifndef LINE_PROTO_H -#define LINE_PROTO_H - -/** - * @file - * - * Support for protocols that send/receive lines - */ -#include "transport.h" -#include "error.h" - -/** - * The line_proto state handle - */ -struct line_proto; - -/** - * User callbacks for event-based line_proto behaviour - */ -struct line_proto_callbacks { - /** Handle received line */ - void (*on_line) (char *line, void *arg); - - /** Transport failed, the line_proto is corrupt, you should call line_proto_release next. */ - void (*on_error) (const error_t *err, void *arg); -}; - -/** - * Create a new line_proto off the the given sock_stream. The newly allocated line_proto will be returned via *lp_ptr. - * - * The incoming lines are buffered in a buffer of \a buf_size bytes. This imposes a maximum limit on the line length. - * - * In case of errors, \a transport will be destroyed in any case. - * - * @param lp_ptr a pointer to the new line_proto will be returned via this pointer - * @param transport the connected transport to use - * @param buf_size the incoming/outgoing buffer size, should be enough to hold the biggest possible line - * @param callbacks the callbacks to use, a copy is stored - * @param cb_arg the read_cb callback argument - * @param err error information is returned via this pointer - */ -err_t line_proto_create (struct line_proto **lp_ptr, transport_t *transport, size_t buf_size, - const struct line_proto_callbacks *callbacks, void *cb_arg, error_t *err); - -/** - * Runs transport_read() with our internal buffer. If a full line was received, a pointer to our internal bufffer is - * returned via *line_ptr, and we return SUCCESS. If we don't yet have a full line, and receiving more would block, - * NULL is returned via *line_ptr instead. Otherwise, nonzero error return code. - * - * @param line_ptr a pointer to the received line is returned via this pointer - */ -err_t line_proto_recv (struct line_proto *lp, char **line_ptr); - -/** - * Write a single line to the sock_stream, buffering any incomplete fragment that remains unsent. Returns zero if the - * line was succesfully sent, >0 if it was only partially sent, or -err on errors. - * - * The given line should already include the terminating '\r\n' character sequence. - * - * @param line pointer to buffer containing \r\n\0 terminated line - */ -int line_proto_send (struct line_proto *lp, const char *line); - -/** - * Flush out any buffered line fragment. Returns zero if the buffer was flushed empty, >0 if there's still fragments - * remaining, or -err on errors. - * - * It is a bug to call this if there is no data waiting to be sent. - */ -int line_proto_flush (struct line_proto *lp); - -/** - * Get current error_info* - */ -const error_t* line_proto_error (struct line_proto *lp); - -/** - * Destroy any buffers and the underlying transport. - * - * This does not close the connection cleanly, and is intended for use to abort after errors. - */ -void line_proto_destroy (struct line_proto *lp); - -#endif /* LINE_PROTO_H */ diff -r 5229a5d098b2 -r cefec18b8268 src/lua_func.c --- a/src/lua_func.c Thu May 28 00:35:02 2009 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,207 +0,0 @@ -#include "lua_func.h" -#include "error.h" - -#include - -/** - * Pushes onto the stack the value at t[i] - */ -static void lua_getindex (lua_State *L, int t, int i) -{ - lua_pushinteger(L, i); - lua_gettable(L, t); -} - -/** - * Pushes onto the stack either: - * * the value at t[name] - * * the value at t[index] - * - * Returns the new index, or 0, if neither could be found - */ -static int lua_arg_lookup (lua_State *L, int t, const char *name, int index) -{ - // try name - lua_getfield(L, t, name); - - if (!lua_isnil(L, -1)) - return lua_gettop(L); - else - lua_pop(L, 1); - - // try index - lua_getindex(L, t, index); - - if (!lua_isnil(L, -1)) - return lua_gettop(L); - - else - lua_pop(L, 1); - - // not found - return 0; -} - -static const char *_lua_arg_string (lua_State *L, int index, const char *name, const char *def) -{ - const char *value; - - // use default? - if (lua_isnoneornil(L, index) && def != (const char *) LUA_ARG_REQUIRED) - return def; - - // value given? - if ((value = lua_tostring(L, index))) - return value; - - // error - luaL_error(L, "missing value for required string argument <%d:%s>", index, name); return NULL; -} - -static bool _lua_arg_bool (lua_State *L, int index, const char *name, int def) -{ - (void) name; - - // use default? - if (lua_isnoneornil(L, index) && def != LUA_ARG_REQUIRED) - return def; - - // value given - return lua_toboolean(L, index); -} - -static long _lua_arg_int (lua_State *L, int index, const char *name, long def) -{ - (void) name; - - // use default? - if (lua_isnoneornil(L, index) && def != LUA_ARG_REQUIRED) - return def; - - // conver to integer - // XXX: check compatibility? - return lua_tointeger(L, index); -} - -static void * _lua_arg_obj (lua_State *L, int index, const struct lua_type *type, bool optional) -{ - // not given? - if (!lua_isnoneornil(L, index)) - return lua_type_get(L, type, index); - - if (optional) - return NULL; - - luaL_error(L, "missing value for required object argument <%d:%s>", index, type->name); - return NULL; -} - -/** - * Look up the arg index to use for the given index/name. - * - * If no value is found for the corresponding index, returns zero. - */ -static int lua_arg_index (lua_State *L, int nargs, int index, const char *name) -{ - // lookup from table? - if (nargs == 2 && lua_istable(L, 2) && name) { - // push the value from the named field onto the stack - lua_getfield(L, 2, name); - - // no named field? - if (lua_isnil(L, -1)) { - lua_pop(L, 1); - - lua_getindex(L, 2, index - 1); - } - - // no index field? - if (lua_isnil(L, -1)) { - lua_pop(L, 1); - - return 0; - } - - // found either a named or indexed arg - return lua_gettop(L); - - } else if (index <= nargs) { - // use the same index - return index; - - } else { - // no index - return 0; - } -} - -const char *lua_arg_string (lua_State *L, int nargs, int index, const char *name, const char *def) -{ - return _lua_arg_string(L, lua_arg_index(L, nargs, index, name), name, def); -} - -bool lua_arg_bool (lua_State *L, int nargs, int index, const char *name, int def) -{ - return _lua_arg_bool(L, lua_arg_index(L, nargs, index, name), name, def); -} - -void* lua_arg_obj (lua_State *L, int nargs, int index, const struct lua_type *type, bool optional) -{ - return _lua_arg_obj(L, lua_arg_index(L, nargs, index, NULL), type, optional); -} - -long lua_arg_int (lua_State *L, int nargs, int index, const char *name, long def) -{ - return _lua_arg_int(L, lua_arg_index(L, nargs, index, name), name, def); -} - -void lua_args_parse (lua_State *L, const struct lua_func *func, void **obj_ptr, ...) -{ - int argidx = 1, argtbl = 0, idx; - const struct lua_func_arg *arg; - va_list vargs; - - // first, the obj argument - if (func->type) - *obj_ptr = lua_type_get(L, func->type, argidx++); - - // were we given a table of arguments? - if (lua_istable(L, argidx)) - argtbl = argidx++; - - // parse the args - va_start(vargs, obj_ptr); - - for (arg = func->args, idx = 1; arg->name && arg->type; arg++, idx++) { - int index; - - // map index - if (!argtbl) - // direct - index = argidx++; - - else - // lookup from table - index = lua_arg_lookup(L, argtbl, arg->name, idx); - - // apply - switch (arg->type) { - case LUA_ARG_STRING: - *va_arg(vargs, const char **) = _lua_arg_string(L, index, arg->name, arg->def.string); - break; - - case LUA_ARG_BOOL: - *va_arg(vargs, bool *) = _lua_arg_bool(L, index, arg->name, arg->def.boolean); - break; - - case LUA_ARG_INT: - *va_arg(vargs, long *) = _lua_arg_int(L, index, arg->name, arg->def.integer); - break; - - default: - NOT_REACHED(); - }; - } - - va_end(vargs); -} diff -r 5229a5d098b2 -r cefec18b8268 src/lua_func.h --- a/src/lua_func.h Thu May 28 00:35:02 2009 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,107 +0,0 @@ -#ifndef LUA_FUNC_H -#define LUA_FUNC_H - -/** - * @file - * - * Convenience functions for working with lua C functions - */ -#include "lua_type.h" -#include - -/** - * Lua function argument types - */ -enum lua_arg_type { - LUA_ARG_INVALID, - - /** A `const char *` pointing to lua-GC'd memory */ - LUA_ARG_STRING, - - /** A c99 `bool` */ - LUA_ARG_BOOL, - - /** A `long signed int` */ - LUA_ARG_INT, -}; - -/** - * Function argument def - */ -struct lua_func_arg { - /** Argument name */ - const char *name; - - /** Expected type */ - enum lua_arg_type type; - - /** Default value */ - union { - const char *string; - int boolean; - long integer; - } def; -}; - -/** - * Function def - */ -struct lua_func { - /** Object type, or NULL */ - const struct lua_type *type; - - /** Function name */ - const char *name; - - /** Help string */ - const char *help; - - /** Arguments */ - const struct lua_func_arg args[]; -}; - -/** - * Used as the "invalid" default value - */ -#define LUA_ARG_REQUIRED (-1) -#define LUA_ARG_STRING_REQUIRED ((const char *) (-1)) - -/** - * Define a function argument - */ -#define LUA_FUNC_ARG_STRING(name, def) { (name), LUA_ARG_STRING, { .string = (def) } } -#define LUA_FUNC_ARG_BOOL(name, def) { (name), LUA_ARG_BOOL, { .boolean = (def) } } -#define LUA_FUNC_ARG_INT(name, def) { (name), LUA_ARG_INT, { .integer = (def) } } -#define LUA_FUNC_ARG_END { NULL, 0, { 0 } } - -/** - * Define a function - */ -#define LUA_FUNC(type, name, help, ...) { (type), (name), (help), { __VA_ARGS__, LUA_FUNC_ARG_END } } - -/** - * Parse and return a string argument - */ -const char *lua_arg_string (lua_State *L, int nargs, int index, const char *name, const char *def); - -/** - * Parse and return a boolean argument - */ -bool lua_arg_bool (lua_State *L, int nargs, int index, const char *name, int def); - -/** - * Parse and return an integer argument - */ -long lua_arg_int (lua_State *L, int nargs, int index, const char *name, long def); - -/** - * Return a userdata argument at the given fixed index - */ -void* lua_arg_obj (lua_State *L, int nargs, int index, const struct lua_type *type, bool optional); - -/** - * Parse function arguments as defined - */ -void lua_args_parse (lua_State *L, const struct lua_func *func, void **obj_ptr, ...); - -#endif diff -r 5229a5d098b2 -r cefec18b8268 src/lua_irc.h --- a/src/lua_irc.h Thu May 28 00:35:02 2009 +0300 +++ b/src/lua_irc.h Thu May 28 01:17:36 2009 +0300 @@ -4,10 +4,11 @@ /** * Defines lua wrappers for the irc_* objects */ -#include "nexus_lua.h" -#include "lua_objs.h" #include "irc_client.h" +// XXX: remove this dep ASAP. +#include + /** * Our lua wrapper for irc_chan */ diff -r 5229a5d098b2 -r cefec18b8268 src/lua_objs.c --- a/src/lua_objs.c Thu May 28 00:35:02 2009 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,462 +0,0 @@ -#include "lua_objs.h" -#include "lua_irc.h" -#include "lua_func.h" -#include "lua_thread.h" -#include "log.h" - -#include -#include - -/** - * Wrapper for module - */ -struct lua_module { - struct module *module; -}; - -static struct lua_type lua_module_type = LUA_TYPE("spbot.module"); - -/** - * Create a lua_module userdata from the given module and push it onto the stack, returning 1. - * - * The given module should be a reference of its own right. - */ -static int lua_module_create (lua_State *L, struct module *module) -{ - // create the new obj - struct lua_module *lua_module = lua_type_create(L, &lua_module_type, sizeof(*lua_module)); - - // initialize - lua_module->module = module; - - // ok - return 1; -} - -/** - * module_put() our module reference - */ -static int lua_module__gc (lua_State *L) -{ - struct lua_module *lua_module = lua_type_get(L, &lua_module_type, 1); - - // put it - module_put(lua_module->module); - - return 0; -} - -static int lua_module_conf (lua_State *L) -{ - struct lua_module *lua_module = lua_type_get(L, &lua_module_type, 1); - const struct config_option *option; - struct error_info err; - bool is_err = true; - - // the list of given config values, and temporary storage for string values - struct config_value values[CONFIG_VALUES_MAX], *value = values; - char *value_bufs[CONFIG_VALUES_MAX], **value_buf = value_bufs; - - // number of arguments given - int nargs = lua_gettop(L), argidx = 2; - - // init to zero - memset(values, 0, sizeof(values)); - memset(value_bufs, 0, sizeof(value_bufs)); - - // XXX: come up with some better way... - struct nexus *nexus = lua_module->module->modules->nexus; - - // the config name - const char *conf_name = luaL_checkstring(L, argidx++); - - // look it up - if ((option = module_conf_lookup(lua_module->module, conf_name, &err)) == NULL) - return luaL_error(L, "module_conf_lookup: %s/%s: %s", module_name(lua_module->module), conf_name, error_msg(&err)); - - // maximum number of arguments accepted - int maxargs = config_params_count(option); - - // too many arguments? - if (nargs - argidx > maxargs) - return luaL_error(L, "lua_module_conf: too many arguments (>%d) given (%d)", maxargs, nargs - argidx); - - // the current param - const struct config_param *param = option->params; - - // apply each given argument to the correct param, storing it in value - for (; argidx <= nargs; argidx++, value++, param++) { - // the given config value - switch (lua_type(L, argidx)) { - case LUA_TNONE: - case LUA_TNIL: - // no value - value->type = CONFIG_NULL; - - break; - - case LUA_TSTRING: { - // string arg - const char *arg_str = lua_tostring(L, argidx); - - // copy it as a mutable string buffer - if ((*value_buf = strdup(arg_str)) == NULL) { - lua_pushfstring(L, "strdup"); - goto error; - } - - // parse it as a raw value - if (config_parse_param(param, nexus, value, *value_buf, &err)) { - lua_pushfstring(L, "config_parse: %s/%s: %s", option->name, *value_buf, error_msg(&err)); - goto error; - } - - // seek to next value_buf - value_buf++; - - } break; - - case LUA_TUSERDATA: - // some kind of userdata, use its metatable to figure out what type it is - if (!lua_getmetatable(L, argidx)) { - lua_pushfstring(L, "config value is userdata without metatable"); - goto error; - } - - // get the target metatable - lua_getfield(L, LUA_REGISTRYINDEX, "evirc.chan"); - - // is it a chan? - if (!lua_rawequal(L, -1, -2)) { - lua_pushfstring(L, "config value is userdata of unknown type"); - goto error; - } - - // pop the metatables - lua_pop(L, 2); - - // get the irc_chan - struct lua_chan *lua_chan = lua_touserdata(L, argidx); - - // build the value - value->type = CONFIG_IRC_CHAN; - value->irc_chan = lua_chan->chan; - - break; - - default: - lua_pushfstring(L, "config value is of unknown lua type '%s'", lua_typename(L, argidx)); - goto error; - - } - } - - // apply it - if (module_conf(lua_module->module, option, values, &err)) { - lua_pushfstring(L, "module_conf: %s/%s: %s", module_name(lua_module->module), option->name, error_msg(&err)); - goto error; - } - - // ok - is_err = false; - -error: - // release any allocated strings - for (value_buf = value_bufs; value_buf <= value_bufs + CONFIG_VALUES_MAX && *value_buf; value_buf++) - free(*value_buf); - - // either error or successful return - if (is_err) - return lua_error(L); - else - return 0; -} - -static int lua_module_unload (lua_State *L) -{ - struct lua_module *lua_module = lua_type_get(L, &lua_module_type, 1); - struct error_info err; - - // just unload it - if ((ERROR_CODE(&err) = module_unload(lua_module->module))) - return luaL_error(L, "module_unload: %s: %s", module_name(lua_module->module), error_msg(&err)); - - // ok - return 0; -} - -static struct lua_method lua_module_methods[] = LUA_METHODS( - LUA_METHOD("__gc", lua_module__gc, NULL ), - LUA_METHOD("conf", lua_module_conf, NULL ), - LUA_METHOD("unload", lua_module_unload, NULL ) -); - -/** - * Wrapper for modules - */ -struct lua_modules { - struct modules *modules; - - // strdup'd path for module_path - // XXX: remove when gc'd - char *path; -}; - -static struct lua_type lua_modules_type = LUA_TYPE("spbot.modules"); - -static int lua_modules__gc (lua_State *L) -{ - struct lua_modules *lua_modules = lua_type_get(L, &lua_modules_type, 1); - - // remove the modules path if it was set by us - if (lua_modules->path && modules_path(lua_modules->modules, NULL) == lua_modules->path) - modules_path(lua_modules->modules, ""); - - // release any strdup'd path - free(lua_modules->path); - - // ok - return 0; -} - -static int lua_modules_path (lua_State *L) -{ - struct lua_modules *lua_modules = lua_type_get(L, &lua_modules_type, 1); - char *path = NULL; - const char *old_path; - - if (!lua_isnoneornil(L, 2)) { - // the new path - if ((path = strdup(luaL_checkstring(L, 2))) == NULL) - return luaL_error(L, "strdup"); - } - - // set or get - old_path = modules_path(lua_modules->modules, path); - - // return the old path - if (old_path) - lua_pushstring(L, old_path); - else - lua_pushnil(L); - - if (path) { - // replace the old path - free(lua_modules->path); - lua_modules->path = path; - } - - // ok - return 1; -} - -static int lua_modules_load (lua_State *L) -{ - struct lua_modules *lua_modules = lua_type_get(L, &lua_modules_type, 1); - struct module *module; - struct module_info info; - struct error_info err; - - // the module name/path - info.name = luaL_checkstring(L, 2); - info.path = lua_isnoneornil(L, 3) ? NULL : luaL_checkstring(L, 3); - - // load and get a new reference - if (module_load(lua_modules->modules, &module, &info, &err)) - return luaL_error(L, "module_load: %s/%s: %s", info.name, info.path, error_msg(&err)); - - // wrap - return lua_module_create(L, module); -} - -static int lua_modules_module (lua_State *L) -{ - struct lua_modules *lua_modules = lua_type_get(L, &lua_modules_type, 1); - struct module *module; - - // the module name - const char *name = luaL_checkstring(L, 2); - - // look it up, as a new reference - if ((module = modules_get(lua_modules->modules, name)) == NULL) - return luaL_error(L, "module_get: %s: no such module", name); - - // wrap - return lua_module_create(L, module); -} - -static struct lua_method lua_modules_methods[] = LUA_METHODS( - LUA_METHOD("__gc", lua_modules__gc, NULL ), - LUA_METHOD("path", lua_modules_path, NULL ), - LUA_METHOD("load", lua_modules_load, NULL ), - LUA_METHOD("module", lua_modules_module, NULL ) - ); - - - -/** - * Initialize the spbot.modules type for lua_modules, and registers an instance bound to the given modules list at - * 'modules'. - */ -static void lua_modules_init (lua_State *L, struct modules *modules) -{ - // allocate the global "modules" object - struct lua_modules *lua_modules = lua_type_register_global(L, &lua_modules_type, lua_modules_methods, "modules", sizeof(*lua_modules)); - - // initialize it - lua_modules->modules = modules; -} - -/** - * Wrapper for nexus - */ -struct lua_nexus { - struct nexus *nexus; -}; - -static struct lua_type lua_nexus_type = LUA_TYPE("spbot.nexus"); - -static int lua_nexus_shutdown (lua_State *L) -{ - struct lua_nexus *lua_nexus = lua_type_get(L, &lua_nexus_type, 1); - - // just shut it down - nexus_shutdown(lua_nexus->nexus); - - return 0; -} - -static int lua_nexus_load_config (lua_State *L) -{ - struct lua_nexus *lua_nexus = lua_type_get(L, &lua_nexus_type, 1); - struct error_info err; - - const char *path = luaL_checkstring(L, 2); - - // just load it - if (nexus_load_config(lua_nexus->nexus, path, &err)) - return luaL_error(L, "nexus_load_config(%s): %s", path, error_msg(&err)); - - return 0; -} - -static struct lua_func lua_nexus_sleep_func = LUA_FUNC(&lua_nexus_type, "sleep", - "Schedules itself to resume after the given delay (in seconds) and yields", - - LUA_FUNC_ARG_INT("tv_sec", LUA_ARG_REQUIRED) - ); - -static void lua_nexus_sleep_wakeup (evutil_socket_t fd, short what, void *arg) -{ - lua_State *L = arg; - - (void) fd; - (void) what; - - // resume the thread that called lua_nexus_sleep - lua_thread_resume_state(L); -} - -static int lua_nexus_sleep (lua_State *L) -{ - struct lua_nexus *lua_nexus; - long tv_sec; - - // parse args - lua_args_parse(L, &lua_nexus_sleep_func, (void *) &lua_nexus, &tv_sec); - - // build tv - struct timeval tv = { tv_sec, 0 }; - - // schedule wakeup - // use a pure-timeout event - if (event_base_once(lua_nexus->nexus->ev_base, -1, EV_TIMEOUT, lua_nexus_sleep_wakeup, L, &tv)) - return luaL_error(L, "event_base_once"); - - // yield - return lua_thread_yield_state(L); -} - -static struct lua_method lua_nexus_methods[] = LUA_METHODS( - LUA_METHOD("shutdown", lua_nexus_shutdown, NULL ), - LUA_METHOD("load_config", lua_nexus_load_config, NULL ), - LUA_METHOD("sleep", lua_nexus_sleep, &lua_nexus_sleep_func ) - ); - -/** - * Initialize the spbot.nexus type for lua_nexus, and registers an instance bound to the given nexus list at - * 'nexus'. - */ -static void lua_nexus_init (lua_State *L, struct nexus *nexus) -{ - // allocate the global "nexus" object - struct lua_nexus *lua_nexus = lua_type_register_global(L, &lua_nexus_type, lua_nexus_methods, "nexus", sizeof(*lua_nexus)); - - // initialize it - lua_nexus->nexus = nexus; -} - - -/** - * Global functions - */ -static int lua_log_level (lua_State *L) -{ - // log level as a string - enum log_level new_level = luaL_checkoption(L, 1, NULL, log_level_names); - - // set it - set_log_level(new_level); - - // ok - return 0; -} - -static int lua_log (lua_State *L) -{ - // log level as a string - enum log_level level = luaL_checkoption(L, 1, NULL, log_level_names); - - // log message - const char *msg = luaL_checkstring(L, 2); - - // log it - _log_msg(level, "lua", "%s", msg); - - // ok - return 0; -} - -static const struct luaL_Reg lua_global_functions[] = { - { "log_level", lua_log_level }, - { "log", lua_log }, - { NULL, NULL } -}; - -static void lua_global_init (lua_State *L) -{ - const struct luaL_Reg *reg; - - for (reg = lua_global_functions; reg->name && reg->func; reg++) { - // put the function on the stack - lua_pushcfunction(L, reg->func); - - // set the global - lua_setglobal(L, reg->name); - } -} - -void lua_objs_init (struct nexus_lua *lua) -{ - // register types - lua_type_register(lua->st, &lua_module_type, lua_module_methods); - - // globals - lua_nexus_init(lua->st, lua->nexus); - lua_modules_init(lua->st, lua->nexus->modules); - - // global functions - lua_global_init(lua->st); -} - - diff -r 5229a5d098b2 -r cefec18b8268 src/lua_objs.h --- a/src/lua_objs.h Thu May 28 00:35:02 2009 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,24 +0,0 @@ -#ifndef LUA_OBJS_H -#define LUA_OBJS_H - -/** - * @file - * - * Defines lua functions to access the various objects in a nexus - */ -#include "nexus_lua.h" - -#include -#include - -// XXX: remove -#include "lua_type.h" - -/** - * Registers our lua runtime objects into the given lua state. - * - * Call in protected mode. - */ -void lua_objs_init (struct nexus_lua *lua); - -#endif /* LUA_OBJS_H */ diff -r 5229a5d098b2 -r cefec18b8268 src/lua_type.c --- a/src/lua_type.c Thu May 28 00:35:02 2009 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,69 +0,0 @@ -#include "lua_type.h" - -#include - -void lua_type_register (lua_State *L, const struct lua_type *type, const struct lua_method methods[]) -{ - const struct lua_method *method; - - // create the metatable - luaL_newmetatable(L, type->name); - - // set the metatable __index to itself - lua_pushvalue(L, -1); - lua_setfield(L, -1, "__index"); - - // add the methods to the metatable - for (method = methods; method->func; method++) { - lua_pushcfunction(L, method->func); - lua_setfield(L, -2, method->name); - } -} - -void* lua_type_create (lua_State *L, const struct lua_type *type, size_t size) -{ - // create the new userdata on the stack - void *ud = lua_newuserdata(L, size); - - // get the type and set it - luaL_getmetatable(L, type->name); - lua_setmetatable(L, -2); - - // ok - return ud; -} - -void* lua_type_register_global (lua_State *L, const struct lua_type *type, const struct lua_method methods[], - const char *global_name, size_t size) -{ - // allocate the global object - void *obj = lua_newuserdata(L, size); - - // create the type metatable - lua_type_register(L, type, methods); - - // set the userdata's metatable - lua_setmetatable(L, -2); - - // store it as a global - lua_setglobal(L, global_name); - - // ok - return obj; -} - -void* lua_type_get (lua_State *L, const struct lua_type *type, int index) -{ - void *ud; - - // validate the userdata arg - // XXX: the luaL_checkudata actually raises an error itself - if ((ud = luaL_checkudata(L, index, type->name)) == NULL) { - luaL_error(L, "bad type argument: `%s` expected", type->name); return NULL; - - } else { - // ok - return ud; - - } -} diff -r 5229a5d098b2 -r cefec18b8268 src/lua_type.h --- a/src/lua_type.h Thu May 28 00:35:02 2009 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,74 +0,0 @@ -#ifndef LUA_TYPE_H -#define LUA_TYPE_H - -/** - * @file - * - * Convenience functions for defining "types" in lua - */ -#include - -// XXX: remove -#include - -/** - * A type's method - * - * XXX: a name field? - */ -struct lua_method { - /** The name of the method */ - const char *name; - - /** The function pointer */ - lua_CFunction func; - - /** The function definition, optional */ - const struct lua_func *info; -}; - -#define LUA_METHOD(name, func, info) \ - { (name), (func), (info) } - -#define LUA_METHODS(...) \ - { __VA_ARGS__, { NULL, NULL, NULL } } - -/** - * A type - */ -struct lua_type { - /** The name of the type */ - const char *name; -}; - -#define LUA_TYPE(name) \ - { (name) } - -/** - * Register a new metadata table for the given type in the given lua state. - * - * This leaves the new type (metatable) on the stack. - */ -void lua_type_register (lua_State *L, const struct lua_type *type, const struct lua_method methods[]); - -/** - * Create a new instance of the given type. - * - * This leaves the new userdata object on the stack. - */ -void* lua_type_create (lua_State *L, const struct lua_type *type, size_t size); - -/** - * Create a new userdata type, and also create an instance of it, register it as a global, and return it. - * - * This leaves the new userdata object on the stack. - */ -void* lua_type_register_global (lua_State *L, const struct lua_type *type, const struct lua_method methods[], - const char *global_name, size_t size); - -/** - * Get an object of the given type from the given stack position - */ -void* lua_type_get (lua_State *L, const struct lua_type *type, int index); - -#endif diff -r 5229a5d098b2 -r cefec18b8268 src/msg_proto.c --- a/src/msg_proto.c Thu May 28 00:35:02 2009 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,493 +0,0 @@ -#include "msg_proto.h" - -#include -#include -#include - -/** - * I/O buffer - */ -struct msg_buf { - /** Buffer base pointer */ - char *base; - - /** Size of the buffer */ - size_t size; - - /** Current read/write offset */ - size_t off; -}; - -/** - * The minimum size used for any msg_buf::size related operation. - */ -#define MSG_BUF_MIN_SIZE 1024 - -/** - * Growth rate for size - */ -#define MSG_BUF_GROW_RATE 2 - -/** - * Initialize a message buffer at the given initial size - */ -err_t msg_buf_init (struct msg_buf *buf, size_t hint) -{ - // apply minimum size - if (hint < MSG_BUF_MIN_SIZE) - hint = MSG_BUF_MIN_SIZE; - - // allocate the initial buffer - if ((buf->base = malloc(hint)) == NULL) - return ERR_MEM; - - // set fields - buf->size = hint; - buf->off = 0; - - // ok - return SUCCESS; -} - -/** - * Grow the buffer if needed to fit the given capacity. - */ -err_t msg_buf_grow (struct msg_buf *buf, size_t size) -{ - char *tmp = buf->base; - - if (buf->size >= size) - // nothing to do - return SUCCESS; - - // calculate new size - while (buf->size < size) - buf->size *= MSG_BUF_GROW_RATE; - - // resize - if ((buf->base = realloc(buf->base, buf->size)) == NULL) { - buf->base = tmp; - - return ERR_MEM; - } - - // ok - return SUCCESS; -} - -/** - * Drain \a len bytes off the head of the buffer - */ -err_t msg_buf_drain (struct msg_buf *buf, size_t len) -{ - // simple memmove - memmove(buf->base, buf->base + len, buf->off - len); - - // update offfset - buf->off -= len; - - // ok - return SUCCESS; -} - -/** - * Read into the buffer from a transport_t. - * - * This will attempt to read \a len bytes onto the end of the buffer, growing it if needed to fit. - * - * This may read/return more data than the given len. Use msg_buf_drain the remove the data from the buffer once you - * have used it. - * - * Returns the number of new bytes read, zero for transport read buffer empty, -err_t for error. - */ -ssize_t msg_buf_read (struct msg_buf *buf, transport_t *transport, size_t len, error_t *err) -{ - ssize_t ret; - - // clamp size - if (len < MSG_BUF_MIN_SIZE) - len = MSG_BUF_MIN_SIZE; - - // ensure space - if ((ERROR_CODE(err) = msg_buf_grow(buf, buf->off + len))) - goto error; - - // read - if ((ret = transport_read(transport, buf->base + buf->off, len, err)) < 0) - goto error; - - // no data left? - if (!ret) - return 0; - - // update offset - buf->off += ret; - - // ok - return ret; - -error: - return -ERROR_CODE(err); -} - -/** - * Drives transport_write on the given data until all the given data is written, or zero is returned. - * - * @param transport transport to write to - * @param data input data - * @param len number of bytes to write from data - * @param err returned error info - * @return number of bytes written (which may be zero or less than len), or -err_t. - */ -static ssize_t _transport_write_all (transport_t *transport, const char *data, size_t len, error_t *err) -{ - ssize_t ret; - size_t written = 0; - - while (len) { - // try and write out remaining data - if ((ret = transport_write(transport, data, len, err)) < 0) - goto error; - - if (!ret) { - // write buffer full - break; - - } else { - // update and continue - written += ret; - data += ret; - len -= ret; - } - } - - // ok - return written; - -error: - return -ERROR_CODE(err); -} - -/** - * If the buffer is empty, this will attempt to write the given data directly using transport_write until either all - * the data is written (in which case nothing more needs to be done), or the transport won't accept any more writes, - * in which case the remaining data will be buffered. - * - * If the buffer is not empty, then the given data will be added to the end of the buffer, since otherwise the order of - * data would be broken. - * - * In either case, transport_write semantics garuntee that our buffer will either be empty, or an on_write will be - * pending on the transport. See msg_buf_flush() for how to handle transport_callbacks::on_write. - */ -err_t msg_buf_write (struct msg_buf *buf, transport_t *transport, const void *data_ptr, size_t len, error_t *err) -{ - ssize_t ret; - const char *data = data_ptr; - - if (!buf->off) { - // no data buffered, so we can try and write directly - if ((ret = _transport_write_all(transport, data, len, err)) < 0) - goto error; - - // update written - data += ret; - len -= ret; - - if (len == 0) - // wrote it all - return SUCCESS; - } - - // ensure space - if ((ERROR_CODE(err) = msg_buf_grow(buf, buf->off + len))) - goto error; - - // store - memcpy(buf->base + buf->off, data, len); - - // update - buf->off += len; - - // ok - return SUCCESS; - -error: - return ERROR_CODE(err); -} - -/** - * Flush buffered write data to the transport, driving transport_write() until either all of our bufferd data has been - * written, or the transport will not accept any more. - * - * In either case, transport_write semantics garuntee that our buffer will either be empty, or an on_write will be - * pending on the transport. - */ -err_t msg_buf_flush (struct msg_buf *buf, transport_t *transport, error_t *err) -{ - ssize_t ret; - - // write - if ((ret = _transport_write_all(transport, buf->base, buf->off, err)) < 0) - goto error; - - if (ret) - // unbuffer the written data - msg_buf_drain(buf, ret); - - // ok - return SUCCESS; - -error: - return ERROR_CODE(err); -} - -/** - * Deinitialize msg_buf to release allocated buffers - */ -void msg_buf_deinit (struct msg_buf *buf) -{ - // release - free(buf->base); - - // reset - buf->base = NULL; - buf->size = buf->off = 0; -} - -/** - * Message header - */ -struct msg_header { - /** Message length, including header */ - uint16_t len; -}; - -/** - * Message header size - */ -#define MSG_PROTO_HEADER_SIZE (sizeof(uint16_t)) - -/** - * Our state struct - */ -struct msg_proto { - /** The transport */ - transport_t *transport; - - /** User callbacks */ - const struct msg_proto_callbacks *cb_tbl; - - /** User callback argument */ - void *cb_arg; - - /** Input buffer */ - struct msg_buf in; - - /** Output buffer */ - struct msg_buf out; -}; - -/** - * Signal error to user - */ -static void msg_proto_error (struct msg_proto *proto, const error_t *err) -{ - // invoke user callback - proto->cb_tbl->on_error(proto, err, proto->cb_arg); -} - -/** - * Attempt to read the current header from our input buffer. - * - * Returns >0 for full header, 0 for incomplete header, -err_t for error. - */ -static int msg_proto_peek_header (struct msg_proto *proto, struct msg_header *header, error_t *err) -{ - if (proto->in.off < MSG_PROTO_HEADER_SIZE) - // not enough data for header - return 0; - - // read header - header->len = ntohs(*((uint16_t *) proto->in.base)); - - // bad header? - if (header->len < MSG_PROTO_HEADER_SIZE) - JUMP_SET_ERROR_STR(err, ERR_MISC, "message_header::len"); - - // ok, got header - return 1; - -error: - return -ERROR_CODE(err); -} - -/** - * Recieved a message with the given header, and a pointer to the message data - * - * XXX: what to do if the user callback destroys the msg_proto? - */ -static err_t msg_proto_on_msg (struct msg_proto *proto, struct msg_header *header, char *data, error_t *err) -{ - (void) err; - - // invoke user callback - proto->cb_tbl->on_msg(proto, data, header->len - MSG_PROTO_HEADER_SIZE, proto->cb_arg); - - // XXX: handle user errors - return SUCCESS; -} - -static void msg_proto_on_read (transport_t *transport, void *arg) -{ - struct msg_proto *proto = arg; - struct msg_header header; - ssize_t ret; - error_t err; - - // we might be able to read more than one message per event - do { - // try and read message length for incomplete message - if ((ret = msg_proto_peek_header(proto, &header, &err)) < 0) - goto error; - - // need to read more data? - if (!ret || header.len > proto->in.off) { - // msg_buf_read a minimum size, so passing a zero is OK - size_t to_read = ret ? header.len : 0; - - // read into our buffer - if ((ret = msg_buf_read(&proto->in, transport, to_read, &err)) < 0) - goto error; - - } else { - // handle full message - if (msg_proto_on_msg(proto, &header, proto->in.base + MSG_PROTO_HEADER_SIZE, &err)) - goto error; - - // remove the data from the buffer - msg_buf_drain(&proto->in, header.len); - } - } while (ret); - - // ok - return; - -error: - // notify user - msg_proto_error(proto, &err); -} - -static void msg_proto_on_write (transport_t *transport, void *arg) -{ - struct msg_proto *proto = arg; - error_t err; - - // flush - if (msg_buf_flush(&proto->out, transport, &err)) - // notify user on transport errors - msg_proto_error(proto, &err); -} - -static void msg_proto_on_error (transport_t *transport, const error_t *err, void *arg) -{ - struct msg_proto *proto = arg; - - (void) transport; - - // report to user - msg_proto_error(proto, err); -} - -static const struct transport_callbacks msg_proto_transport_callbacks = { - .on_read = msg_proto_on_read, - .on_write = msg_proto_on_write, - .on_error = msg_proto_on_error, -}; - -err_t msg_proto_create (struct msg_proto **proto_ptr, transport_t *transport, const struct msg_proto_callbacks *cb_tbl, void *cb_arg, error_t *err) -{ - struct msg_proto *proto; - - // alloc - if ((proto = calloc(1, sizeof(*proto))) == NULL) - return ERR_MEM; - - // store - proto->transport = transport; - proto->cb_tbl = cb_tbl; - proto->cb_arg = cb_arg; - - // init - if ( - (ERROR_CODE(err) = msg_buf_init(&proto->in, 0)) - || (ERROR_CODE(err) = msg_buf_init(&proto->out, 0)) - ) - goto error; - - // setup transport - if ((ERROR_CODE(err) = transport_events(transport, TRANSPORT_READ | TRANSPORT_WRITE))) - goto error; - - transport_set_callbacks(transport, &msg_proto_transport_callbacks, proto); - - // ok - *proto_ptr = proto; - - return SUCCESS; - -error: - // release - msg_proto_destroy(proto); - - return ERROR_CODE(err); -} - -/** - * Build and write out the data for the given header - */ -static err_t msg_proto_write_header (struct msg_proto *proto, const struct msg_header *header, error_t *err) -{ - char buf[MSG_PROTO_HEADER_SIZE]; - - // validate - if (header->len < MSG_PROTO_HEADER_SIZE) - return SET_ERROR(err, ERR_MISC); - - // build - *((uint16_t *) buf) = htons(header->len); - - // write - return msg_buf_write(&proto->out, proto->transport, buf, sizeof(buf), err); -} - -err_t msg_proto_send (struct msg_proto *proto, const void *data, size_t len, error_t *err) -{ - struct msg_header header; - - // build header - header.len = MSG_PROTO_HEADER_SIZE + len; - - // write it - if ( - msg_proto_write_header(proto, &header, err) - || msg_buf_write(&proto->out, proto->transport, data, len, err) - ) - return ERROR_CODE(err); - - // ok - return SUCCESS; -} - -void msg_proto_destroy (struct msg_proto *proto) -{ - // drop buffers - msg_buf_deinit(&proto->in); - msg_buf_deinit(&proto->out); - - // kill transport - transport_destroy(proto->transport); - - // release ourself - free(proto); -} - diff -r 5229a5d098b2 -r cefec18b8268 src/msg_proto.h --- a/src/msg_proto.h Thu May 28 00:35:02 2009 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,54 +0,0 @@ -#ifndef MSG_PROTO_H -#define MSG_PROTO_H - -/** - * @param - * - * Support for simple protocols that send/recieve length-prefixed messages over a transport stream. - * - * This implementation is mostly geared towards handling a reasonable number of reasonably sized messages in a - * reasonable way. Hence, - */ -#include "transport.h" - -/** - * Protocol state struct - */ -struct msg_proto; - -/** - * User callbacks - */ -struct msg_proto_callbacks { - /** - * Message recieved. - * - * XXX: currently you must not call msg_proto_destroy from within this callback - */ - void (*on_msg) (struct msg_proto *proto, void *data, size_t len, void *arg); - - /** - * Transport/protocol error occured in event handling. - */ - void (*on_error) (struct msg_proto *proto, const error_t *err, void *arg); -}; - -/** - * Create a msg_proto state using the given transport. - * - * This will install our callback handlers on the given transport. - */ -err_t msg_proto_create (struct msg_proto **proto_ptr, transport_t *transport, const struct msg_proto_callbacks *cb_tbl, void *cb_arg, error_t *err); - -/** - * Send a message to the other endpoint - */ -err_t msg_proto_send (struct msg_proto *proto, const void *data, size_t len, error_t *err); - -/** - * Destroy the protocol state and transport - */ -void msg_proto_destroy (struct msg_proto *proto); - - -#endif /* MSG_PROTO_H */ diff -r 5229a5d098b2 -r cefec18b8268 src/resolve.c --- a/src/resolve.c Thu May 28 00:35:02 2009 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,89 +0,0 @@ -#include "resolve.h" - -#include -#include -#include -#include - -err_t resolve_addr (struct resolve_result *res, const char *node, const char *service, int socktype, int ai_flags, error_t *err) -{ - struct addrinfo hints, *ai; - int ret; - - // build hints - memset(&hints, 0, sizeof(hints)); - hints.ai_flags = ai_flags; - hints.ai_family = AF_UNSPEC; - hints.ai_socktype = socktype; - - // resolve (blocking) - if ((ret = getaddrinfo(node, service, &hints, &ai))) - RETURN_SET_ERROR_EXTRA(err, ERR_GETADDRINFO, ret); - - // no results? - if (!ai) - RETURN_SET_ERROR(err, ERR_GETADDRINFO_EMPTY); - - // store - res->list = res->item = ai; - - // ok - return SUCCESS; -} - -void resolve_result_init (struct resolve_result *res) -{ - res->list = res->item = NULL; -} - -struct addrinfo* resolve_result_next (struct resolve_result *res) -{ - if (!res->item) { - // no items left - return NULL; - - } else { - // ...remember the current item - struct addrinfo *ai = res->item; - - if (res->item) - // advance item to the next one - res->item = res->item->ai_next; - - // return the current one - return ai; - } -} - -void resolve_result_deinit (struct resolve_result *res) -{ - if (res->list) - // free them all - freeaddrinfo(res->list); - - // invalidate - res->list = res->item = NULL; -} - -const char * resolve_addr_text (const struct addrinfo *addr) -{ - static char text[1024]; - char host[NI_MAXHOST], service[NI_MAXSERV]; - int ret; - - // lookup the reverse nameinfo - if ((ret = getnameinfo( - addr->ai_addr, addr->ai_addrlen, - host, sizeof(host), service, sizeof(service), - NI_NUMERICHOST | NI_NUMERICSERV - ))) { - strcpy(host, "???"); - strcpy(service, "???"); - } - - // format message - snprintf(text, sizeof(text), "[%s]:%s", host, service); - - // return static pointer - return text; -} diff -r 5229a5d098b2 -r cefec18b8268 src/resolve.h --- a/src/resolve.h Thu May 28 00:35:02 2009 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,59 +0,0 @@ -#ifndef RESOLVE_H -#define RESOLVE_H - -/** - * @file - * - * DNS resolver interface - */ -#include "error.h" -#include - -/** - * Lookup result state - */ -struct resolve_result { - /** Head of the addrinfo list */ - struct addrinfo *list; - - /** Current addrinfo item */ - struct addrinfo *item; -}; - -/** - * Resolve the given node/service tuple as a series of addrinfos for the given socktype. - * - * This will never return an empty result. - * - * XXX: blocking DNS stuff - * - * @param res where to store the result state - * @param node hostname/address to look up - * @param service service/port to look up - * @param socktype a SOCK_* value to return addrinfo's for that socktype - * @param ai_flags optional bitmask of AI_* flags to use - * @param err returned error info - */ -err_t resolve_addr (struct resolve_result *res, const char *node, const char *service, int socktype, int ai_flags, error_t *err); - -/** - * Initialize the given result to zero - */ -void resolve_result_init (struct resolve_result *res); - -/** - * Get the next address from a result, if any left - */ -struct addrinfo* resolve_result_next (struct resolve_result *res); - -/** - * Release the addrinfo resources associated with the given result - */ -void resolve_result_deinit (struct resolve_result *res); - -/** - * Returns a pointer to a static buffer containing a string description of the given addrinfo - */ -const char * resolve_addr_text (const struct addrinfo *addr); - -#endif /* RESOLVE_H */ diff -r 5229a5d098b2 -r cefec18b8268 src/service.c --- a/src/service.c Thu May 28 00:35:02 2009 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,35 +0,0 @@ -#include "service_internal.h" - -const struct object_type service_type_type = { - .parent = NULL, -}; - -void service_init (service_t *service, const struct service_type *type, const struct service_info *info) -{ - // init object - object_init(&service->base_obj, &type->base_type); - - // store user info - service->info = *info; -} - -void* service_check (service_t *service, const struct service_type *type) -{ - return object_cast(&service->base_obj, &type->base_type); -} - -void service_error (service_t *service, const error_t *err) -{ - // just call the user callback - service->info.cb_tbl->on_error(service, err, service->info.cb_arg); -} - -void service_destroy (service_t *service) -{ - const struct service_type *type = object_type(&service->base_obj, &service_type_type); - - // invoke method - type->methods.deinit(service); - - free(service); -} diff -r 5229a5d098b2 -r cefec18b8268 src/service.h --- a/src/service.h Thu May 28 00:35:02 2009 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,50 +0,0 @@ -#ifndef SERVICE_H -#define SERVICE_H - -/** - * @file - * - * Defines a simple interface for creating services, which listen for connections and create transport_t's. - */ -#include "transport.h" - -/** - * Opaque state struct. - */ -typedef struct service service_t; - -/** - * User callbacks for services. - */ -struct service_callbacks { - /** - * The service broke. - * - * This is only called for errors which occur when called directly from the event loop, and never for errors that - * occur inside of calls to service_*. - */ - void (*on_error) (service_t *service, const error_t *err, void *arg); -}; - -/** - * User info required to build a service - */ -struct service_info { - /** Callback table */ - const struct service_callbacks *cb_tbl; - - /** Callback context arg */ - void *cb_arg; - - /** Settings for the service's client transports */ - struct transport_info trans_info; -}; - -/** - * Destroy a service to stop accepting any connections and release all resources. - * - * Any connected client transports should stay intact (?) - */ -void service_destroy (service_t *service); - -#endif /* SERVICE_H */ diff -r 5229a5d098b2 -r cefec18b8268 src/service_internal.h --- a/src/service_internal.h Thu May 28 00:35:02 2009 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,60 +0,0 @@ -#ifndef SERVICE_INTERNAL_H -#define SERVICE_INTERNAL_H - -/** - * @file - * - * Internal interface for implementations of service_t - */ -#include "service.h" -#include "transport.h" -#include "object.h" - -/** - * The object_type of service_type - */ -extern const struct object_type service_type_type; - -/** - * Type definition with method table - */ -struct service_type { - struct object_type base_type; - - /** Method table */ - struct service_methods { - /** - * Release internal state, but not the service_t itself - */ - void (*deinit) (service_t *service); - } methods; -}; - -/** - * Base service_t state - */ -struct service { - struct object base_obj; - - /** User info */ - struct service_info info; -}; - -/** - * Initialize a service by binding it to a specific type, with the given user info for this service, and for spawned transports. - */ -void service_init (service_t *service, const struct service_type *type, const struct service_info *info); - -/** - * Used to up-cast a generic service_t pointer to an implementation of the given service_type (or subtype). - * - * It is a bug to call this with a service of a different type. - */ -void* service_check (service_t *service, const struct service_type *type); - -/** - * The service failed, call the user callback - */ -void service_error (service_t *service, const error_t *err); - -#endif /* SERVICE_INTERNAL_H */ diff -r 5229a5d098b2 -r cefec18b8268 src/sock.c --- a/src/sock.c Thu May 28 00:35:02 2009 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,22 +0,0 @@ - -#include "sock_internal.h" -#include "ssl_internal.h" - -#include - -// global sock_stream_ctx instance -struct sock_stream_ctx _sock_stream_ctx; - -err_t sock_init (struct event_base *ev_base, struct error_info *err) -{ - // store ev_base - _sock_stream_ctx.ev_base = ev_base; - - // XXX: just call these all directly for now - if (ssl_global_init(err)) - return ERROR_CODE(err); - - // done - return SUCCESS; -} - diff -r 5229a5d098b2 -r cefec18b8268 src/sock.h --- a/src/sock.h Thu May 28 00:35:02 2009 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,23 +0,0 @@ -#ifndef SOCK_H -#define SOCK_H - -/** - * @file - * - * Legacy sock_* interface for global state - */ -#include "error.h" -#include -#include - -/** - * Initialize the socket module's global state. Call this before calling any other sock_* functions. - * - * The given \a ev_base is the libevent base to use for nonblocking operation. - * - * @param ev_base the libevent base to use for events - * @param err returned error info - */ -err_t sock_init (struct event_base *ev_base, error_t *err); - -#endif diff -r 5229a5d098b2 -r cefec18b8268 src/sock_internal.h --- a/src/sock_internal.h Thu May 28 00:35:02 2009 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,20 +0,0 @@ -#ifndef SOCK_INTERNAL_H -#define SOCK_INTERNAL_H - -/** - * @file - * - * internal sock_* interface - */ -#include "sock.h" - -/** - * Global sock_stream_ctx used for sock_init() and all sock_stream's - */ -extern struct sock_stream_ctx { - /** libevent core */ - struct event_base *ev_base; - -} _sock_stream_ctx; - -#endif /* SOCK_INTERNAL_H */ diff -r 5229a5d098b2 -r cefec18b8268 src/spbot/lua_objs.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/spbot/lua_objs.c Thu May 28 01:17:36 2009 +0300 @@ -0,0 +1,462 @@ +#include "lua_objs.h" +#include "lua_irc.h" +#include "lua_func.h" +#include "lua_thread.h" +#include "log.h" + +#include +#include + +/** + * Wrapper for module + */ +struct lua_module { + struct module *module; +}; + +static struct lua_type lua_module_type = LUA_TYPE("spbot.module"); + +/** + * Create a lua_module userdata from the given module and push it onto the stack, returning 1. + * + * The given module should be a reference of its own right. + */ +static int lua_module_create (lua_State *L, struct module *module) +{ + // create the new obj + struct lua_module *lua_module = lua_type_create(L, &lua_module_type, sizeof(*lua_module)); + + // initialize + lua_module->module = module; + + // ok + return 1; +} + +/** + * module_put() our module reference + */ +static int lua_module__gc (lua_State *L) +{ + struct lua_module *lua_module = lua_type_get(L, &lua_module_type, 1); + + // put it + module_put(lua_module->module); + + return 0; +} + +static int lua_module_conf (lua_State *L) +{ + struct lua_module *lua_module = lua_type_get(L, &lua_module_type, 1); + const struct config_option *option; + struct error_info err; + bool is_err = true; + + // the list of given config values, and temporary storage for string values + struct config_value values[CONFIG_VALUES_MAX], *value = values; + char *value_bufs[CONFIG_VALUES_MAX], **value_buf = value_bufs; + + // number of arguments given + int nargs = lua_gettop(L), argidx = 2; + + // init to zero + memset(values, 0, sizeof(values)); + memset(value_bufs, 0, sizeof(value_bufs)); + + // XXX: come up with some better way... + struct nexus *nexus = lua_module->module->modules->nexus; + + // the config name + const char *conf_name = luaL_checkstring(L, argidx++); + + // look it up + if ((option = module_conf_lookup(lua_module->module, conf_name, &err)) == NULL) + return luaL_error(L, "module_conf_lookup: %s/%s: %s", module_name(lua_module->module), conf_name, error_msg(&err)); + + // maximum number of arguments accepted + int maxargs = config_params_count(option); + + // too many arguments? + if (nargs - argidx > maxargs) + return luaL_error(L, "lua_module_conf: too many arguments (>%d) given (%d)", maxargs, nargs - argidx); + + // the current param + const struct config_param *param = option->params; + + // apply each given argument to the correct param, storing it in value + for (; argidx <= nargs; argidx++, value++, param++) { + // the given config value + switch (lua_type(L, argidx)) { + case LUA_TNONE: + case LUA_TNIL: + // no value + value->type = CONFIG_NULL; + + break; + + case LUA_TSTRING: { + // string arg + const char *arg_str = lua_tostring(L, argidx); + + // copy it as a mutable string buffer + if ((*value_buf = strdup(arg_str)) == NULL) { + lua_pushfstring(L, "strdup"); + goto error; + } + + // parse it as a raw value + if (config_parse_param(param, nexus, value, *value_buf, &err)) { + lua_pushfstring(L, "config_parse: %s/%s: %s", option->name, *value_buf, error_msg(&err)); + goto error; + } + + // seek to next value_buf + value_buf++; + + } break; + + case LUA_TUSERDATA: + // some kind of userdata, use its metatable to figure out what type it is + if (!lua_getmetatable(L, argidx)) { + lua_pushfstring(L, "config value is userdata without metatable"); + goto error; + } + + // get the target metatable + lua_getfield(L, LUA_REGISTRYINDEX, "evirc.chan"); + + // is it a chan? + if (!lua_rawequal(L, -1, -2)) { + lua_pushfstring(L, "config value is userdata of unknown type"); + goto error; + } + + // pop the metatables + lua_pop(L, 2); + + // get the irc_chan + struct lua_chan *lua_chan = lua_touserdata(L, argidx); + + // build the value + value->type = CONFIG_IRC_CHAN; + value->irc_chan = lua_chan->chan; + + break; + + default: + lua_pushfstring(L, "config value is of unknown lua type '%s'", lua_typename(L, argidx)); + goto error; + + } + } + + // apply it + if (module_conf(lua_module->module, option, values, &err)) { + lua_pushfstring(L, "module_conf: %s/%s: %s", module_name(lua_module->module), option->name, error_msg(&err)); + goto error; + } + + // ok + is_err = false; + +error: + // release any allocated strings + for (value_buf = value_bufs; value_buf <= value_bufs + CONFIG_VALUES_MAX && *value_buf; value_buf++) + free(*value_buf); + + // either error or successful return + if (is_err) + return lua_error(L); + else + return 0; +} + +static int lua_module_unload (lua_State *L) +{ + struct lua_module *lua_module = lua_type_get(L, &lua_module_type, 1); + struct error_info err; + + // just unload it + if ((ERROR_CODE(&err) = module_unload(lua_module->module))) + return luaL_error(L, "module_unload: %s: %s", module_name(lua_module->module), error_msg(&err)); + + // ok + return 0; +} + +static struct lua_method lua_module_methods[] = LUA_METHODS( + LUA_METHOD("__gc", lua_module__gc, NULL ), + LUA_METHOD("conf", lua_module_conf, NULL ), + LUA_METHOD("unload", lua_module_unload, NULL ) +); + +/** + * Wrapper for modules + */ +struct lua_modules { + struct modules *modules; + + // strdup'd path for module_path + // XXX: remove when gc'd + char *path; +}; + +static struct lua_type lua_modules_type = LUA_TYPE("spbot.modules"); + +static int lua_modules__gc (lua_State *L) +{ + struct lua_modules *lua_modules = lua_type_get(L, &lua_modules_type, 1); + + // remove the modules path if it was set by us + if (lua_modules->path && modules_path(lua_modules->modules, NULL) == lua_modules->path) + modules_path(lua_modules->modules, ""); + + // release any strdup'd path + free(lua_modules->path); + + // ok + return 0; +} + +static int lua_modules_path (lua_State *L) +{ + struct lua_modules *lua_modules = lua_type_get(L, &lua_modules_type, 1); + char *path = NULL; + const char *old_path; + + if (!lua_isnoneornil(L, 2)) { + // the new path + if ((path = strdup(luaL_checkstring(L, 2))) == NULL) + return luaL_error(L, "strdup"); + } + + // set or get + old_path = modules_path(lua_modules->modules, path); + + // return the old path + if (old_path) + lua_pushstring(L, old_path); + else + lua_pushnil(L); + + if (path) { + // replace the old path + free(lua_modules->path); + lua_modules->path = path; + } + + // ok + return 1; +} + +static int lua_modules_load (lua_State *L) +{ + struct lua_modules *lua_modules = lua_type_get(L, &lua_modules_type, 1); + struct module *module; + struct module_info info; + struct error_info err; + + // the module name/path + info.name = luaL_checkstring(L, 2); + info.path = lua_isnoneornil(L, 3) ? NULL : luaL_checkstring(L, 3); + + // load and get a new reference + if (module_load(lua_modules->modules, &module, &info, &err)) + return luaL_error(L, "module_load: %s/%s: %s", info.name, info.path, error_msg(&err)); + + // wrap + return lua_module_create(L, module); +} + +static int lua_modules_module (lua_State *L) +{ + struct lua_modules *lua_modules = lua_type_get(L, &lua_modules_type, 1); + struct module *module; + + // the module name + const char *name = luaL_checkstring(L, 2); + + // look it up, as a new reference + if ((module = modules_get(lua_modules->modules, name)) == NULL) + return luaL_error(L, "module_get: %s: no such module", name); + + // wrap + return lua_module_create(L, module); +} + +static struct lua_method lua_modules_methods[] = LUA_METHODS( + LUA_METHOD("__gc", lua_modules__gc, NULL ), + LUA_METHOD("path", lua_modules_path, NULL ), + LUA_METHOD("load", lua_modules_load, NULL ), + LUA_METHOD("module", lua_modules_module, NULL ) + ); + + + +/** + * Initialize the spbot.modules type for lua_modules, and registers an instance bound to the given modules list at + * 'modules'. + */ +static void lua_modules_init (lua_State *L, struct modules *modules) +{ + // allocate the global "modules" object + struct lua_modules *lua_modules = lua_type_register_global(L, &lua_modules_type, lua_modules_methods, "modules", sizeof(*lua_modules)); + + // initialize it + lua_modules->modules = modules; +} + +/** + * Wrapper for nexus + */ +struct lua_nexus { + struct nexus *nexus; +}; + +static struct lua_type lua_nexus_type = LUA_TYPE("spbot.nexus"); + +static int lua_nexus_shutdown (lua_State *L) +{ + struct lua_nexus *lua_nexus = lua_type_get(L, &lua_nexus_type, 1); + + // just shut it down + nexus_shutdown(lua_nexus->nexus); + + return 0; +} + +static int lua_nexus_load_config (lua_State *L) +{ + struct lua_nexus *lua_nexus = lua_type_get(L, &lua_nexus_type, 1); + struct error_info err; + + const char *path = luaL_checkstring(L, 2); + + // just load it + if (nexus_load_config(lua_nexus->nexus, path, &err)) + return luaL_error(L, "nexus_load_config(%s): %s", path, error_msg(&err)); + + return 0; +} + +static struct lua_func lua_nexus_sleep_func = LUA_FUNC(&lua_nexus_type, "sleep", + "Schedules itself to resume after the given delay (in seconds) and yields", + + LUA_FUNC_ARG_INT("tv_sec", LUA_ARG_REQUIRED) + ); + +static void lua_nexus_sleep_wakeup (evutil_socket_t fd, short what, void *arg) +{ + lua_State *L = arg; + + (void) fd; + (void) what; + + // resume the thread that called lua_nexus_sleep + lua_thread_resume_state(L); +} + +static int lua_nexus_sleep (lua_State *L) +{ + struct lua_nexus *lua_nexus; + long tv_sec; + + // parse args + lua_args_parse(L, &lua_nexus_sleep_func, (void *) &lua_nexus, &tv_sec); + + // build tv + struct timeval tv = { tv_sec, 0 }; + + // schedule wakeup + // use a pure-timeout event + if (event_base_once(lua_nexus->nexus->ev_base, -1, EV_TIMEOUT, lua_nexus_sleep_wakeup, L, &tv)) + return luaL_error(L, "event_base_once"); + + // yield + return lua_thread_yield_state(L); +} + +static struct lua_method lua_nexus_methods[] = LUA_METHODS( + LUA_METHOD("shutdown", lua_nexus_shutdown, NULL ), + LUA_METHOD("load_config", lua_nexus_load_config, NULL ), + LUA_METHOD("sleep", lua_nexus_sleep, &lua_nexus_sleep_func ) + ); + +/** + * Initialize the spbot.nexus type for lua_nexus, and registers an instance bound to the given nexus list at + * 'nexus'. + */ +static void lua_nexus_init (lua_State *L, struct nexus *nexus) +{ + // allocate the global "nexus" object + struct lua_nexus *lua_nexus = lua_type_register_global(L, &lua_nexus_type, lua_nexus_methods, "nexus", sizeof(*lua_nexus)); + + // initialize it + lua_nexus->nexus = nexus; +} + + +/** + * Global functions + */ +static int lua_log_level (lua_State *L) +{ + // log level as a string + enum log_level new_level = luaL_checkoption(L, 1, NULL, log_level_names); + + // set it + set_log_level(new_level); + + // ok + return 0; +} + +static int lua_log (lua_State *L) +{ + // log level as a string + enum log_level level = luaL_checkoption(L, 1, NULL, log_level_names); + + // log message + const char *msg = luaL_checkstring(L, 2); + + // log it + _log_msg(level, "lua", "%s", msg); + + // ok + return 0; +} + +static const struct luaL_Reg lua_global_functions[] = { + { "log_level", lua_log_level }, + { "log", lua_log }, + { NULL, NULL } +}; + +static void lua_global_init (lua_State *L) +{ + const struct luaL_Reg *reg; + + for (reg = lua_global_functions; reg->name && reg->func; reg++) { + // put the function on the stack + lua_pushcfunction(L, reg->func); + + // set the global + lua_setglobal(L, reg->name); + } +} + +void lua_objs_init (struct nexus_lua *lua) +{ + // register types + lua_type_register(lua->st, &lua_module_type, lua_module_methods); + + // globals + lua_nexus_init(lua->st, lua->nexus); + lua_modules_init(lua->st, lua->nexus->modules); + + // global functions + lua_global_init(lua->st); +} + + diff -r 5229a5d098b2 -r cefec18b8268 src/spbot/lua_objs.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/spbot/lua_objs.h Thu May 28 01:17:36 2009 +0300 @@ -0,0 +1,21 @@ +#ifndef LUA_OBJS_H +#define LUA_OBJS_H + +/** + * @file + * + * Defines lua functions to access the various objects in a nexus + */ +#include "nexus_lua.h" + +#include +#include + +/** + * Registers our lua runtime objects into the given lua state. + * + * Call in protected mode. + */ +void lua_objs_init (struct nexus_lua *lua); + +#endif /* LUA_OBJS_H */ diff -r 5229a5d098b2 -r cefec18b8268 src/spbot/nexus.c --- a/src/spbot/nexus.c Thu May 28 00:35:02 2009 +0300 +++ b/src/spbot/nexus.c Thu May 28 01:17:36 2009 +0300 @@ -1,6 +1,6 @@ #include "nexus.h" #include "lua_config.h" -#include "sock.h" +#include #include #include diff -r 5229a5d098b2 -r cefec18b8268 src/spbot/nexus_lua.c --- a/src/spbot/nexus_lua.c Thu May 28 00:35:02 2009 +0300 +++ b/src/spbot/nexus_lua.c Thu May 28 01:17:36 2009 +0300 @@ -1,5 +1,5 @@ #include "nexus_lua.h" -#include "../lua_objs.h" +#include "lua_objs.h" #include "../lua_irc.h" #include @@ -37,14 +37,14 @@ // alloc if ((lua = calloc(1, sizeof(*lua))) == NULL) - return SET_ERROR(err, ERR_CALLOC); + return SET_ERROR_MEM(err); // store lua->nexus = nexus; // create the lua state if ((lua->st = luaL_newstate()) == NULL) - JUMP_SET_ERROR(err, ERR_LUA_MEM); + JUMP_SET_ERROR(err, &lua_errors, ERR_LUA_MEM); // init in protected mode if (nexus_lua_error(lua->st, lua_cpcall(lua->st, &nexus_lua_init, lua), err)) @@ -74,7 +74,7 @@ int ret; bool loaded = false; - RESET_ERROR(err); + error_reset(err); // load the line as a lua function if ((ret = luaL_loadstring(lua->st, chunk))) @@ -105,13 +105,13 @@ const char *error = lua_tostring(L, -1); switch (ret) { - case 0: RETURN_SET_ERROR(err, SUCCESS); - case LUA_ERRSYNTAX: RETURN_SET_ERROR_STR(err, ERR_LUA_SYNTAX, error); - case LUA_ERRRUN: RETURN_SET_ERROR_STR(err, ERR_LUA_RUN, error); - case LUA_ERRMEM: RETURN_SET_ERROR_STR(err, ERR_LUA_MEM, error); - case LUA_ERRERR: RETURN_SET_ERROR_STR(err, ERR_LUA_ERR, error); - case LUA_ERRFILE: RETURN_SET_ERROR_STR(err, ERR_LUA_FILE, error); - default: RETURN_SET_ERROR_EXTRA(err, ERR_UNKNOWN, ret); + case 0: return SUCCESS; + case LUA_ERRSYNTAX: return SET_ERROR_STR(err, &lua_errors, ERR_LUA_SYNTAX, error); + case LUA_ERRRUN: return SET_ERROR_STR(err, &lua_errors, ERR_LUA_RUN, error); + case LUA_ERRMEM: return SET_ERROR_STR(err, &lua_errors, ERR_LUA_MEM, error); + case LUA_ERRERR: return SET_ERROR_STR(err, &lua_errors, ERR_LUA_ERR, error); + case LUA_ERRFILE: return SET_ERROR_STR(err, &lua_errors, ERR_LUA_FILE, error); + default: return SET_ERROR(err, &general_errors, ERR_UNKNOWN); }; } diff -r 5229a5d098b2 -r cefec18b8268 src/ssl.c --- a/src/ssl.c Thu May 28 00:35:02 2009 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,102 +0,0 @@ -#include "ssl_internal.h" - -#include - -/* - * Global shared anonymous client credentials - */ -struct ssl_client_cred ssl_client_cred_anon = { .x509 = NULL, .verify = false, .refcount = 0 }; - - -// XXX: GnuTLS log func -void _log (int level, const char *msg) -{ - printf("gnutls: %d: %s", level, msg); -} - -err_t ssl_global_init (error_t *err) -{ - // global init - if ((ERROR_EXTRA(err) = gnutls_global_init()) < 0) - return SET_ERROR(err, ERR_GNUTLS_GLOBAL_INIT); - - // initialize the anon client credentials - if ((ERROR_EXTRA(err) = gnutls_certificate_allocate_credentials(&ssl_client_cred_anon.x509)) < 0) - return SET_ERROR(err, ERR_GNUTLS_CERT_ALLOC_CRED); - - // XXX: debug -// gnutls_global_set_log_function(&_log); -// gnutls_global_set_log_level(11); - - // done - return SUCCESS; -} - -static void ssl_client_cred_destroy (struct ssl_client_cred *cred) -{ - // simple - gnutls_certificate_free_credentials(cred->x509); - - free(cred); -} - -err_t ssl_client_cred_create (struct ssl_client_cred **ctx_cred, - const char *cafile_path, bool verify, - const char *cert_path, const char *pkey_path, - error_t *err -) { - struct ssl_client_cred *cred; - - // alloc it - if ((cred = calloc(1, sizeof(*cred))) == NULL) - return SET_ERROR(err, ERR_CALLOC); - - // create the cert - if ((ERROR_EXTRA(err) = gnutls_certificate_allocate_credentials(&cred->x509)) < 0) - JUMP_SET_ERROR(err, ERR_GNUTLS_CERT_ALLOC_CRED); - - // load the trusted ca certs? - if (cafile_path) { - // load them - if ((ERROR_EXTRA(err) = gnutls_certificate_set_x509_trust_file(cred->x509, cafile_path, GNUTLS_X509_FMT_PEM)) < 0) - JUMP_SET_ERROR(err, ERR_GNUTLS_CERT_SET_X509_TRUST_FILE); - - } - - // set the verify flags? - cred->verify = verify; - gnutls_certificate_set_verify_flags(cred->x509, 0); - - // load the client cert? - if (cert_path || pkey_path) { - // need both... - assert(cert_path && pkey_path); - - // load - if ((ERROR_EXTRA(err) = gnutls_certificate_set_x509_key_file(cred->x509, cert_path, pkey_path, GNUTLS_X509_FMT_PEM))) - JUMP_SET_ERROR(err, ERR_GNUTLS_CERT_SET_X509_KEY_FILE); - } - - // ok - cred->refcount = 1; - *ctx_cred = cred; - - return SUCCESS; - -error: - // release - ssl_client_cred_destroy(cred); - - return ERROR_CODE(err); -} - -void ssl_client_cred_get (struct ssl_client_cred *cred) -{ - cred->refcount++; -} - -void ssl_client_cred_put (struct ssl_client_cred *cred) -{ - if (--cred->refcount == 0) - ssl_client_cred_destroy(cred); -} diff -r 5229a5d098b2 -r cefec18b8268 src/ssl.h --- a/src/ssl.h Thu May 28 00:35:02 2009 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,77 +0,0 @@ -#ifndef SSL_H -#define SSL_H - -/** - * @file - * - * SSL transport implementation. - */ -#include "transport.h" -#include - -/** - * SSL client credentials for use with ssl_client_credentials/sock_ssl_connect - */ -struct ssl_client_cred; - -/** - * Set up SSL client credentials for use with sock_ssl_connect. This includes information both required to identify - * ourselves to the server, as well as to verify the server. - * - * To verify the server's certificate, pass in a path to a file containing the CA certificate(s) that should be used to - * verify the server's certificate, and then either give `verify` as true to force verification, or false to simply - * warn. XXX: not entirely true - * - * To supply a client certificate to the server, pass in the paths to the cert/pkey files. If given as NULL, an - * anonymous client certificate will be used. Both must be supplied if given. - * - * The newly created SSL client credential will initially have a refcount of one, and can then be used with sock_ssl_connect. - * - * @param ctx_cred the newly created client credentials are returned via this - * @param cafile_path given as non-NULL to load trusted certs for verification from the given path - * @param verify force verification of the peer cert - * @param cert_path path to the client certificate file, or NULL - * @param pkey_path path to the client private key, or NULL - * @param err returned error info - */ -err_t ssl_client_cred_create (struct ssl_client_cred **ctx_cred, - const char *cafile_path, bool verify, - const char *cert_path, const char *pkey_path, - error_t *err -); - -/** - * Aquire a referenec for the given cred. - */ -void ssl_client_cred_get (struct ssl_client_cred *cred); - -/** - * Release a reference allocated for the given cred. - */ -void ssl_client_cred_put (struct ssl_client_cred *cred); - -/** - * Start a non-blocking SSL connect/handshake to the given host/service. The socket will not yet be connected when the - * function returns, but rather, the eventual redyness/failure of the connect/handshake will be indicated later using - * the given \a cb_func. - * - * The given ssl_client_cred should either be NULL to use an anonymous client cert and not verify the server cert, - * or a ssl_client_cred allocated using ssl_client_cred_create(). The contexts are reference-counted, so once - * a cred context has been released, it will be destroyed once the last connection using it is destroyed. - * - * @param info the transport setup info - * @param transport_ptr returned transport - * @param hostname the hostname to connect to - * @param service the TCP service name (i.e. port) to connect to - * @param cred the SSL client credentials to use, if not NULL - * @param err returned error info - */ -err_t ssl_connect (const struct transport_info *info, transport_t **transport_ptr, - const char *hostname, const char *service, - struct ssl_client_cred *cred, - error_t *err - ); - - - -#endif /* SSL_H */ diff -r 5229a5d098b2 -r cefec18b8268 src/ssl_client.c --- a/src/ssl_client.c Thu May 28 00:35:02 2009 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,454 +0,0 @@ -#include "ssl_internal.h" - -#include - -#include -#include -#include - -// XXX: remove -#include "log.h" -#include - - -/** - * Cast a ssl_client to a sock_fd. - */ -#define SSL_CLIENT_FD(client_ptr) (&(client_ptr)->base_tcp.base_trans.base_fd) - -/** - * Cast a ssl_client to a sock_stream. - */ -#define SSL_CLIENT_TRANSPORT(client_ptr) (&(client_ptr)->base_tcp.base_trans.base_fd.base) - - - -/** - * Enable the TCP events based on the session's gnutls_record_get_direction(). - */ -static err_t ssl_client_ev_enable (struct ssl_client *client, error_t *err) -{ - int ret; - short mask; - - // gnutls_record_get_direction tells us what I/O operation gnutls would have required for the last - // operation, so we can use that to determine what events to register - switch ((ret = gnutls_record_get_direction(client->session))) { - case 0: - // read more data - mask = TRANSPORT_READ; - break; - - case 1: - // write buffer full - mask = TRANSPORT_WRITE; - break; - - default: - // random error - RETURN_SET_ERROR_EXTRA(err, ERR_GNUTLS_RECORD_GET_DIRECTION, ret); - } - - // do the enabling - if ((ERROR_CODE(err) = transport_fd_enable(SSL_CLIENT_FD(client), mask))) - return ERROR_CODE(err); - - - return SUCCESS; -} - -/** - * Translate a set of gnutls_certificate_status_t values to a constant error message - */ -static const char* ssl_client_verify_error (unsigned int status) -{ - if (status & GNUTLS_CERT_REVOKED) - return "certificate was revoked"; - - else if (status & GNUTLS_CERT_INVALID) { - if (status & GNUTLS_CERT_SIGNER_NOT_FOUND) - return "certificate signer was not found"; - - else if (status & GNUTLS_CERT_SIGNER_NOT_CA) - return "certificate signer is not a Certificate Authority"; - - else if (status & GNUTLS_CERT_INSECURE_ALGORITHM) - return "certificate signed using an insecure algorithm"; - - else - return "certificate could not be verified"; - - } else - return "unknown error"; - -} - -/** - * Perform the certificate validation procedure on the peer cert. - * - * Based on the GnuTLS examples/ex-rfc2818.c - */ -static err_t ssl_client_verify (struct ssl_client *client, error_t *err) -{ - unsigned int status; - const gnutls_datum_t *cert_list; - unsigned int cert_list_size; - gnutls_x509_crt_t cert = NULL; - time_t t, now; - - // init - RESET_ERROR(err); - now = time(NULL); - - // inspect the peer's cert chain using the installed trusted CAs - if ((ERROR_EXTRA(err) = gnutls_certificate_verify_peers2(client->session, &status))) - JUMP_SET_ERROR(err, ERR_GNUTLS_CERT_VERIFY_PEERS2); - - // verify errors? - if (status) - JUMP_SET_ERROR_STR(err, ERR_GNUTLS_CERT_VERIFY, ssl_client_verify_error(status)); - - // import the main cert - assert(gnutls_certificate_type_get(client->session) == GNUTLS_CRT_X509); - - if ((ERROR_EXTRA(err) = gnutls_x509_crt_init(&cert))) - JUMP_SET_ERROR_STR(err, ERR_GNUTLS_CERT_VERIFY, "gnutls_x509_crt_init"); - - if ((cert_list = gnutls_certificate_get_peers(client->session, &cert_list_size)) == NULL) - JUMP_SET_ERROR_STR(err, ERR_GNUTLS_CERT_VERIFY, "gnutls_certificate_get_peers"); - - if (!cert_list_size) - JUMP_SET_ERROR_STR(err, ERR_GNUTLS_CERT_VERIFY, "cert_list_size"); - - if ((ERROR_EXTRA(err) = gnutls_x509_crt_import(cert, &cert_list[0], GNUTLS_X509_FMT_DER))) - JUMP_SET_ERROR_STR(err, ERR_GNUTLS_CERT_VERIFY, "gnutls_x509_crt_import"); - - // check expire/activate... not sure if we need to do this - if ((t = gnutls_x509_crt_get_expiration_time(cert)) == ((time_t) -1) || t < now) - JUMP_SET_ERROR_STR(err, ERR_GNUTLS_CERT_VERIFY, "gnutls_x509_crt_get_expiration_time"); - - if ((t = gnutls_x509_crt_get_activation_time(cert)) == ((time_t) -1) || t > now) - JUMP_SET_ERROR_STR(err, ERR_GNUTLS_CERT_VERIFY, "gnutls_x509_crt_get_activation_time"); - - // check hostname - if (!gnutls_x509_crt_check_hostname(cert, client->hostname)) - JUMP_SET_ERROR_STR(err, ERR_GNUTLS_CERT_VERIFY, "gnutls_x509_crt_check_hostname"); - -error: - // cleanup - if (cert) - gnutls_x509_crt_deinit(cert); - - // should be SUCCESS - return ERROR_CODE(err); -} - - -/** - * Our handshake driver. This will execute the next gnutls_handshake step, handling E_AGAIN. - * - * This updates the ssl_client::handshake state internally, as used by ssl_client_event_handler. - * - * If the client is marked as verify, this will perform the verification, returning on any errors, and then unset the - * verify flag - this ensures that the peer cert is only verified once per connection... - * - * @return >0 for finished handshake, 0 for handshake-in-progress, -err_t for errors. - */ -static int ssl_client_handshake (struct ssl_client *client, error_t *err) -{ - int ret; - - // perform the handshake - if ((ret = gnutls_handshake(client->session)) < 0 && ret != GNUTLS_E_AGAIN) - JUMP_SET_ERROR_EXTRA(err, ERR_GNUTLS_HANDSHAKE, ret); - - // complete? - if (ret == 0) { - // update state - client->handshake = false; - - // verify? - if (client->verify) { - // perform the validation - if (ssl_client_verify(client, err)) - goto error; - - // unmark - client->verify = false; - } - - // handshake done - return 1; - - } else { - // set state, isn't really needed every time, but easier this way - client->handshake = true; - - // re-enable the event for the next iteration - return ssl_client_ev_enable(client, err); - } - -error: - return -ERROR_CODE(err); -} - -/** - * Our transport_fd event handler. Drive the handshake if that's current, otherwise, invoke user callbacks. - */ -static void ssl_client_on_event (struct transport_fd *fd, short what, void *arg) -{ - struct ssl_client *client = arg; - error_t err; - - (void) fd; - - // XXX: timeouts - (void) what; - - // are we in the handshake cycle? - if (client->handshake) { - RESET_ERROR(&err); - - // perform the next handshake step - // this returns zero when the handshake is not yet done, errors/completion then trigger the else-if-else below - if (ssl_client_handshake(client, &err) == 0) { - // handshake continues - - } else if (!SSL_CLIENT_TRANSPORT(client)->connected) { - // the async connect+handshake process has completed - // invoke the user connect callback directly with appropriate error - transport_connected(SSL_CLIENT_TRANSPORT(client), ERROR_CODE(&err) ? &err : NULL, true); - - } else { - // in-connection re-handshake completed - if (ERROR_CODE(&err)) - // the re-handshake failed, so this transport is dead - transport_error(SSL_CLIENT_TRANSPORT(client), &err); - - else - // re-handshake completed, so continue with the transport_callbacks - transport_invoke(SSL_CLIENT_TRANSPORT(client), what); - } - - } else { - // normal transport operation - // gnutls might be able to proceed now, so invoke user callbacks - transport_invoke(SSL_CLIENT_TRANSPORT(client), what); - } -} - -static err_t ssl_client__read (transport_t *transport, void *buf, size_t *len, error_t *err) -{ - struct ssl_client *client = transport_check(transport, &ssl_client_type); - int ret; - - // read gnutls record - do { - ret = gnutls_record_recv(client->session, buf, *len); - - } while (ret == GNUTLS_E_INTERRUPTED); - - // errors - // XXX: E_REHANDSHAKE? - if (ret < 0 && ret != GNUTLS_E_AGAIN) - RETURN_SET_ERROR_EXTRA(err, ERR_GNUTLS_RECORD_RECV, ret); - - else if (ret == 0) - return SET_ERROR(err, ERR_EOF); - - - // EAGAIN? - if (ret < 0) { - *len = 0; - - } else { - // updated length - *len = ret; - - } - - return SUCCESS; -} - -static err_t ssl_client__write (transport_t *transport, const void *buf, size_t *len, error_t *err) -{ - struct ssl_client *client = transport_check(transport, &ssl_client_type); - int ret; - - // read gnutls record - do { - ret = gnutls_record_send(client->session, buf, *len); - - } while (ret == GNUTLS_E_INTERRUPTED); - - // errors - if (ret < 0 && ret != GNUTLS_E_AGAIN) - RETURN_SET_ERROR_EXTRA(err, ERR_GNUTLS_RECORD_SEND, ret); - - else if (ret == 0) - return SET_ERROR(err, ERR_WRITE_EOF); - - - // eagain? - if (ret < 0) { - *len = 0; - - } else { - // updated length - *len = ret; - } - - return SUCCESS; -} - -void ssl_client_deinit (struct ssl_client *client) -{ - // close the session rudely - gnutls_deinit(client->session); - client->session = NULL; - - // terminate the TCP transport - tcp_client_deinit(&client->base_tcp); - - if (client->cred) { - // drop the cred ref - ssl_client_cred_put(client->cred); - - client->cred = NULL; - } - - // free - free(client->hostname); - client->hostname = NULL; -} - - -static void ssl_client__deinit (transport_t *transport) -{ - struct ssl_client *client = transport_check(transport, &ssl_client_type); - - // die - ssl_client_deinit(client); -} - -/** - * Our tcp_client-invoked connect handler - */ -static void ssl_client__connected (transport_t *transport, const error_t *tcp_err) -{ - struct ssl_client *client = transport_check(transport, &ssl_client_type); - error_t err; - - // trap errors to let the user handle them directly - if (tcp_err) - JUMP_SET_ERROR_INFO(&err, tcp_err); - - // bind default transport functions (recv/send) to use the TCP fd - gnutls_transport_set_ptr(client->session, (gnutls_transport_ptr_t) (long int) SSL_CLIENT_FD(client)->fd); - - // add ourselves as the event handler - if ((ERROR_CODE(&err) = transport_fd_setup(SSL_CLIENT_FD(client), ssl_client_on_event, client))) - goto error; - - // start handshake - if (ssl_client_handshake(client, &err)) - // this should complete with SUCCESS if it returns >0 - goto error; - - // ok, so we wait... - return; - -error: - // tell the user - transport_connected(transport, &err, true); -} - -struct transport_type ssl_client_type = { - .base_type = { - .parent = &tcp_client_type.base_type, - }, - .methods = { - .read = ssl_client__read, - .write = ssl_client__write, - .deinit = ssl_client__deinit, - ._connected = ssl_client__connected, - }, -}; - - - -static void ssl_client_destroy (struct ssl_client *client) -{ - ssl_client_deinit(client); - - free(client); -} - -err_t ssl_connect (const struct transport_info *info, transport_t **transport_ptr, - const char *hostname, const char *service, - struct ssl_client_cred *cred, - error_t *err - ) -{ - struct ssl_client *client = NULL; - - // alloc - if ((client = calloc(1, sizeof(*client))) == NULL) - return SET_ERROR(err, ERR_CALLOC); - - // initialize base - transport_init(SSL_CLIENT_TRANSPORT(client), &ssl_client_type, info); - - if (!cred) { - // default credentials - cred = &ssl_client_cred_anon; - - } else { - // take a ref - client->cred = cred; - cred->refcount++; - }; - - // do verify? - if (cred->verify) - client->verify = true; - - // init - if ((client->hostname = strdup(hostname)) == NULL) - JUMP_SET_ERROR(err, ERR_STRDUP); - - // initialize TCP - tcp_client_init(&client->base_tcp); - - // initialize client session - if ((ERROR_EXTRA(err) = gnutls_init(&client->session, GNUTLS_CLIENT)) < 0) - JUMP_SET_ERROR(err, ERR_GNUTLS_INIT); - - // ...default priority stuff - if ((ERROR_EXTRA(err) = gnutls_set_default_priority(client->session))) - JUMP_SET_ERROR(err, ERR_GNUTLS_SET_DEFAULT_PRIORITY); - - // XXX: silly hack for OpenSSL interop - gnutls_dh_set_prime_bits(client->session, 512); - - // bind credentials - if ((ERROR_EXTRA(err) = gnutls_credentials_set(client->session, GNUTLS_CRD_CERTIFICATE, cred->x509))) - JUMP_SET_ERROR(err, ERR_GNUTLS_CRED_SET); - - // TCP connect - if (tcp_client_connect_async(&client->base_tcp, hostname, service, err)) - goto error; - - // done, wait for the connect to complete - *transport_ptr = SSL_CLIENT_TRANSPORT(client); - - return SUCCESS; - -error: - // cleanup - ssl_client_destroy(client); - - return ERROR_CODE(err); -} - - diff -r 5229a5d098b2 -r cefec18b8268 src/ssl_internal.h --- a/src/ssl_internal.h Thu May 28 00:35:02 2009 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,87 +0,0 @@ -#ifndef SSL_INTERNAL_H -#define SSL_INTERNAL_H - -/** - * @file - * - * A sock_stream implementation using GnuTLS for SSL - */ -#include "ssl.h" -#include "tcp_internal.h" - -#include - -/** - * GnuTLS library error codes - */ -enum ssl_error_code { - _ERR_SSL_BEGIN = _ERR_GNUTLS, - - ERR_GNUTLS_CERT_ALLOC_CRED, - ERR_GNUTLS_GLOBAL_INIT, - ERR_GNUTLS_INIT, - ERR_GNUTLS_SET_DEFAULT_PRIORITY, - ERR_GNUTLS_CRED_SET, - ERR_GNUTLS_HANDSHAKE, - ERR_GNUTLS_RECORD_SEND, - ERR_GNUTLS_RECORD_RECV, - ERR_GNUTLS_RECORD_GET_DIRECTION, - ERR_GNUTLS_CERT_VERIFY_PEERS2, - ERR_GNUTLS_CERT_VERIFY, - ERR_GNUTLS_CERT_SET_X509_TRUST_FILE, - ERR_GNUTLS_CERT_SET_X509_KEY_FILE, -}; - -/** - * GnuTLS credentials for client sockets. - */ -struct ssl_client_cred { - /** Our client certificate */ - gnutls_certificate_credentials_t x509; - - /** Should we verify? */ - bool verify; - - /** Refcount from ssl_client */ - int refcount; -}; - -/** - * Global anonymous x509 credentials - */ -extern struct ssl_client_cred ssl_client_cred_anon; - -/* - * Our transport_type - */ -extern struct transport_type ssl_client_type; - -/** - * An SSL-encrypted TCP connection, using libgnutls - */ -struct ssl_client { - /** The underlying TCP connection */ - struct tcp_client base_tcp; - - /** The hostname we connected to, for verification */ - char *hostname; - - /** The credentials we are using, unless anon */ - struct ssl_client_cred *cred; - - /** The GnuTLS session for this connection */ - gnutls_session_t session; - - /** Should we verify the peer cert? */ - bool verify; - - /** Are we running a handshake? */ - bool handshake; -}; - -/** - * Initialize the global gnutls state - */ -err_t ssl_global_init (error_t *err); - -#endif /* SSL_INTERNAL_H */ diff -r 5229a5d098b2 -r cefec18b8268 src/tcp.c --- a/src/tcp.c Thu May 28 00:35:02 2009 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,43 +0,0 @@ -#include "tcp_internal.h" - -int tcp_sock_create (const struct addrinfo *addr, error_t *err) -{ - int sock; - - // create a new socket using addr->ai_family/socktype/protocol - if ((sock = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol)) < 0) - JUMP_SET_ERROR_ERRNO(err, ERR_SOCKET); - - return sock; - -error: - return -ERROR_CODE(err); -} - -err_t tcp_sock_error (evutil_socket_t sock, error_t *err) -{ - int optval; - socklen_t optlen; - - RESET_ERROR(err); - - // init params - optval = 0; - optlen = sizeof(optval); - - // read error code - if (getsockopt(sock, SOL_SOCKET, SO_ERROR, &optval, &optlen)) - RETURN_SET_ERROR_ERRNO(err, ERR_GETSOCKOPT); - - // sanity-check optlen... not sure if this is sensible - if (optlen != sizeof(optval)) - RETURN_SET_ERROR_EXTRA(err, ERR_GETSOCKOPT, EINVAL); - - // then store the system error code - ERROR_EXTRA(err) = optval; - - // ok - return SUCCESS; -} - - diff -r 5229a5d098b2 -r cefec18b8268 src/tcp.h --- a/src/tcp.h Thu May 28 00:35:02 2009 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,35 +0,0 @@ -#ifndef TCP_H -#define TCP_H - -/** - * @file - * - * TCP transport/service implementation. - * - * XXX: provide some TCP-specific type/functions? - */ -#include "transport.h" -#include "service.h" - -/** - * Connect the given transport via TCP to the given host/service. The transport will not be ready for use until the - * transport_callbacks::on_connect callback has been invoked. - * - * XXX: blocking DNS resolution - * - * @param info the transport setup info - * @param transport_ptr returned transport - * @param host the hostname to connect to - * @param service the service name (i.e. port) to connect to - * @param err returned error info - */ -err_t tcp_connect (const struct transport_info *info, transport_t **transport_ptr, - const char *host, const char *service, error_t *err); - -/** - * Create a passive/listening TCP socket on the given interface/port (NULL to pick automatically). - */ -err_t tcp_listen (const struct service_info *info, service_t **service_ptr, - const char *interface, const char *service, error_t *err); - -#endif diff -r 5229a5d098b2 -r cefec18b8268 src/tcp_client.c --- a/src/tcp_client.c Thu May 28 00:35:02 2009 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,278 +0,0 @@ -#include "tcp_internal.h" -#include "log.h" - -/* - * Our transport methods - */ -static void tcp_client__deinit (transport_t *transport) -{ - struct tcp_client *client = transport_check(transport, &tcp_client_type); - - // proxy - tcp_client_deinit(client); -} - -/* - * Our transport_type - */ -const struct transport_type tcp_client_type = { - .base_type = { - .parent = &tcp_transport_type.base_type, - }, - .methods = { - .read = transport_fd__read, - .write = transport_fd__write, - .events = transport_fd__events, - .deinit = tcp_client__deinit, - }, -}; - -/* - * Forward-declare - */ -static void tcp_client_on_connect (struct transport_fd *fd, short what, void *arg); - -/* - * Function implementations - */ -void tcp_client_init (struct tcp_client *client) -{ - tcp_transport_init(&client->base_trans, -1); - - resolve_result_init(&client->rr); -} - -/* - * Start connecting to the given address in a non-blocking fashion. Returns any errors that immediately crop up, - * otherwise eventually calls tcp_client_connect_done(). - */ -static err_t tcp_client_connect_addr (struct tcp_client *client, struct addrinfo *addr, error_t *err) -{ - struct transport_fd *_fd = &client->base_trans.base_fd; - int ret; - evutil_socket_t sock; - err_t tmp; - - // first, create the socket - if ((sock = tcp_sock_create(addr, err)) < 0) - return ERROR_CODE(err); - - // set it as our sock - if ((ERROR_CODE(err) = transport_fd_set(_fd, sock))) - goto error; - - // then, set it up as nonblocking - if ((ERROR_CODE(err) = transport_fd_nonblock(_fd, true))) - goto error; - - // then, initiate the connect - if ((ret = connect(sock, addr->ai_addr, addr->ai_addrlen)) < 0 && errno != EINPROGRESS) - JUMP_SET_ERROR_ERRNO(err, ERR_CONNECT); - - if (ret < 0) { - // ok, connect started, setup our completion callback - if ((ERROR_CODE(err) = transport_fd_setup(_fd, tcp_client_on_connect, client))) - goto error; - - // enable for write - if ((ERROR_CODE(err) = transport_fd_enable(_fd, TRANSPORT_WRITE))) - goto error; - - } else { - // oops... blocking connect - fail to avoid confusion - // XXX: come up with a better error name to use - // XXX: support non-async connects as well - JUMP_SET_ERROR_EXTRA(err, ERR_CONNECT, EINPROGRESS); - } - - // ok - return SUCCESS; - -error: - // close the stuff we did open - if ((tmp = transport_fd_close(_fd))) - log_warn("error closing socket after connect error: %s", error_name(tmp)); - - return ERROR_CODE(err); -} - - -/* - * Attempt to connect to the next addrinfo, or the next one, if that fails, etc. - * - * This does not call transport_connected(). - */ -static err_t tcp_client_connect_continue (struct tcp_client *client, error_t *err) -{ - struct addrinfo *addr; - - // try and connect to each one until we find one that works - while ((addr = resolve_result_next(&client->rr))) { - // attempt to start connect - if (tcp_client_connect_addr(client, addr, err) == SUCCESS) - break; - - // log a warning on the failed connect - log_warn_error(err, "%s", resolve_addr_text(addr)); - } - - - if (addr) - // we succesfully did a tcp_client_connect_addr on valid address - return SUCCESS; - - else - // all of the connect_async_addr's failed, return the last error - return ERROR_CODE(err); -} - -/* - * Cleanup our resolver state and any connect callbacks after a connect - */ -static void tcp_client_connect_cleanup (struct tcp_client *client) -{ - // drop the resolver stuff - resolve_result_deinit(&client->rr); - - // remove our event handler - transport_fd_clear(&client->base_trans.base_fd); -} - -/* - * Our async connect operation has completed, clean up, set up state for event-based operation with user callbacks, and - * invoke transport_connected(). - * - * The given \a err should be NULL for successful completion, or the error for failures. - */ -static void tcp_client_connect_done (struct tcp_client *client, error_t *conn_err) -{ - error_t err; - - // cleanup - tcp_client_connect_cleanup(client); - - if (conn_err) - JUMP_SET_ERROR_INFO(&err, conn_err); - - // let the transport handle the rest - if (tcp_transport_connected(&client->base_trans, &err)) - goto error; - - // ok - return; - -error: - // pass the error on to transport - transport_connected(&client->base_trans.base_fd.base, &err, false); -} - -/* - * Our async connect callback - */ -static void tcp_client_on_connect (struct transport_fd *fd, short what, void *arg) -{ - struct tcp_client *client = arg; - error_t err; - err_t tmp; - - // XXX: timeouts - (void) what; - - // read socket error code - if (tcp_sock_error(client->base_trans.base_fd.fd, &err)) - goto error; - - // did the connect fail? - if (ERROR_EXTRA(&err)) - JUMP_SET_ERROR(&err, ERR_CONNECT); - - // done, success - return tcp_client_connect_done(client, NULL); - -error: - // close the socket - if ((tmp = transport_fd_close(fd))) - log_warn("error closing socket after connect error: %s", error_name(tmp)); - - // log a warning - log_warn_error(&err, "connect to %s failed", "???"); - - // try the next one or fail completely - if (tcp_client_connect_continue(client, &err)) - tcp_client_connect_done(client, &err); -} - -err_t tcp_client_connect_async (struct tcp_client *client, const char *hostname, const char *service, error_t *err) -{ - // do the resolving - if (resolve_addr(&client->rr, hostname, service, SOCK_STREAM, 0, err)) - return ERROR_CODE(err); - - // start connecting with the first result - if (tcp_client_connect_continue(client, err)) - goto error; - - // ok - return SUCCESS; - -error: - // cleanup - resolve_result_deinit(&client->rr); - - return ERROR_CODE(err); -} - -void tcp_client_deinit (struct tcp_client *client) -{ - // cleanup our stuff - resolve_result_deinit(&client->rr); - - // deinit lower transport - tcp_transport_deinit(&client->base_trans); -} - -/* - * Deinit and free, not using the transport interface - */ -static void tcp_client_destroy (struct tcp_client *client) -{ - tcp_client_deinit(client); - - free(client); -} - -/* - * Public interface - */ -err_t tcp_connect (const struct transport_info *info, transport_t **transport_ptr, - const char *host, const char *service, error_t *err) -{ - struct tcp_client *client; - - // alloc - if ((client = calloc(1, sizeof(*client))) == NULL) - return ERR_CALLOC; - - // init transport - transport_init(&client->base_trans.base_fd.base, &tcp_client_type, info); - - // init our state - tcp_client_init(client); - - // begin connect - if (tcp_client_connect_async(client, host, service, err)) - goto error; - - // good - *transport_ptr = &client->base_trans.base_fd.base; - - return 0; - -error: - // cleanup - tcp_client_destroy(client); - - // return error code - return ERROR_CODE(err); -} - diff -r 5229a5d098b2 -r cefec18b8268 src/tcp_internal.h --- a/src/tcp_internal.h Thu May 28 00:35:02 2009 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,170 +0,0 @@ -#ifndef TCP_INTERNAL_H -#define TCP_INTERNAL_H - -/** - * @file - * - * Internal TCP interface for implementations - */ -#include "tcp.h" -#include "resolve.h" -#include "transport_fd.h" -#include "transport_internal.h" -#include "service_internal.h" -#include "error.h" - -#include -#include - -/** - * Create a new socket() using the given addr's family/socktype/protocol and return it. - * - * In case of errors, this returns -err_t - * - * @param addr the addrinfo to create the socket for - * @param err returned error info - * @return new fd on success, -err_t on error - */ -int tcp_sock_create (const struct addrinfo *addr, error_t *err); - -/** - * Return the socket's current error code via err->extra. - * - * In case getting the socket error code itself fails, this will return normal error code/info. - * - * Otherwise, this will return SUCCESS, with the errno value stored in err->extra. - */ -err_t tcp_sock_error (evutil_socket_t sock, error_t *err); - - -/** - * TCP transport type - */ -extern const struct transport_type tcp_transport_type; - -/** - * Base TCP transport - * - * XXX: currently just the same as transport_fd, but this will probably change - */ -struct tcp_transport { - /** Base FD-based implementation */ - struct transport_fd base_fd; -}; - -/** - * Initialize the tcp_transport state. - * - * This initializes the transport_fd base using the global sock_ctx::ev_base and the given socket. - */ -void tcp_transport_init (struct tcp_transport *trans, evutil_socket_t sock); - -/** - * Create a new tcp_transport with the given sock. - * - * For convenience, this will also make the sock nonblocking. - * - * In case of errors, this will the socket. - * - * @param trans_ptr returned tcp_transport - * @param info the transport user settings - * @param sock the unused TCP socket - * @param err returned error info - */ -err_t tcp_transport_create (struct tcp_transport **trans_ptr, const struct transport_info *info, evutil_socket_t sock, error_t *err); - -/** - * The transport as now connected, this sets up the intitial user settings, and invokes the callback. - * - * XXX: this does an 'indirect' call to transport_connected(). - * - * @param err returned error info - */ -err_t tcp_transport_connected (struct tcp_transport *trans, error_t *err); - -/** - * Deinitialize the transport state, terminating the connection and releasing resources. - */ -void tcp_transport_deinit (struct tcp_transport *trans); - -/** - * Deinitialize and free the given tcp_transport - */ -void tcp_transport_destroy (struct tcp_transport *trans); - -/** - * TCP client transport type - */ -extern const struct transport_type tcp_client_type; - -/** - * TCP client state - */ -struct tcp_client { - /** Base transport stuff */ - struct tcp_transport base_trans; - - /** The resolver lookup result for the async connect process */ - struct resolve_result rr; -}; - -/** - * Initialize the tcp_client state - */ -void tcp_client_init (struct tcp_client *client); - -/** - * Attempt to connect asyncronously to the given hostname/service. Once a connection has been established, this will - * call transport_connected(), so you can register transport_methods::_connected if layering on top of TCP. - * - * In case of errors while starting the async connect process, an error code will be returned. If the connect fails - * later on, transport_connected() will be called with the error code. - * - * The sock must have been initialized using sock_tcp_init(). - * - * @param client the unconnected TCP client socket to connect with - * @param hostname the hostname to resolve - * @param service the service to connect to - * @param err returned error info for immediate errors - */ -err_t tcp_client_connect_async (struct tcp_client *client, const char *hostname, const char *service, error_t *err); - -/** - * Deinitialize the tcp_client's state, including the tcp_transport state. - */ -void tcp_client_deinit (struct tcp_client *client); - - - -/** - * TCP service type - */ -extern const struct service_type tcp_server_type; - -/** - * TCP service state - */ -struct tcp_server { - /** Base service state */ - struct service base_service; - - /** The input event with our listen() socket */ - struct event *ev; -}; - -/** - * The listen() backlog - */ -#define TCP_SERVER_BACKLOG 5 - -/** - * Open the listening socket on the given interface/service. - */ -err_t tcp_server_listen (struct tcp_server *serv, const char *interface, const char *service, error_t *err); - -/** - * Release the tcp_server's state, and cleanup the struct. - */ -void tcp_server_deinit (struct tcp_server *serv); - -#endif /* TCP_INTERNAL_H */ diff -r 5229a5d098b2 -r cefec18b8268 src/tcp_server.c --- a/src/tcp_server.c Thu May 28 00:35:02 2009 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,260 +0,0 @@ -#include "tcp_internal.h" -#include "sock_internal.h" -#include "log.h" - -#include - -/* - * Service methods - */ -void tcp_server__deinit (service_t *service) -{ - struct tcp_server *serv = service_check(service, &tcp_server_type); - - tcp_server_deinit(serv); -} - -/* - * Service type - */ -const struct service_type tcp_server_type = { - .base_type = { - .parent = &service_type_type, - }, - .methods = { - .deinit = tcp_server__deinit, - }, -}; - -/* - * We got a new client, build a transport for it and give it to the user - */ -static err_t tcp_server_client (struct tcp_server *serv, evutil_socket_t sock, error_t *err) -{ - struct tcp_transport *trans; - - // create a new transport for it, this also makes it nonblocking - if (tcp_transport_create(&trans, &serv->base_service.info.trans_info, sock, err)) - goto error; - - // make it connected - // this will call transport_callbacks::on_connect, which is all the user needs - if (tcp_transport_connected(trans, err)) - goto error; - - // ok - return SUCCESS; - -error: - // cleanup - if (trans) - tcp_transport_destroy(trans); - - return ERROR_CODE(err); -} - -/* - * Libevent callback - */ -static void tcp_server_on_accept (evutil_socket_t sock, short what, void *arg) -{ - struct tcp_server *serv = arg; - evutil_socket_t client_sock; - error_t err; - - (void) what; - - // accept as a new client connection - if ((client_sock = accept(sock, NULL, NULL)) < 0 && errno != EAGAIN) - JUMP_SET_ERROR_ERRNO(&err, ERR_ACCEPT); - - // spurious read event? - if (client_sock < 0) - return; - - // handle it - if (tcp_server_client(serv, client_sock, &err)) - goto error; - - // ok - return; - -error: - if (client_sock >= 0) - EVUTIL_CLOSESOCKET(client_sock); - - // faaail - service_error(&serv->base_service, &err); -} - -/* - * Attempts to construct a listen()'d socket with the given addr, and return it - * - * @param addr the addrinfo to try and create a socket for - * @param err returned error info - * @return listening socket, or -err_t on error - */ -static int tcp_server_sock_addr (struct addrinfo *addr, error_t *err) -{ - evutil_socket_t sock; - - // create the sock - if ((sock = tcp_sock_create(addr, err)) < 0) - goto error; - - // bind it - if (bind(sock, addr->ai_addr, addr->ai_addrlen) < 0) - JUMP_SET_ERROR_ERRNO(err, ERR_BIND); - - // listen - if (listen(sock, TCP_SERVER_BACKLOG) < 0) - JUMP_SET_ERROR_ERRNO(err, ERR_LISTEN); - - // ok, valid socket - return sock; - -error: - if (sock >= 0) - // cleanup - EVUTIL_CLOSESOCKET(sock); - - return -ERROR_CODE(err); -} - -/* - * Construct a listen()'d socket with the given resolver result, and return it. - * - * @param rr the resolver lookup result to create a socket for - * @param err returned error info - * @return listening socket, or -err_t on error - */ -static int tcp_server_sock (struct resolve_result *rr, error_t *err) -{ - struct addrinfo *addr; - evutil_socket_t sock; - - // try each addrinfo - while ((addr = resolve_result_next(rr))) { - // attempt to construct given socket - if ((sock = tcp_server_sock_addr(addr, err)) < 0) - // log an informative error warning - log_warn_error(err, "%s", resolve_addr_text(addr)); - - else - // got a valid socket - break; - } - - if (sock >= 0) - // valid socket - return sock; - - else - // some error occured - return -ERROR_CODE(err); -} - -err_t tcp_server_listen (struct tcp_server *serv, const char *interface, const char *service, error_t *err) -{ - struct resolve_result rr; - evutil_socket_t sock; - - // get the global event_base - struct event_base *ev_base = _sock_stream_ctx.ev_base; - - // init the resolver - resolve_result_init(&rr); - - // resolve the interface/service - if (resolve_addr(&rr, interface, service, SOCK_STREAM, AI_PASSIVE, err)) - return ERROR_CODE(err); - - // create the socket - if ((sock = tcp_server_sock(&rr, err)) < 0) - goto error; - - // deinit lookup results - resolve_result_deinit(&rr); - - // make it nonblocking - if (evutil_make_socket_nonblocking(sock)) - JUMP_SET_ERROR_STR(err, ERR_MISC, "evutil_make_socket_nonblocking"); - - // construct event for the sock - if ((serv->ev = event_new(ev_base, sock, EV_READ | EV_PERSIST, tcp_server_on_accept, serv)) == NULL) - JUMP_SET_ERROR(err, ERR_EVENT_NEW); - - // add it - if (event_add(serv->ev, NULL)) - JUMP_SET_ERROR(err, ERR_EVENT_ADD); - - // ok - return SUCCESS; - -error: - // deinit results just to be sure - resolve_result_deinit(&rr); - - if (sock >= 0 && !serv->ev) - // need to close socket ourselves, because we couldn't register our event for it - EVUTIL_CLOSESOCKET(sock); - - // general cleanup - tcp_server_deinit(serv); - - return ERROR_CODE(err); -} - -void tcp_server_deinit (struct tcp_server *serv) -{ - if (serv->ev) { - // ignore errors - event_del(serv->ev); - - // ignore errors - close(event_get_fd(serv->ev)); - - // release event - event_free(serv->ev); - - // invalidate - serv->ev = NULL; - } -} - -static void tcp_server_destroy (struct tcp_server *serv) -{ - tcp_server_deinit(serv); - - free(serv); -} - -/* - * Public interface - */ -err_t tcp_listen (const struct service_info *info, service_t **service_ptr, - const char *interface, const char *service, error_t *err) -{ - struct tcp_server *serv; - - // alloc - if ((serv = calloc(1, sizeof(*serv))) == NULL) - return SET_ERROR(err, ERR_MEM); - - // init service - service_init(&serv->base_service, &tcp_server_type, info); - - // init ourselves - if (tcp_server_listen(serv, interface, service, err)) - goto error; - - // ok - *service_ptr = &serv->base_service; - - return SUCCESS; - -error: - tcp_server_destroy(serv); - - return ERROR_CODE(err); -} diff -r 5229a5d098b2 -r cefec18b8268 src/tcp_transport.c --- a/src/tcp_transport.c Thu May 28 00:35:02 2009 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,86 +0,0 @@ -#include "tcp_internal.h" -#include "sock_internal.h" - -#include - -/* - * Our transport_type - */ -const struct transport_type tcp_transport_type = { - .base_type = { - .parent = &transport_fd_type.base_type, - }, - .methods = { - .read = transport_fd__read, - .write = transport_fd__write, - .events = transport_fd__events, - .deinit = transport_fd__deinit, - }, -}; - -void tcp_transport_init (struct tcp_transport *trans, evutil_socket_t sock) -{ - struct event_base *ev_base = _sock_stream_ctx.ev_base; - - transport_fd_init(&trans->base_fd, ev_base, sock); -} - -err_t tcp_transport_create (struct tcp_transport **trans_ptr, const struct transport_info *info, evutil_socket_t sock, error_t *err) -{ - struct tcp_transport *trans; - - // alloc - if ((trans = calloc(1, sizeof(*trans))) == NULL) - JUMP_SET_ERROR(err, ERR_MEM); - - // init transport - transport_init(&trans->base_fd.base, &tcp_transport_type, info); - - // init ourselves - tcp_transport_init(trans, sock); - - // setup the socket? - if (sock >= 0) { - // make it non-blocking - if ((ERROR_CODE(err) = transport_fd_nonblock(&trans->base_fd, true))) - goto error; - } - - // ok - *trans_ptr = trans; - - return SUCCESS; - -error: - // cleanup - if (trans) - tcp_transport_deinit(trans); - else - EVUTIL_CLOSESOCKET(sock); - - return ERROR_CODE(err); -} - -err_t tcp_transport_connected (struct tcp_transport *trans, error_t *err) -{ - // set up for default transport event-based operation - if ((ERROR_CODE(err) = transport_fd_defaults(&trans->base_fd))) - return ERROR_CODE(err); - - // ok - transport_connected(&trans->base_fd.base, NULL, false); - - return SUCCESS; -} - -void tcp_transport_deinit (struct tcp_transport *trans) -{ - transport_fd_deinit(&trans->base_fd); -} - -void tcp_transport_destroy (struct tcp_transport *trans) -{ - tcp_transport_deinit(trans); - - free(trans); -} diff -r 5229a5d098b2 -r cefec18b8268 src/transport.c --- a/src/transport.c Thu May 28 00:35:02 2009 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,153 +0,0 @@ -#include "transport_internal.h" - -#include - -/** - * Our own object_type - */ -const struct object_type transport_type_type = { - .parent = NULL, -}; - -/* - * Internal API - */ -void transport_init (transport_t *transport, const struct transport_type *type, const struct transport_info *info) -{ - // init object - object_init(&transport->base_obj, &type->base_type); - - // store - if (info) - transport->info = *info; -} - -void* transport_check (transport_t *transport, const struct transport_type *type) -{ - // trip as a bug - assert(object_check(&transport->base_obj, &type->base_type)); - - // ok, cast via void* - return transport; -} - -void transport_connected (transport_t *transport, const error_t *err, bool direct) -{ - const struct transport_type *type = object_type(&transport->base_obj, &transport_type_type); - - if (direct || !type->methods._connected) { - // user callback - if (err) { - // connect failed - transport->info.cb_tbl->on_error(transport, err, transport->info.cb_arg); - - } else { - // update state - transport->connected = true; - - // connect succesfull - transport->info.cb_tbl->on_connect(transport, transport->info.cb_arg); - } - - } else { - // wrapper method - type->methods._connected(transport, err); - - } -} - -void transport_invoke (transport_t *transport, short what) -{ - // on_ready - if (what & TRANSPORT_READ && transport->info.cb_tbl->on_read) - transport->info.cb_tbl->on_read(transport, transport->info.cb_arg); - - // on_write - if (what & TRANSPORT_WRITE && transport->info.cb_tbl->on_write) - transport->info.cb_tbl->on_write(transport, transport->info.cb_arg); - -} - -void transport_error (transport_t *transport, const error_t *err) -{ - // update state - transport->connected = false; - - // invoke callback - transport->info.cb_tbl->on_error(transport, err, transport->info.cb_arg); -} - -/* - * Public API - */ -int transport_read (transport_t *transport, void *buf, size_t len, error_t *err) -{ - const struct transport_type *type = object_type(&transport->base_obj, &transport_type_type); - - // not readable - if (!type->methods.read) - return SET_ERROR(err, -1); - - // proxy off to method handler - if (type->methods.read(transport, buf, &len, err)) - return -ERROR_CODE(err); - - // return updated 'bytes-read' len - return len; -} - -int transport_write (transport_t *transport, const void *buf, size_t len, error_t *err) -{ - const struct transport_type *type = object_type(&transport->base_obj, &transport_type_type); - - // XXX: not writeable - if (!type->methods.write) - return SET_ERROR(err, -1); - - // proxy off to method handler - if (type->methods.write(transport, buf, &len, err)) - return -ERROR_CODE(err); - - // return updated 'bytes-written' len - return len; -} - -err_t transport_events (transport_t *transport, short mask) -{ - const struct transport_type *type = object_type(&transport->base_obj, &transport_type_type); - error_t err; - - // notify transport - if (type->methods.events) { - if (type->methods.events(transport, mask, &err)) - goto error; - } - - // update the event mask - transport->info.ev_mask = mask; - - // ok - return SUCCESS; - -error: - return ERROR_CODE(&err); -} - -void transport_set_callbacks (transport_t *transport, const struct transport_callbacks *cb_tbl, void *cb_arg) -{ - transport->info.cb_tbl = cb_tbl; - transport->info.cb_arg = cb_arg; -} - -void transport_destroy (transport_t *transport) -{ - const struct transport_type *type = object_type(&transport->base_obj, &transport_type_type); - - // destroy the transport-specific stuff - if (type->methods.deinit) - type->methods.deinit(transport); - - // then the transport itself - free(transport); -} - diff -r 5229a5d098b2 -r cefec18b8268 src/transport.h --- a/src/transport.h Thu May 28 00:35:02 2009 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,166 +0,0 @@ -#ifndef TRANSPORT_H -#define TRANSPORT_H - -/** - * @file - * - * Defines a intermediate-level (as opposed to high-level or low-level) API for connected streams of data, presumeably - * non-blocking ones. - */ -#include "error.h" - -/** - * Opaque transport state handle. - * - * Transports are reliable byte streams, connected with some endpoint over some medium. Common implementations are - * e.g. TCP, SSL or fifo transports (using the OS file/socket API). - * - * Transports can be connected or unconnected. For synchronous opens (e.g. fifo_open_read()), the transport returned - * will already be connected, meaning that the transport_callbacks::on_connect callback is unused. For async connects - * such as sock_tcp_connect()/sock_ssl_connect(), the transport returned is *not* connected, and you must wait for - * transport_callbacks::on_connect to be called before being able to send/recieve data on the transport. - * - * Once you have an opened transport, sending and receiving data is simple - just call transport_read()/transport_write(). - * These implement unbuffered I/O, so they may do partial reads/writes. In terms of the system read/write calls, the - * main difference is in the error return codes. On EOF, instead of returning zero, they return ERR_EOF (or - * ERR_WRITE_EOF for transport_write, for whoever knows what that means...). This means that when the underlying - * transport is unable to fufill the request due to lack of data/buffer space, these can return zero to signifiy - * something simliar to EAGAIN. - * - * The transport API also implements non-blocking/event-based operation (usually on top of libevent), although at a - * slightly different level than the normal select/poll API. Instead of the user asking the transport to notify for - * read/write after transport_read/transport_write return zero, the transport will take care of this itself. - * - * Specifically, the user can supply a mask of events they are currently interested in. By default, this should be the - * full TRANSPORT_READ | TRANSPORT_WRITE, as the transport will take care of managing events by itself. If you wish to - * e.g. throttle read/write, you may set a different event mask using transport_events(), which will prevent the - * relevant callback from being triggered. - * - * For reads, the transport maintains a persistent read event, and will always call on_read when data is available on - * the socket (i.e. normal select() semantics). If masked out using transport_events(), there should be no event - * activity on the transport (i.e. the fd read event is removed). - * - * For writes, the transport maintains a write event that is disabled by default. If transport_write() returns zero, it will - * become enabled *once*, and consequently trigger transport_callbacks::on_write *once*, after which you must call - * transport_write() to possibly enable it again. If masked out using transport_events(), transport_write() will not - * enable the write event, and any pending write event is cancelled. If masked back in using transport_events(), the - * write event will *not* be registered, so if you have pending data, do a transport_write() after enabling - * TRANSPORT_WRITE. - * - * Note that transport_write() returning fewer bytes than given will *not* enable the write event! You must call - * transport_write() until you have either written all of your data, or it returns zero! - */ -struct transport; - -/** - * @see transport - */ -typedef struct transport transport_t; - -/** - * User callbacks for transports - * - * @see transport - */ -struct transport_callbacks { - /** - * The transport is now connected - */ - void (*on_connect) (transport_t *transport, void *arg); - - /** - * Data is now available for reading from the transport - */ - void (*on_read) (transport_t *transport, void *arg); - - /** - * The transport has become writeable - */ - void (*on_write) (transport_t *transport, void *arg); - - /** - * An asynchronous error has occured. This is only called for errors that occur while being called directly from - * the underlying event loop, and never from inside an API function. - * - * You must call transport_destroy to release the transport. - */ - void (*on_error) (transport_t *transport, const error_t *err, void *arg); -}; - -/** - * Bitmask of available events - * - * @see transport - */ -enum transport_event { - TRANSPORT_READ = 0x01, - TRANSPORT_WRITE = 0x02, -}; - -/** - * User info required to build a transport - * - * @see transport - */ -struct transport_info { - /** The callbacks table */ - const struct transport_callbacks *cb_tbl; - - /** The callback context argument */ - void *cb_arg; - - /** Initial event mask using transport_event flags */ - short ev_mask; -}; - -/** - * Read a series of bytes from the transport into the given \a buf (up to \a len bytes). If succesfull, this returns - * the number of bytes read (which will be less than or equal to \a len). If the transport is nonblocking, and there is - * no data available, this returns zero, and need not be called again until transport_callbacks::on_read is invoked. - * - * On errors, this returns the negative error code, and more info via \a err. Note that as opposed to read(2), EOF is - * handled as an error, returning ERR_EOF. - * - * @param transport the transport state - * @param buf the buffer to read the bytes into - * @param len the number of bytes to read into the buffer - * @param err returned error info - * @return bytes read, zero if none available, -err_t - */ -int transport_read (transport_t *transport, void *buf, size_t len, error_t *err); - -/** - * Write a series of bytes from the given \a buf (containing \a len bytes) to the transport. If succesfull, this - * returns the number of bytes written (which may be less than \a len). If the transport is nonblocking, and the - * operation would have blocked, no data will be written, and zero is returned; in this case, the transport's write - * event is enabled (unless TRANSPORT_WRITE is masked out). - * - * On errors, this returns the negative error code, along with extended info via \a err. - * - * @param transport the transport state - * @param buf the buffer to write the bytes from - * @param len number of bytes to write - * @param err returned error info - * @return bytes written, zero if would have blocked, -err_t - */ -int transport_write (transport_t *transport, const void *buf, size_t len, error_t *err); - -/** - * Change the mask of enabled events. - */ -err_t transport_events (transport_t *transport, short mask); - -/** - * Install a new set of callback handlers, replacing the old ones. - */ -void transport_set_callbacks (transport_t *transport, const struct transport_callbacks *cb_tbl, void *cb_arg); - -/** - * Close and destroy the transport immediately, severing any established connection rudely. - * - * This will release all resources associated with the transport, including the transport itself, which must not be - * used anymore. - */ -void transport_destroy (transport_t *transport); - -#endif diff -r 5229a5d098b2 -r cefec18b8268 src/transport_fd.c --- a/src/transport_fd.c Thu May 28 00:35:02 2009 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,367 +0,0 @@ -#include "transport_fd.h" - -#include "log.h" - -#include -#include -#include - -/** - * Our libevent callback - */ -static void transport_fd_on_event (evutil_socket_t _fd, short ev_what, void *arg) -{ - struct transport_fd *fd = arg; - - (void) _fd; - - short what = 0; - - // build flags - if (ev_what & EV_READ) - what |= TRANSPORT_READ; - - if (ev_what & EV_WRITE) - what |= TRANSPORT_WRITE; - - // invoke user callback - fd->cb_func(fd, what, fd->cb_arg); -} - -/** - * Our transport_methods implementations - */ -err_t transport_fd__read (transport_t *transport, void *buf, size_t *len, error_t *err) -{ - struct transport_fd *fd = transport_check(transport, &transport_fd_type); - int ret; - - RESET_ERROR(err); - - // read(), and detect non-EAGAIN or EOF - if ((ret = read(fd->fd, buf, *len)) < 0 && errno != EAGAIN) - // unexpected error - RETURN_SET_ERROR_ERRNO(err, ERR_READ); - - else if (ret == 0) - // EOF - return SET_ERROR(err, ERR_EOF); - - - if (ret < 0) { - // EAGAIN -> zero bytes - *len = 0; - - } else { - // normal -> bytes read - *len = ret; - } - - // ok - return SUCCESS; -} - -err_t transport_fd__write (transport_t *transport, const void *buf, size_t *len, struct error_info *err) -{ - struct transport_fd *fd = transport_check(transport, &transport_fd_type); - int ret; - - RESET_ERROR(err); - - // write(), and detect non-EAGAIN or EOF - if ((ret = write(fd->fd, buf, *len)) < 0 && errno != EAGAIN) - // unexpected error - RETURN_SET_ERROR_ERRNO(err, ERR_WRITE); - - else if (ret == 0) - // EOF - return SET_ERROR(err, ERR_WRITE_EOF); - - - if (ret < 0) { - // EAGAIN -> zero bytes - *len = 0; - - if (transport->info.ev_mask & TRANSPORT_WRITE) - // enable the write event - if ((ERROR_CODE(err) = transport_fd_enable(fd, TRANSPORT_WRITE))) - return ERROR_CODE(err); - - } else { - // normal -> bytes read - *len = ret; - } - - return SUCCESS; -} - -err_t transport_fd__events (transport_t *transport, short ev_mask, error_t *err) -{ - struct transport_fd *fd = transport_check(transport, &transport_fd_type); - - short mask = 0; - - // enable read as requested - if (ev_mask & TRANSPORT_READ) - mask |= TRANSPORT_READ; - - // enable write if requested and it's currently enabled - if ((ev_mask & TRANSPORT_WRITE) && event_pending(fd->ev_write, EV_WRITE, NULL)) - mask |= TRANSPORT_WRITE; - - // set - return (ERROR_CODE(err) = transport_fd_events(fd, mask)); -} - -void transport_fd__deinit (transport_t *transport) -{ - struct transport_fd *fd = transport_check(transport, &transport_fd_type); - - transport_fd_deinit(fd); -} - -const struct transport_type transport_fd_type = { - .base_type = { - .parent = &transport_type_type, - }, - .methods = { - .read = transport_fd__read, - .write = transport_fd__write, - .events = transport_fd__events, - .deinit = transport_fd__deinit - } -}; - -/** - * Dummy callbacks - */ -void transport_fd_callback_user (struct transport_fd *fd, short what, void *arg) -{ - (void) arg; - - // proxy - transport_invoke(TRANSPORT_FD_BASE(fd), what); -} - -/** - * Function implementations - */ -void transport_fd_init (struct transport_fd *fd, struct event_base *ev_base, int _fd) -{ - // sanity-check - assert(!fd->fd); - assert(!fd->ev_read && !fd->ev_write); - assert(_fd == TRANSPORT_FD_INVALID || _fd >= 0); - - // initialize - fd->ev_base = ev_base; - fd->fd = _fd; - fd->cb_func = fd->cb_arg = NULL; -} - -err_t transport_fd_nonblock (struct transport_fd *fd, bool nonblock) -{ - assert(fd->fd != TRANSPORT_FD_INVALID); - - // XXX: maintain old flags? - - - // set new flags - if (fcntl(fd->fd, F_SETFL, nonblock ? O_NONBLOCK : 0) < 0) - return ERR_FCNTL; - - return SUCCESS; -} - -/** - * Install our internal event handler. - * - * The events should not already be set up. - * - * Cleans up partial events on errors - */ -err_t transport_fd_install (struct transport_fd *fd) -{ - assert(fd->fd != TRANSPORT_FD_INVALID); - assert(!fd->ev_read && !fd->ev_write); - - // create new events - if ((fd->ev_read = event_new(fd->ev_base, fd->fd, EV_READ | EV_PERSIST, transport_fd_on_event, fd)) == NULL) - goto err_event_add; - - if ((fd->ev_write = event_new(fd->ev_base, fd->fd, EV_WRITE, transport_fd_on_event, fd)) == NULL) - goto err_event_add; - - // ok - return SUCCESS; - -err_event_add: - // remove partial events - transport_fd_clear(fd); - - return ERR_EVENT_NEW; -} - -err_t transport_fd_setup (struct transport_fd *fd, transport_fd_callback_func cb_func, void *cb_arg) -{ - // requires a valid fd - assert(fd->fd != TRANSPORT_FD_INVALID); - - // store - fd->cb_func = cb_func; - fd->cb_arg = cb_arg; - - // install the event handlers? - if (!fd->ev_read || !fd->ev_write) - return transport_fd_install(fd); - else - return SUCCESS; -} - -err_t transport_fd_enable (struct transport_fd *fd, short mask) -{ - // just add the appropriate events - if (mask & TRANSPORT_READ && event_add(fd->ev_read, NULL)) - return ERR_EVENT_ADD; - - if (mask & TRANSPORT_WRITE && event_add(fd->ev_write, NULL)) - return ERR_EVENT_ADD; - - - return SUCCESS; -} - -err_t transport_fd_disable (struct transport_fd *fd, short mask) -{ - if (mask & TRANSPORT_READ && event_del(fd->ev_read)) - return ERR_EVENT_DEL; - - if (mask & TRANSPORT_WRITE && event_del(fd->ev_write)) - return ERR_EVENT_DEL; - - - return SUCCESS; -} - -err_t transport_fd_events (struct transport_fd *fd, short mask) -{ - err_t err; - - // enable/disable read - if (mask & TRANSPORT_READ) - err = event_add(fd->ev_read, NULL); - else - err = event_del(fd->ev_read); - - if (err) - return err; - - // enable/disable write - if (mask & TRANSPORT_WRITE) - err = event_add(fd->ev_write, NULL); - else - err = event_del(fd->ev_write); - - if (err) - return err; - - // ok - return SUCCESS; -} - -/** - * Remove our current ev_* events, but leave the cb_* intact. - */ -static void transport_fd_remove (struct transport_fd *fd) -{ - if (fd->ev_read) - event_free(fd->ev_read); - - if (fd->ev_write) - event_free(fd->ev_write); - - fd->ev_read = NULL; - fd->ev_write = NULL; -} - -void transport_fd_clear (struct transport_fd *fd) -{ - // remove the events - transport_fd_remove(fd); - - // clear the callbacks - fd->cb_func = fd->cb_arg = NULL; -} - -err_t transport_fd_defaults (struct transport_fd *fd) -{ - error_t err; - - // install the transport_invoke callback handler - if ((ERROR_CODE(&err) = transport_fd_setup(fd, transport_fd_callback_user, NULL))) - goto error; - - // enable read unless masked out - if (TRANSPORT_FD_BASE(fd)->info.ev_mask & TRANSPORT_READ) { - if ((ERROR_CODE(&err) = transport_fd_enable(fd, TRANSPORT_READ))) - goto error; - } - - // ok - return SUCCESS; - -error: - return ERROR_CODE(&err); -} - -err_t transport_fd_set (struct transport_fd *fd, int _fd) -{ - assert(_fd == TRANSPORT_FD_INVALID || _fd >= 0); - - // close the old stuff - transport_fd_close(fd); - - // set the new one - fd->fd = _fd; - - // do we have callbacks that we need to setup? - if (fd->cb_func) - return transport_fd_install(fd); - else - return SUCCESS; -} - -void transport_fd_invoke (struct transport_fd *fd, short what) -{ - // invoke - transport_invoke(TRANSPORT_FD_BASE(fd), what); -} - -err_t transport_fd_close (struct transport_fd *fd) -{ - int _fd = fd->fd; - - // remove any installed events - transport_fd_remove(fd); - - // invalidate fd - fd->fd = TRANSPORT_FD_INVALID; - - // close the fd - if (_fd != TRANSPORT_FD_INVALID && close(_fd)) - return ERR_CLOSE; - - - return SUCCESS; -} - -void transport_fd_deinit (struct transport_fd *fd) -{ - err_t tmp; - - // XXX: this might block - if ((tmp = transport_fd_close(fd))) - log_warn_err(tmp, "close"); - -} - diff -r 5229a5d098b2 -r cefec18b8268 src/transport_fd.h --- a/src/transport_fd.h Thu May 28 00:35:02 2009 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,173 +0,0 @@ -#ifndef TRANSPORT_FD_H -#define TRANSPORT_FD_H - -/** - * @file - * - * Support for transport implementations that use POSIX file descriptor streams. - * - * This provides the read/write methods, as well as functions to implement the event-based behaviour. - */ -#include "transport_internal.h" - -#include -#include - -// forward-declare -struct transport_fd; - -/** - * Our transport_type - */ -extern const struct transport_type transport_fd_type; - -/** - * Low-level callback - */ -typedef void (*transport_fd_callback_func) (struct transport_fd *fd, short what, void *arg); - -/** - * The fd-based transport implementation - */ -struct transport_fd { - /** Base transport state */ - struct transport base; - - /** Libevent base to use */ - struct event_base *ev_base; - - /** OS file descriptor */ - evutil_socket_t fd; - - /** IO events */ - struct event *ev_read, *ev_write; - - /** Low-level callback */ - transport_fd_callback_func cb_func; - - /** Callback context argument */ - void *cb_arg; - -}; - -/** - * Get a transport_t pointer from a transport_fd - */ -#define TRANSPORT_FD_BASE(tp_ptr) (&(tp_ptr)->base) - -/** - * Invalid OS FD - */ -#define TRANSPORT_FD_INVALID ((evutil_socket_t) -1) - -/** - * Implementation of transport_methods::read - */ -err_t transport_fd__read (transport_t *transport, void *buf, size_t *len, error_t *err); - -/** - * Implementation of transport_methods::write. - * - * If this gets EAGAIN, it will automatically enable the write event, unless masked out. - */ -err_t transport_fd__write (transport_t *transport, const void *buf, size_t *len, error_t *err); - -/** - * Implementation of transport_methods::events. - * - * For TRANSPORT_READ, this will simply apply enable/disable as given. - * - * For TRANSPORT_WRITE, the write event will only be enabled if given in the mask, *and* the ev_write event is currently - * active (via transport_fd_methods_write()); otherwise, the write event will not be enabled. - */ -err_t transport_fd__events (transport_t *transport, short mask, error_t *err); - -/** - * Implementation of transport_methods::deinit. - * - * This simply calls transport_fd_deinit(). - */ -void transport_fd__deinit (transport_t *transport); - -/** - * A transport_fd_callback_func that simply invokes the transport_callback user functions. - * - * Register with a NULL cb_arg. - */ -void transport_fd_callback_user (struct transport_fd *fd, short what, void *arg); - - - - -/** - * Initialize the transport_fd to use the given, connected fd, or TRANSPORT_FD_INVALID if we don't yet have an fd. - * - * It is an error to call this if the transport_fd already has an fd set - * - * @param fd the transport_fd state - * @param ev_base the libevent base to use - * @param _fd the OS file descriptor, or TRANSPORT_FD_INVALID - */ -void transport_fd_init (struct transport_fd *fd, struct event_base *ev_base, int _fd); - -/** - * Set the fd's nonblocking mode using fcntl. - */ -err_t transport_fd_nonblock (struct transport_fd *fd, bool nonblock); - -/** - * Set or replace the fd's event callback. The callback will not be enabled. - * - * The transport must have a valid fd bound to it. - */ -err_t transport_fd_setup (struct transport_fd *fd, transport_fd_callback_func cb_func, void *cb_arg); - -/** - * Enable the specified events, any of { TRANSPORT_READ, TRANSPORT_WRITE }. - */ -err_t transport_fd_enable (struct transport_fd *fd, short mask); - -/** - * Disable the specified events, any of { TRANSPORT_READ, TRANSPORT_WRITE }. - */ -err_t transport_fd_disable (struct transport_fd *fd, short mask); - -/** - * Set the enable/disable state of our events to the given mask. - */ -err_t transport_fd_events (struct transport_fd *fd, short mask); - -/** - * Remove any old event callback present, so it will not be called anymore. - * - * It is perfectly safe to call this without any callbacks installed. - */ -void transport_fd_clear (struct transport_fd *fd); - -/** - * Setup and enable the default event handlers for transport operation, that is, use transport_fd_callback_user as the - * callback and enable TRANSPORT_READ, unless masked out. - */ -err_t transport_fd_defaults (struct transport_fd *fd); - -/** - * Replace the old fd with a new one, maintaining any event callbacks set with transport_fd_callback. If any events were - * enabled before, they are not enabled anymore. - */ -err_t transport_fd_set (struct transport_fd *fd, int _fd); - -/** - * Close an opened fd, releasing all resources within our state. - */ -err_t transport_fd_close (struct transport_fd *fd); - -/** - * Deinitialize the transport_fd. - * - * This logs a warning if the close() fails. - * - * XXX: this may actually block, I think? SO_LINGER? - */ -void transport_fd_deinit (struct transport_fd *fd); - -#endif diff -r 5229a5d098b2 -r cefec18b8268 src/transport_internal.h --- a/src/transport_internal.h Thu May 28 00:35:02 2009 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,126 +0,0 @@ -#ifndef TRANSPORT_INTERNAL_H -#define TRANSPORT_INTERNAL_H - -/** - * @file - * - * The internal interface for transport implementations. - */ -#include "transport.h" -#include "object.h" - -#include - -/** - * The object_type for a transport_type - */ -extern const struct object_type transport_type_type; - -/** - * The definition of a transport type - */ -struct transport_type { - struct object_type base_type; - - /** - * Method table for implementation stuff. - * - * Note that it is the transport's resposibility to implement the behaviour described in transport.h - */ - struct transport_methods { - /** For transport_read() */ - err_t (*read) (transport_t *transport, void *buf, size_t *len, error_t *err); - - /** For transport_write() */ - err_t (*write) (transport_t *transport, const void *buf, size_t *len, error_t *err); - - /** - * The mask of event flags will be set to the given mask if this method is succesfull. - * - * The old mask is still available in transport::info::ev_mask. - */ - err_t (*events) (transport_t *transport, short mask, error_t *err); - - /** - * Release the transport's internal state, but not the transport itself. - * - * In other words, this should release everything inside the transport_t, but not free() the transport_t itself. - */ - void (*deinit) (transport_t *transport); - - /** - * Used by layered transports to handle transport_connected. - * - * If this is NULL, transport_connected will call the user callback directly, otherwise, it will proxy through this. - * - * The \a err param follows the same rules as for transport_connected() - NULL for success, error info otherwise. - * - * @param transport the transport state - * @param err error info if the connect failed - */ - void (*_connected) (transport_t *transport, const error_t *err); - } methods; -}; - -/** - * The base transport type - */ -struct transport { - struct object base_obj; - - /** User info */ - struct transport_info info; - - /** Are we connected? */ - bool connected; -}; - -/** - * Bind the given transport to the given type with the given user info. - * - * \a info may be given as NULL to not have any callbacks, but this will crash if any transport_* is called before - * transport_set_callbacks(). - * - * It is a bug to call this with a transport that is already bound. - */ -void transport_init (transport_t *transport, const struct transport_type *type, const struct transport_info *info); - -/** - * Check the type of the transport, and return the transport as a void* suitable for casting to the appropriate struct - * for the type, or any of its children. - * - * It is a bug to call this with a transport of a different type. - */ -void* transport_check (transport_t *transport, const struct transport_type *type); - -/** - * Mark the transport as connected, calling transport_methods::_connected if it exists and \a direct is not given, - * transport_callbacks::on_connected/transport_callbacks::on_error otherwise. - * - * If the connect succeeded, \a err should be given as NULL. If the connect failed, \a err should contain the error - * info. - * - * If called from the transport_methods::_connected method, pass in direct to avoid recursion. - * - * This sets the transport::connected flag before calling transport_callbacks::on_connected (i.e. directly) without any - * error set. - * - * XXX: implement proper layering of types by taking a transport_type arg and chaining down from there. - * - * @param transport the transport state - * @param err NULL for success, otherwise connect error code - * @param direct call the user callback directly, ignoring any method - */ -void transport_connected (transport_t *transport, const error_t *err, bool direct); - -/** - * Invoke the user callbacks based on the given TRANSPORT_* flags - */ -void transport_invoke (transport_t *transport, short what); - -/** - * Mark the transport as failed, calling transport_methods::on_error with the given error code. - */ -void transport_error (transport_t *transport, const error_t *err); - -#endif /* TRANSPORT_INTERNAL_H */ diff -r 5229a5d098b2 -r cefec18b8268 src/transport_test.c --- a/src/transport_test.c Thu May 28 00:35:02 2009 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,366 +0,0 @@ -#include "transport_test.h" -#include "transport_internal.h" - -#include -#include -#include -#include -#include - -/** - * Simple IO vector - */ -struct io_vec { - /** The buffer */ - char *buf; - - /** Buffer size */ - size_t len; -}; - -/** - * Simple vectored IO-buffer - */ -struct io_buf { - /** The array of buffer-vectors, {NULL}-terminated */ - struct io_vec *vecs; - - /** The number of io_vecs */ - size_t count; - - /** Current read/write vector */ - struct io_vec *read_vec, *write_vec; - - /** Offset into current vector */ - size_t off; -}; - -/** - * Forward-declare our transport_type - */ -extern const struct transport_type transport_test_type; - - -/** - * A dummy sock_stream implementation intended for testing purposes. - */ -struct transport_test { - /** The base transport stuff */ - struct transport base; - - /** The send/recieve buffers */ - struct io_buf send_buf, recv_buf; - - /** No more data is going to be added, return EOF once all the rest is consumed */ - bool eof; -}; - -/** - * Get a transport pointer from a transport_test pointer - */ -#define TRANSPORT_TEST_BASE(tp_ptr) (&(tp_ptr)->base) - -/** - * Grow buf->vecs if needed to ensure that buf->write_vec points to a valid io_vec - */ -static err_t io_buf_grow (struct io_buf *buf) -{ - size_t read_vec_offset = buf->read_vec ? (buf->read_vec - buf->vecs) : 0; - size_t write_vec_offset = buf->write_vec ? (buf->write_vec - buf->vecs) : 0; - struct io_vec *v; - struct io_vec *vecs_tmp = buf->vecs; - - // don't grow if not full - if (buf->vecs && buf->write_vec < buf->vecs + buf->count) - return SUCCESS; - - // new size - buf->count = buf->count * 2 + 1; - - // grow - if ((buf->vecs = realloc(buf->vecs, buf->count * sizeof(struct io_vec))) == NULL) { - // restore old value - buf->vecs = vecs_tmp; - - return ERR_CALLOC; - } - - // restore vec positions - buf->write_vec = buf->vecs + write_vec_offset; - buf->read_vec = buf->vecs + read_vec_offset; - - // zero new vecs - for (v = buf->write_vec; v < buf->vecs + buf->count; v++) - memset(v, 0, sizeof(*v)); - - // ok - return SUCCESS; -} - -/** - * Write some data to an io_buf, copying it. - */ -static err_t io_buf_write (struct io_buf *buf, const char *data, size_t len) -{ - error_t err; - - // ensure there's room - if ((ERROR_CODE(&err) = io_buf_grow(buf))) - goto error; - - // the vector to use - struct io_vec *vec = buf->write_vec; - - // allocate - if ((vec->buf = malloc(len)) == NULL) - JUMP_SET_ERROR(&err, ERR_MEM); - - // store - vec->len = len; - memcpy(vec->buf, data, len); - - // vec consumed - buf->write_vec++; - - // ok - return SUCCESS; - -error: - return ERROR_CODE(&err); -} - -/** - * Destroy the io_buf, freeing all resources. - * - * The io_buf must not be used anymore. - */ -static void io_buf_destroy (struct io_buf *buf) -{ - size_t i; - - // free the io_vec buffers - for (i = 0; i < buf->count; i++) { - free(buf->vecs[i].buf); - } - - // free the vector list - free(buf->vecs); -} - -/** - * transport_methods::read implementation. - */ -static err_t transport_test__read (transport_t *transport, void *buf_ptr, size_t *len, error_t *err) -{ - struct transport_test *tp = transport_check(transport, &transport_test_type); - struct io_buf *buf = &tp->recv_buf; - struct io_vec *vec = buf->read_vec; - - // EOF/nonblock if we're past the end of the last vector - if (!vec || vec == buf->vecs + buf->count || buf->off >= vec->len) { - if (!tp->eof) { - // wait for more to be fed in - *len = 0; - return SUCCESS; - - } else { - // EOF! - return SET_ERROR(err, ERR_EOF); - } - } - - // amount of data available in this iovec - size_t available = vec->len - buf->off; - - // amount to read - size_t to_read = *len; - - // trim down? - if (to_read > available) - to_read = available; - - // copy - memcpy(buf_ptr, vec->buf + buf->off, to_read); - - - if (to_read < available) { - // bytes still left in the vector - buf->off += to_read; - - } else { - // consumed the whole vector - // XXX: release data? - buf->read_vec++; - buf->off = 0; - } - - // update len - *len = to_read; - - // ok - return SUCCESS; -} - -/** - * transport_methods::write implementation. - */ -static err_t transport_test__write (transport_t *transport, const void *data, size_t *len, error_t *err) -{ - struct transport_test *tp = transport_check(transport, &transport_test_type); - - // write it out - // XXX: partial writes? - if ((ERROR_CODE(err) = io_buf_write(&tp->send_buf, data, *len))) - goto error; - - // ok - return SUCCESS; - -error: - return ERROR_CODE(err); -} - -static err_t transport_test__events (transport_t *transport, short mask, error_t *err) -{ - struct transport_test *tp = transport_check(transport, &transport_test_type); - - (void) tp; - (void) mask; - (void) err; - - // XXX: don't re-trigger anything - - return SUCCESS; -} - -static void transport_test__deinit (transport_t *transport) -{ - struct transport_test *tp = transport_check(transport, &transport_test_type); - - transport_test_destroy(tp); -} - -/* - * Our sock_stream_type - */ -const struct transport_type transport_test_type = { - .base_type = { - .parent = &transport_type_type, - }, - .methods = { - .read = transport_test__read, - .write = transport_test__write, - .events = transport_test__events, - .deinit = transport_test__deinit - }, -}; - -struct transport_test* transport_test_create (struct transport_info *info) -{ - struct transport_test *tp; - - // allocate - assert((tp = calloc(1, sizeof(*tp)))); - - // initialize base with our transport_type - transport_init(TRANSPORT_TEST_BASE(tp), &transport_test_type, info); - - // ok - return tp; -} - -transport_t* transport_test_cast (struct transport_test *tp) -{ - return TRANSPORT_TEST_BASE(tp); -} - -void transport_test_event (struct transport_test *tp, short what) -{ - // invoke, masking out as needed - // this won't do anything if all the bits are masked out - transport_invoke(TRANSPORT_TEST_BASE(tp), what & TRANSPORT_TEST_BASE(tp)->info.ev_mask); -} - -void transport_test_push_buf (struct transport_test *tp, const char *data, size_t len) -{ - // push it - assert(io_buf_write(&tp->recv_buf, data, len) == SUCCESS); - - // notify - transport_test_event(tp, TRANSPORT_READ); -} - -void transport_test_push_str (struct transport_test *tp, const char *str) -{ - // push it - transport_test_push_buf(tp, str, strlen(str)); -} - -void transport_test_push_fmt (struct transport_test *tp, const char *fmt, ...) -{ - char buf[TRANSPORT_TEST_FMT_MAX]; - size_t ret; - - // format - va_list vargs; va_start(vargs, fmt); - assert((ret = vsnprintf(buf, sizeof(buf), fmt, vargs)) <= sizeof(buf)); - va_end(vargs); - - // push it - transport_test_push_buf(tp, buf, ret); -} - -void transport_test_push_eof (struct transport_test *tp) -{ - // update state - tp->eof = true; - - transport_test_event(tp, TRANSPORT_READ); -} - -void transport_test_pull_buf (struct transport_test *tp, char **buf_ptr, size_t *len_ptr) -{ - struct io_buf *buf = &tp->send_buf; - size_t len = 0, i, off = 0; - char *out; - - // calculate total size - for (i = 0; i < buf->count; i++) { - len += buf->vecs[i].len; - } - - // alloc - assert((out = malloc(len))); - - // copy - for (i = 0; i < buf->count; i++) { - struct io_vec *vec = buf->vecs + i; - - memcpy(out + off, vec->buf, vec->len); - off += vec->len; - - // zero - free(vec->buf); vec->buf = NULL; - vec->len = 0; - } - - // update return - *buf_ptr = out; - *len_ptr = len; - - // update write_vec - buf->write_vec = buf->vecs; -} - -void transport_test_async_error (struct transport_test *tp, const error_t *err) -{ - transport_error(&tp->base, err); -} - -void transport_test_destroy (struct transport_test *tp) -{ - // free the buffers - io_buf_destroy(&tp->send_buf); - io_buf_destroy(&tp->recv_buf); -} - diff -r 5229a5d098b2 -r cefec18b8268 src/transport_test.h --- a/src/transport_test.h Thu May 28 00:35:02 2009 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,79 +0,0 @@ -#ifndef TRANSPORT_TEST_H -#define TRANSPORT_TEST_H - -/** - * @file - * - * Dummy transport implemention for local testing. - */ -#include "transport.h" - -/** - * The opaque transport state - */ -struct transport_test; - -/** - * Construct a new, empty, connected transport_test. - */ -struct transport_test* transport_test_create (struct transport_info *info); - -/** - * A transport_test is a valid transport, this performs the cast - */ -transport_t* transport_test_cast (struct transport_test *tp); - -/** - * Invoke the transport's user callbacks for the given event mask, unless masked out. - */ -void transport_test_event (struct transport_test *tp, short what); - -/** - * Adds a data buffer to the recieve buffer. - * - * The given data is copied. - * - * If events are enabled, they are triggered. - */ -void transport_test_push_buf (struct transport_test *tp, const char *buf, size_t len); - -/** - * Add a string to the recieve buffer using transport_test_push_buf() - */ -void transport_test_push_str (struct transport_test *tp, const char *str); - -/** - * Maximum length of a formatted string pushed - */ -#define TRANSPORT_TEST_FMT_MAX 4096 - -/** - * Add a formatted string to the recieve buffer - * - * @see TRANSPORT_TEST_FMT_MAX - */ -void transport_test_push_fmt (struct transport_test *tp, const char *fmt, ...); - -/** - * Set EOF on recv. - */ -void transport_test_push_eof (struct transport_test *tp); - -/** - * Get the send buffer contents as a single buffer, free() after use. - * - * This clears the send buffer, so this doesn't return the same data twice. - */ -void transport_test_pull_buf (struct transport_test *tp, char **buf_ptr, size_t *len_ptr); - -/** - * Send async error - */ -void transport_test_async_error (struct transport_test *tp, const error_t *err); - -/** - * Destroy the transport buffer, releasing any buffers we allocated ourself - */ -void transport_test_destroy (struct transport_test *tp); - -#endif /* TRANSPORT_TEST_H */