--- 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})
--- 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 <stdlib.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-#include <string.h>
-
-/**
- * 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);
-}
--- 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 <event2/event.h>
-
-/**
- * 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
--- 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 <lib/transport.h>
+#include <lib/line_proto.h>
#include "irc_queue.h"
#include "irc_line.h"
#include "irc_cmd.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 <lib/error.h>
+#include <lib/ssl.h>
#include <sys/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 <lib/line_proto.h>
+#include <lib/error.h>
#include <sys/queue.h>
#include <event2/event.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 */
--- 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" )
+);
--- 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: <perror>
+ ERR_READ, ///< read: <perror>
+ ERR_WRITE, ///< write: <perror>
+ ERR_WRITE_EOF, ///< write: EOF
+ ERR_FCNTL, ///< fcntl: <perror>
+ ERR_CLOSE, ///< close: <perror>
};
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 */
--- /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 <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <string.h>
+
+/**
+ * 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);
+}
--- /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 <event2/event.h>
+
+/**
+ * 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
--- /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 <string.h>
+#include <stdlib.h>
+#include <assert.h>
+
+/*
+ * 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);
+}
+
--- /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
--- /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 <lua5.1/lauxlib.h>
+
+/**
+ * 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);
+}
--- /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 <stdbool.h>
+
+/**
+ * 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
--- /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 <lua5.1/lauxlib.h>
+
+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;
+
+ }
+}
--- /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 <lua5.1/lua.h>
+
+// XXX: remove
+#include <lua5.1/lauxlib.h>
+
+/**
+ * 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
--- /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 <string.h>
+#include <stdint.h>
+#include <arpa/inet.h>
+
+/**
+ * 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);
+}
+
--- /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
--- /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 <sys/types.h>
+#include <sys/socket.h>
+#include <string.h>
+#include <stdio.h>
+
+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;
+}
--- /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 <netdb.h>
+
+/**
+ * Errors
+ */
+enum resolve_error_code {
+ ERR_RESOLVE_NONE,
+ ERR_RESOLVE_GETADDRINFO, ///< getaddrinfo: <gai_*>
+ 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
--- /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 <stdlib.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);
+}
--- /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
--- /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 */
--- /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 <assert.h>
+
+// 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;
+}
+
--- /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 <sys/types.h>
+#include <event2/event.h>
+
+/**
+ * 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
--- /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 */
--- /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 <assert.h>
+
+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);
+}
--- /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 <stdbool.h>
+
+/**
+ * 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
--- /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 <gnutls/x509.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+// XXX: remove
+#include "log.h"
+#include <assert.h>
+
+
+/**
+ * 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);
+}
+
+
--- /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/gnutls.h>
+
+/**
+ * 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
--- /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;
+}
+
+
--- /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
--- /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);
+}
+
--- /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 <event2/event.h>
+#include <event2/util.h>
+
+/**
+ * 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 */
--- /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 <unistd.h>
+
+/*
+ * 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);
+}
--- /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 <unistd.h>
+
+/*
+ * 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);
+}
--- /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 <assert.h>
+#include <stdlib.h>
+
+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);
+}
+
--- /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 <stddef.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_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
--- /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 <fcntl.h>
+#include <unistd.h>
+#include <assert.h>
+
+/**
+ * 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");
+
+}
+
--- /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 <event2/event.h>
+#include <stdbool.h>
+
+// 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
--- /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 <stdbool.h>
+
+/**
+ * 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
--- /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 <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <assert.h>
+
+/**
+ * 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);
+}
+
--- /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 */
--- 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 <string.h>
-#include <stdlib.h>
-#include <assert.h>
-
-/*
- * 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);
-}
-
--- 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 */
--- 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 <lua5.1/lauxlib.h>
-
-/**
- * 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);
-}
--- 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 <stdbool.h>
-
-/**
- * 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
--- 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 <spbot/nexus_lua.h>
+
/**
* Our lua wrapper for irc_chan
*/
--- 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 <stdlib.h>
-#include <string.h>
-
-/**
- * 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);
-}
-
-
--- 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 <lua5.1/lua.h>
-#include <lua5.1/lauxlib.h>
-
-// 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 */
--- 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 <lua5.1/lauxlib.h>
-
-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;
-
- }
-}
--- 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 <lua5.1/lua.h>
-
-// XXX: remove
-#include <lua5.1/lauxlib.h>
-
-/**
- * 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
--- 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 <string.h>
-#include <stdint.h>
-#include <arpa/inet.h>
-
-/**
- * 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);
-}
-
--- 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 */
--- 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 <sys/types.h>
-#include <sys/socket.h>
-#include <string.h>
-#include <stdio.h>
-
-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;
-}
--- 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 <netdb.h>
-
-/**
- * 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 */
--- 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);
-}
--- 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 */
--- 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 */
--- 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 <assert.h>
-
-// 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;
-}
-
--- 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 <sys/types.h>
-#include <event2/event.h>
-
-/**
- * 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
--- 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 */
--- /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 <stdlib.h>
+#include <string.h>
+
+/**
+ * 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);
+}
+
+
--- /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 <lua5.1/lua.h>
+#include <lua5.1/lauxlib.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 */
--- 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 <lib/sock.h>
#include <lib/log.h>
#include <stdlib.h>
--- 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 <stdlib.h>
@@ -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);
};
}
--- 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 <assert.h>
-
-/*
- * 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);
-}
--- 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 <stdbool.h>
-
-/**
- * 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 */
--- 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 <gnutls/x509.h>
-
-#include <stdlib.h>
-#include <string.h>
-#include <time.h>
-
-// XXX: remove
-#include "log.h"
-#include <assert.h>
-
-
-/**
- * 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);
-}
-
-
--- 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/gnutls.h>
-
-/**
- * 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 */
--- 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;
-}
-
-
--- 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
--- 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);
-}
-
--- 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 <event2/event.h>
-#include <event2/util.h>
-
-/**
- * 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 */
--- 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 <unistd.h>
-
-/*
- * 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);
-}
--- 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 <unistd.h>
-
-/*
- * 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);
-}
--- 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 <assert.h>
-
-/**
- * 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);
-}
-
--- 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
--- 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 <fcntl.h>
-#include <unistd.h>
-#include <assert.h>
-
-/**
- * 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");
-
-}
-
--- 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 <event2/event.h>
-#include <stdbool.h>
-
-// 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
--- 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 <stdbool.h>
-
-/**
- * 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 */
--- 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 <stdlib.h>
-#include <string.h>
-#include <stdio.h>
-#include <stdarg.h>
-#include <assert.h>
-
-/**
- * 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);
-}
-
--- 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 */