merge new-transport@a58ad50911fc
authorTero Marttila <terom@fixme.fi>
Mon, 04 May 2009 20:55:43 +0300
changeset 169 e6a1ce44aecc
parent 153 d35e7cb3a489 (current diff)
parent 168 a58ad50911fc (diff)
child 170 1b2f28e26eef
merge new-transport@a58ad50911fc
src/sock_fd.c
src/sock_fd.h
src/sock_fifo.c
src/sock_tcp.h
src/sock_test.c
src/sock_test.h
src/test.c
--- a/TODO	Fri Apr 24 23:01:34 2009 +0300
+++ b/TODO	Mon May 04 20:55:43 2009 +0300
@@ -1,8 +1,5 @@
 sock:
- * async SSL handshake
- * sock_openssl, or improve sock_gnutls
- * client certs for sock_ssl_connect
- * server cert validation for sock_ssl_connect
+ * sock_openssl (as sock_gnutls is kind of 'meh' somehow)
  * tests...
 
 irc_queue:
@@ -14,20 +11,21 @@
 
 irc_net:
  * reconnect, maybe cycling servers?
+ * proper case-insensitive lookups for channel names
 
 config:
- * user-defined types
+ * user-defined types (!)
  * return values
 
 console:
  * improve console_print further, to act more like rlwrap
 
 lua_console:
- * some kind of remote console
+ * some kind of remote console?
 
 irc_log:
  * recode to valid UTF8
 
 logwatch:
- * figure out how to handle overflow
+ * figure out how to handle message length overflow
 
--- a/src/CMakeLists.txt	Fri Apr 24 23:01:34 2009 +0300
+++ b/src/CMakeLists.txt	Mon May 04 20:55:43 2009 +0300
@@ -11,13 +11,14 @@
 
 # define our source code modules
 set (CORE_SOURCES error.c log.c str.c)
-set (SOCK_SOURCES sock.c sock_fd.c sock_tcp.c sock_gnutls.c sock_test.c sock_fifo.c line_proto.c)
+set (IO_SOURCES transport.c transport_fd.c sock.c sock_tcp.c sock_gnutls.c fifo.c line_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)
 set (CONSOLE_SOURCES console.c lua_console.c)
+file (GLOB _TEST_SOURCES "test/*.c")
 
-set (NEXUS_SOURCES nexus.c ${CORE_SOURCES} ${SOCK_SOURCES} ${IRC_SOURCES} ${LUA_SOURCES} ${CONSOLE_SOURCES} signals.c module.c config.c)
-set (TEST_SOURCES test.c ${CORE_SOURCES} ${SOCK_SOURCES} ${IRC_SOURCES})
+set (NEXUS_SOURCES nexus.c ${CORE_SOURCES} ${IO_SOURCES} ${IRC_SOURCES} ${LUA_SOURCES} ${CONSOLE_SOURCES} signals.c module.c config.c)
+set (TEST_SOURCES ${_TEST_SOURCES} ${CORE_SOURCES} ${IO_SOURCES} transport_test.c ${IRC_SOURCES})
 set (IRC_LOG_SOURCES modules/irc_log.c)
 set (LOGWATCH_SOURCES modules/logwatch.c modules/logwatch_source.c modules/logwatch_filter.c modules/logwatch_chan.c)
 
@@ -26,7 +27,7 @@
 set (NEXUS_LIBRARIES ${LibEvent_LIBRARIES} ${GnuTLS_LIBRARIES} ${MODULE_LIBRARIES} "readline" ${Lua51_LIBRARIES})
 
 # compiler flags
-set (CFLAGS "-Wall -Wextra -std=gnu99")
+set (CMAKE_C_FLAGS "-Wall -Wextra -std=gnu99")
 
 # add our binaries
 add_executable (nexus ${NEXUS_SOURCES})
@@ -42,11 +43,6 @@
 target_link_libraries (irc_log ${Evsql_LIBRARIES})
 target_link_libraries (logwatch ${PCRE_LIBRARIES})
 
-# global target properties
-set_target_properties (nexus test irc_log logwatch PROPERTIES
-    COMPILE_FLAGS   ${CFLAGS}
-)
-
 # nexus needs to export its symbols to be able to load modules
 set_target_properties (nexus PROPERTIES
     LINK_FLAGS      "--export-dynamic"
--- a/src/config.c	Fri Apr 24 23:01:34 2009 +0300
+++ b/src/config.c	Mon May 04 20:55:43 2009 +0300
@@ -67,6 +67,9 @@
 {
     // parse the value 
     switch (param->type) {
+        case CONFIG_INVALID:
+            RETURN_SET_ERROR_STR(err, ERR_CONFIG_TYPE, "invalid value for invalid type (too many values?)");
+
         case CONFIG_STRING:
             // simple!
             value->string = raw_value;
@@ -213,21 +216,6 @@
     return config_apply_opt(option, ctx, value, err);
 }
 
-/**
- * Look up an option's param by name, returning NULL if not found
- */
-static const struct config_param* config_get_param (const struct config_option *option, const char *name)
-{
-    const struct config_param *param;
-
-    for (param = option->params; param->name && param->type; param++)
-        if (strcmp(param->name, name) == 0)
-            return param;
-    
-    // not found
-    return NULL;
-}
-
 const struct config_value* config_get_value (const struct config_option *option, const struct config_value values[], const char *name)
 {
     const struct config_param *param;
@@ -263,10 +251,10 @@
     return (value = config_get_value(option, values, name)) ? value->irc_chan : NULL;
 }
 
-void* config_get_user (const struct config_option *option, const struct config_value values[], const char *name, const char *user_type)
+void* config_get_user (const struct config_option *option, const struct config_value values[], const char *name, const struct config_user_type *user_type)
 {
     const struct config_value *value;
 
-    return ((value = config_get_value(option, values, name)) && strcmp(value->user.type, user_type) == 0) ? value->user.ptr : NULL;
+    return ((value = config_get_value(option, values, name)) && value->user.type == user_type) ? value->user.ptr : NULL;
 }
 
--- a/src/config.h	Fri Apr 24 23:01:34 2009 +0300
+++ b/src/config.h	Mon May 04 20:55:43 2009 +0300
@@ -29,6 +29,14 @@
 };
 
 /**
+ * A CONFIG_USER type info
+ */
+struct config_user_type {
+    /** The name of the type */
+    const char *name;
+};
+
+/**
  * Structure to hold a value as defined by config_type
  */
 struct config_value {
@@ -46,9 +54,9 @@
         /** Value for CONFIG_USER */
         struct {
             /** The specific user type */
-            const char *type;
+            const struct config_user_type *type;
 
-            /** A pointer to the user type */
+            /** The pointer value */
             void *ptr;
         } user;
     };
@@ -65,7 +73,7 @@
     enum config_type type;
 
     /** The specific type for CONFIG_USER */
-    const char *user_type;
+    const struct config_user_type *user_type;
 
     /** Description */
     const char *description;
@@ -268,6 +276,6 @@
 
 const char* config_get_string (const struct config_option *option, const struct config_value values[], const char *name);
 struct irc_chan* config_get_irc_chan (const struct config_option *option, const struct config_value values[], const char *name);
-void* config_get_user (const struct config_option *option, const struct config_value values[], const char *name, const char *user_type);
+void* config_get_user (const struct config_option *option, const struct config_value values[], const char *name, const struct config_user_type *user_type);
 
 #endif
--- a/src/error.c	Fri Apr 24 23:01:34 2009 +0300
+++ b/src/error.c	Mon May 04 20:55:43 2009 +0300
@@ -27,7 +27,6 @@
     {   ERR_SOCKET,                         "socket",                                   ERR_EXTRA_ERRNO     },
     {   ERR_CONNECT,                        "connect",                                  ERR_EXTRA_ERRNO     },
     {   ERR_READ,                           "read",                                     ERR_EXTRA_ERRNO     },
-    {   ERR_READ_EOF,                       "read: EOF",                                ERR_EXTRA_NONE      },
     {   ERR_WRITE,                          "write",                                    ERR_EXTRA_ERRNO     },
     {   ERR_WRITE_EOF,                      "write: EOF",                               ERR_EXTRA_NONE      },
     {   ERR_FCNTL,                          "fcntl",                                    ERR_EXTRA_ERRNO     },
@@ -90,10 +89,19 @@
     {   ERR_LUA_ERR,                        "lua: error handling error",                ERR_EXTRA_STR       },
     {   ERR_LUA_FILE,                       "lua: error loading file",                  ERR_EXTRA_STR       },
     {   _ERR_INVALID,                       NULL,                                       0                   }
+
 }, _pcre_error_desc[] = {
     {   ERR_PCRE_COMPILE,                   "pcre_compile",                             ERR_EXTRA_STR       },
     {   ERR_PCRE_EXEC,                      "pcre_exec",                                ERR_EXTRA_STR       },
     {   _ERR_INVALID,                       NULL,                                       0                   }
+}, _general_error_desc[] = {
+    {   ERR_MISC,                           "miscellaneous error",                      ERR_EXTRA_STR       },
+    {   ERR_CMD_OPT,                        "invalid command line option",              ERR_EXTRA_STR       },
+    {   ERR_DUP_NAME,                       "duplicate name",                           ERR_EXTRA_STR       },
+    {   ERR_EOF,                            "EOF",                                      ERR_EXTRA_NONE      },
+    {   ERR_MEM,                            "memory allocation error",                  ERR_EXTRA_NONE      },
+    {   ERR_NOT_IMPLEMENTED,                "function not implemented",                 ERR_EXTRA_NONE      },
+    {   _ERR_INVALID,                       NULL,                                       0                   }
 };
 
 /**
@@ -108,6 +116,7 @@
     _module_error_desc,
     _lua_error_desc,
     _pcre_error_desc,
+    _general_error_desc,
     NULL
 };
 
@@ -136,9 +145,14 @@
 {
     const struct error_desc *desc;
     
-    // do we have an error_desc for it?
-    if ((desc = error_lookup_desc(code)))
+    if (!code)
+        // no error...
+        return "success";
+
+    else if ((desc = error_lookup_desc(code)))
+        // found an error_desc for it
         return desc->name;
+
     else
         // unknown
         return "[unknown]";
--- a/src/error.h	Fri Apr 24 23:01:34 2009 +0300
+++ b/src/error.h	Mon May 04 20:55:43 2009 +0300
@@ -52,8 +52,17 @@
     ERR_GETADDRINFO,
     ERR_GETADDRINFO_EMPTY, 
     
-    /** @see sock_error_code*/
+    /** socket/IO errors */
     _ERR_SOCK       = 0x000300,
+    ERR_SOCKET,         ///< socket(2) failed
+    ERR_CONNECT,        ///< connect(2) error - either direct or async
+    ERR_READ,           ///< read(2) error - will probably show up as an ERR_WRITE as well
+    ERR_WRITE,          ///< write(2) error - data was unsent, will probably show up as an ERR_READ as well
+    ERR_WRITE_EOF,      ///< write(2) gave EOF - zero bytes written
+    ERR_FCNTL,          ///< fcntl(2) failed
+    ERR_CLOSE,          ///< close(2) failed, some written data was probably not sent
+    ERR_GETSOCKOPT,     ///< getsockopt(2) failed
+    ERR_OPEN,           ///< open(2) failed
 
     /** @see sock_gnutls_error_code */
     _ERR_GNUTLS     = 0x000400,
@@ -62,6 +71,7 @@
     _ERR_LIBEVENT   = 0x000500,
     ERR_EVENT_NEW,
     ERR_EVENT_ADD,
+    ERR_EVENT_DEL,
 
     /** Evsql errors */
     _ERR_EVSQL      = 0x000600,
@@ -116,12 +126,18 @@
     /** str errors */
     _ERR_STR        = 0x000f00,
 
+    /** Transport errors */
+    _ERR_TRANSPORT  = 0x001000,
+
     /** General errors */
     _ERR_GENERAL    = 0xffff00,
-    ERR_CMD_OPT,
+    ERR_MISC,               ///< general error
+    ERR_CMD_OPT,            ///< invalid commandline option
     ERR_UNKNOWN,
-    ERR_DUP_NAME,
-
+    ERR_DUP_NAME,           ///< duplicate name
+    ERR_EOF,                ///< end of file
+    ERR_MEM,                ///< memory allocation error
+    ERR_NOT_IMPLEMENTED,    ///< function not implemented
 };
 
 /**
@@ -155,6 +171,11 @@
 };
 
 /**
+ * The public names
+ */
+typedef struct error_info error_t;
+
+/**
  * Translate an err_t into a function name.
  */
 const char *error_name (err_t code);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/fifo.c	Mon May 04 20:55:43 2009 +0300
@@ -0,0 +1,154 @@
+
+#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;
+};
+
+/**
+ * Get a sock_fd pointer from a sock_fifo pointer
+ */
+#define FIFO_FD(sock_ptr) (&(sock_ptr)->base_fd)
+
+/**
+ * Get a sock_base pointer from a sock_fifo pointer
+ */
+#define FIFO_TRANSPORT(sock_ptr) TRANSPORT_FD_BASE(FIFO_FD(sock_ptr))
+
+
+/**
+ * (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_FD(fifo), _fd)))
+        return ERROR_CODE(err);
+    
+    // use default transport event-based behaviour
+    if ((ERROR_CODE(err) = transport_fd_defaults(FIFO_FD(fifo))))
+        return ERROR_CODE(err);
+
+    // ok
+    return SUCCESS;
+}
+
+/**
+ * Destroy the fifo, releasing all resources
+ */
+static void fifo_destroy (struct fifo *fifo)
+{
+    // destroy base
+    transport_fd_destroy(FIFO_FD(fifo));
+
+    // release the path
+    free(fifo->path);
+}
+
+/**
+ * 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_methods_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_destroy (transport_t *transport)
+{
+    struct fifo *fifo = transport_check(transport, &fifo_type);
+    
+    fifo_destroy(fifo);
+}
+
+/*
+ * Our sock_stream_type
+ */
+const struct transport_type fifo_type = {
+    .parent                 = &transport_fd_type,
+    .methods                = {
+        .read               = fifo_read,
+        .write              = NULL,
+        .events             = transport_fd_methods_events,
+        .destroy            = _fifo_destroy,
+    },
+};
+
+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_TRANSPORT(fifo), &fifo_type, transport_info);
+    transport_fd_init(FIFO_FD(fifo), ev_base, TRANSPORT_FD_INVALID);
+    
+    // open the fifo
+    if (fifo_open(fifo, err))
+        goto error;
+
+    // ok
+    *transport_ptr = FIFO_TRANSPORT(fifo);
+
+    return SUCCESS;
+
+error:
+    // cleanup
+    fifo_destroy(fifo);
+
+    return ERROR_CODE(err);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/fifo.h	Mon May 04 20:55:43 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
--- a/src/irc_client.c	Fri Apr 24 23:01:34 2009 +0300
+++ b/src/irc_client.c	Mon May 04 20:55:43 2009 +0300
@@ -58,8 +58,8 @@
     };
 
     // combine _net_info and defaults to get net_info
-    if (_net_info->raw_sock)
-        RETURN_SET_ERROR_STR(err, ERR_IRC_NET_INFO, "raw_sock");
+    if (_net_info->transport)
+        RETURN_SET_ERROR_STR(err, ERR_IRC_NET_INFO, "transport");
 
     if ((net_info.network = _net_info->network) == NULL)
         RETURN_SET_ERROR_STR(err, ERR_IRC_NET_INFO, "network");
--- a/src/irc_conn.c	Fri Apr 24 23:01:34 2009 +0300
+++ b/src/irc_conn.c	Mon May 04 20:55:43 2009 +0300
@@ -215,7 +215,7 @@
     struct irc_conn *conn = arg;
 
     // EOF after quit?
-    if (ERROR_CODE(err) == ERR_READ_EOF && conn->quitting) {
+    if (ERROR_CODE(err) == ERR_EOF && conn->quitting) {
         // udpate states
         conn->registered = false;
         conn->quitting = false;
@@ -239,8 +239,13 @@
     .on_error       = &irc_conn_on_error,
 };
 
-err_t irc_conn_create (struct irc_conn **conn_ptr, struct sock_stream *sock, const struct irc_conn_callbacks *callbacks, 
-        void *cb_arg, struct error_info *err)
+// XXX: ugly hack to get at an event_base
+#include "sock_internal.h"
+
+struct event_base **ev_base_ptr = &_sock_stream_ctx.ev_base;
+
+err_t irc_conn_create (struct irc_conn **conn_ptr, transport_t *transport, const struct irc_conn_callbacks *callbacks, 
+        void *cb_arg, error_t *err)
 {
     struct irc_conn *conn;
 
@@ -264,11 +269,11 @@
         goto error;
 
     // create the line_proto, with our on_line handler
-    if (line_proto_create(&conn->lp, sock, IRC_LINE_MAX * 1.5, &irc_conn_lp_callbacks, conn, err))
+    if (line_proto_create(&conn->lp, transport, IRC_LINE_MAX * 1.5, &irc_conn_lp_callbacks, conn, err))
         goto error;
 
     // create the outgoing line queue
-    if (irc_queue_create(&conn->out_queue, conn->lp, err))
+    if (irc_queue_create(&conn->out_queue, *ev_base_ptr, conn->lp, err))
         goto error;
 
     // ok
@@ -287,7 +292,7 @@
 {
     // the line_proto
     if (conn->lp)
-        line_proto_release(conn->lp);
+        line_proto_destroy(conn->lp);
 
     // the queue
     if (conn->out_queue)
--- a/src/irc_conn.h	Fri Apr 24 23:01:34 2009 +0300
+++ b/src/irc_conn.h	Mon May 04 20:55:43 2009 +0300
@@ -12,7 +12,7 @@
 struct irc_conn;
 
 #include "error.h"
-#include "sock.h"
+#include "transport.h"
 #include "line_proto.h"
 #include "irc_queue.h"
 #include "irc_line.h"
@@ -173,13 +173,13 @@
  * via *err.
  *
  * @param conn_ptr returned new irc_conn structure
- * @param sock the socket connected to the IRC server
+ * @param transport connected transport
  * @param callbacks the high-level status callbacks, required
  * @param cb_arg opqaue context argument for callbacks
  * @param err returned error info
  */
-err_t irc_conn_create (struct irc_conn **conn_ptr, struct sock_stream *sock, const struct irc_conn_callbacks *callbacks, 
-        void *cb_arg, struct error_info *err);
+err_t irc_conn_create (struct irc_conn **conn_ptr, transport_t *transport, const struct irc_conn_callbacks *callbacks, 
+        void *cb_arg, error_t *err);
 
 /**
  * Destroy the irc_conn state, terminating any connection and releasing all resources.
--- a/src/irc_net.c	Fri Apr 24 23:01:34 2009 +0300
+++ b/src/irc_net.c	Mon May 04 20:55:43 2009 +0300
@@ -65,7 +65,7 @@
     log_err_info(err, "irc_conn failed");
     
     // tear down state
-    irc_net_disconnect(net, err);
+    irc_net_disconnect(net);
     
     // reconnect, either right away, or at the five-minute interval
     if (irc_net_connect(net, (time(NULL) - net->connected_ts > IRC_NET_RECONNECT_INTERVAL), err))
@@ -78,10 +78,11 @@
 static void irc_net_conn_quit (struct irc_conn *conn, void *arg)
 {
     struct irc_net *net = arg;
+    
+    (void) conn;
 
     // clean up the conn
-    irc_conn_destroy(conn);
-    net->conn = NULL;
+    irc_net_disconnect(net);
 
     // XXX: notify user
 }
--- a/src/irc_net.h	Fri Apr 24 23:01:34 2009 +0300
+++ b/src/irc_net.h	Mon May 04 20:55:43 2009 +0300
@@ -35,8 +35,8 @@
     /** Protocol registration info (nickname etc) */
     struct irc_conn_register_info register_info;
 
-    /** Raw socket to use, mainly for testing purposes */
-    struct sock_stream *raw_sock;
+    /** Alternatively, raw transport to use, mainly for testing purposes */
+    transport_t *transport;
 };
 
 /**
--- a/src/irc_net_connect.c	Fri Apr 24 23:01:34 2009 +0300
+++ b/src/irc_net_connect.c	Mon May 04 20:55:43 2009 +0300
@@ -1,16 +1,15 @@
 
 #include "irc_net_internal.h"
+#include "sock_tcp.h"
 #include "log.h"
 
 #include <time.h>
 #include <assert.h>
 
-void irc_net_disconnect (struct irc_net *net, struct error_info *err)
+void irc_net_disconnect (struct irc_net *net)
 {
     struct irc_chan *chan = NULL;
 
-    (void) err;
-
     // mark
     net->connected = false;
 
@@ -27,18 +26,18 @@
 }
 
 /**
- * We have succesfully established a connection to our server with the given sock, so create the irc_conn and bind it
- * to us.
+ * We have succesfully established a connection to our server with the given transport, so create the irc_conn and bind
+ * it to us.
  *
- * If this fails, this will clean up any partial state, including sock.
+ * If this fails, this will clean up any partial state, including \a transport.
  */
-static err_t irc_net_connected (struct irc_net *net, struct sock_stream *sock, struct error_info *err)
+static err_t irc_net_connected (struct irc_net *net, transport_t *transport, struct error_info *err)
 {
     // mark state
     net->connecting = false;
 
     // create the irc connection state
-    if (irc_conn_create(&net->conn, sock, &irc_net_conn_callbacks, net, err))
+    if (irc_conn_create(&net->conn, transport, &irc_net_conn_callbacks, net, err))
         goto error;
 
     // add our command handlers
@@ -57,8 +56,8 @@
 
 error:
     if (!net->conn) {
-        // cleanup sock ourselves
-        sock_stream_release(sock);
+        // cleanup transport ourselves
+        transport_destroy(transport);
 
     } else {
         // cleanup the partial stuff
@@ -74,26 +73,36 @@
  * Our sock_*_connect_async callback. If the connect ended up failing, then try and reconnect later. Otherwise, do
  * irc_net_connected().
  */
-static void irc_net_connect_cb (struct sock_stream *sock, struct error_info *conn_err, void *arg)
+static void irc_net_on_connect (transport_t *transport, void *arg)
 {
     struct irc_net *net = arg;
-    struct error_info err;
+    error_t err;
     
-    if (conn_err) {
-        // attempt reconnect later
-        log_err_info(conn_err, "connect failed");
-        
-        if (irc_net_connect(net, false, &err))
-            log_err_info(&err, "unable to reconnect");
+    // yay
+    if (irc_net_connected(net, transport, &err))
+        log_err_info(&err, "irc_net_connected");
+}
 
-    } else {
-        // yay
-        if (irc_net_connected(net, sock, &err))
-            log_err_info(&err, "irc_net_connected");
+static void irc_net_on_connect_error (transport_t *transport, const error_t *conn_err, void *arg)
+{
+    struct irc_net *net = arg;
+    error_t err;
 
-    }
+    // clean up
+    transport_destroy(transport);
+
+    // attempt reconnect later
+    log_err_info(conn_err, "connect failed");
+    
+    if (irc_net_connect(net, false, &err))
+        log_err_info(&err, "unable to reconnect");
 }
 
+static const struct transport_callbacks irc_net_transport_callbacks = {
+    .on_connect = irc_net_on_connect,
+    .on_error   = irc_net_on_connect_error,
+};
+
 /**
  * The low-level connect() implementation, connects based on irc_net::info, calling irc_net_connected/irc_net_reconnect
  * later if this succeeds.
@@ -101,20 +110,24 @@
 static err_t irc_net_do_connect (struct irc_net *net, struct error_info *err)
 {
     struct irc_net_info *info = &net->info;
-    struct sock_stream *sock = NULL;
+    struct transport_info transport_info = { &irc_net_transport_callbacks, net, TRANSPORT_READ | TRANSPORT_WRITE };
+    transport_t *transport = NULL;
 
     // sanity check
     assert(!net->connecting && !net->connected);
 
     // connect based on what's known
-    if (info->raw_sock) {
-        log_debug("connected using raw socket: %p", info->raw_sock);
+    if (info->transport) {
+        log_debug("connected using raw transport: %p", info->transport);
 
-        // direct sock_stream connection
-        sock = info->raw_sock;
+        // direct transport connection
+        transport = info->transport;
 
-        // then create the conn right away
-        if (irc_net_connected(net, sock, err))
+        // invalidate it from info since it will get destroyed later
+        info->transport = NULL;
+
+        // then create the transport right away
+        if (irc_net_connected(net, transport, err))
             goto error;
 
     } else if (info->ssl_cred) {
@@ -125,20 +138,23 @@
         log_debug("connecting to [%s]:%s using SSL", info->hostname, info->service);
 
         // connect
-        if (sock_ssl_connect_async(&sock, info->hostname, info->service, info->ssl_cred, &irc_net_connect_cb, net, err))
+        if (sock_ssl_connect(&transport_info, &transport, info->hostname, info->service, info->ssl_cred, err))
             goto error;
 
         net->connecting = true;
 
-    } else {
+    } else if (info->hostname || info->service) {
         log_debug("connecting to [%s]:%s", info->hostname, info->service);
             
         // begin async connect
-        if (sock_tcp_connect_async(&sock, info->hostname, info->service, &irc_net_connect_cb, net, err))
+        if (sock_tcp_connect(&transport_info, &transport, info->hostname, info->service, err))
             goto error;
         
         net->connecting = true;
 
+    } else {
+        RETURN_SET_ERROR_STR(err, ERR_MISC, "no connection info specified");
+
     }
 
     return SUCCESS;
@@ -163,9 +179,6 @@
         log_err_info(&err, "unable to reconnect");
 }
 
-// XXX: to get the ev_base
-#include "sock_internal.h"
-
 /**
  * Schedule a reconnection attempt in IRC_NET_RECONNECT_INTERVAL.
  */
@@ -206,6 +219,10 @@
     return ERROR_CODE(err);
 }
 
+// XXX: to get the ev_base
+#include "sock_internal.h"
+
+
 err_t irc_net_connect_init (struct irc_net *net, struct error_info *err)
 {
     // look up the ev_base 
--- a/src/irc_net_internal.h	Fri Apr 24 23:01:34 2009 +0300
+++ b/src/irc_net_internal.h	Mon May 04 20:55:43 2009 +0300
@@ -19,7 +19,7 @@
 /**
  * Destroy our irc_conn, and mark ourselves as disconnected.
  */
-void irc_net_disconnect (struct irc_net *net, struct error_info *err);
+void irc_net_disconnect (struct irc_net *net);
 
 /**
  * Fixed delay between reconnection attempts in seconds
--- a/src/irc_queue.c	Fri Apr 24 23:01:34 2009 +0300
+++ b/src/irc_queue.c	Mon May 04 20:55:43 2009 +0300
@@ -1,9 +1,6 @@
 #include "irc_queue.h"
 #include "log.h"
 
-// XXX: for ev_base
-#include "sock_internal.h"
-
 #include <stdlib.h>
 #include <string.h>
 #include <time.h>
@@ -128,7 +125,7 @@
     }
 }
 
-err_t irc_queue_create (struct irc_queue **queue_ptr, struct line_proto *lp, struct error_info *err)
+err_t irc_queue_create (struct irc_queue **queue_ptr, struct event_base *ev_base, struct line_proto *lp, struct error_info *err)
 {
     struct irc_queue *queue;
 
@@ -137,8 +134,7 @@
         return SET_ERROR(err, ERR_CALLOC);
 
     // create the timer event
-    // XXX: using the sock module ev_base
-    if ((queue->ev = evtimer_new(_sock_stream_ctx.ev_base, &irc_queue_timer, queue)) == NULL)
+    if ((queue->ev = evtimer_new(ev_base, &irc_queue_timer, queue)) == NULL)
         JUMP_SET_ERROR(err, ERR_EVENT_NEW);
 
     // initialize
--- a/src/irc_queue.h	Fri Apr 24 23:01:34 2009 +0300
+++ b/src/irc_queue.h	Mon May 04 20:55:43 2009 +0300
@@ -69,7 +69,7 @@
 /**
  * Create a new irc_queue for use with the given line_proto
  */
-err_t irc_queue_create (struct irc_queue **queue_ptr, struct line_proto *lp, struct error_info *err);
+err_t irc_queue_create (struct irc_queue **queue_ptr, struct event_base *ev_base, struct line_proto *lp, struct error_info *err);
 
 /**
  * Process a line, either sending it directly, or enqueueing it, based on the timer state.
--- a/src/line_proto.c	Fri Apr 24 23:01:34 2009 +0300
+++ b/src/line_proto.c	Mon May 04 20:55:43 2009 +0300
@@ -10,8 +10,8 @@
  * Our state
  */
 struct line_proto {
-    /* The sock_stream we read/write with */
-    struct sock_stream *sock;
+    /* The transport we read/write with */
+    transport_t *transport;
 
     /* The incoming/outgoing line buffer */
     char *in_buf, *out_buf;
@@ -36,9 +36,6 @@
     void *cb_arg;
 };
 
-// function prototypes
-static err_t line_proto_schedule_events (struct line_proto *lp, short what);
-
 /**
  * An error occured which we could not recover from; the line_proto should now be considered corrupt.
  *
@@ -54,97 +51,80 @@
 }
 
 /**
- * Our sock_stream on_read handler
+ * Our transport_callbacks::on_read handler
  */
-static void line_proto_on_read (struct sock_stream *sock, void *arg)
+static void line_proto_on_read (transport_t *transport, void *arg)
 {
     struct line_proto *lp = arg;
     char *line;
 
-    (void) sock;
+    (void) transport;
 
     // sanity-check
     assert(lp->tail_offset < lp->buf_len);
     
     do {
         // attempt to read a line
-        if (line_proto_recv(lp, &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);
-
-    // reschedule
-    if (line_proto_schedule_events(lp, EV_READ))
-        line_proto_set_error(lp);
 }
 
 /*
  * Signal for write
  */
-static void line_proto_on_write (struct sock_stream *sock, void *arg)
+static void line_proto_on_write (transport_t *transport, void *arg)
 {
     struct line_proto *lp = arg;
     int ret;
 
-    (void) sock;
+    (void) transport;
 
     // just flush
-    if ((ret = line_proto_flush(lp)) < 0) {
-        // faaaail
-        SET_ERROR(&lp->err, -ret);
-
+    if ((ret = line_proto_flush(lp)) < 0)
+        // faaail
         return line_proto_set_error(lp);
-    }
 }
 
-/*
- * Schedule our sock_stream callback
- */
-static err_t line_proto_schedule_events (struct line_proto *lp, short what) 
-{
-    // just use sock_stream's interface
-    if (sock_stream_event_enable(lp->sock, what))
-        RETURN_SET_ERROR_INFO(&lp->err, sock_stream_error(lp->sock));
-
-    return SUCCESS;
-}
-
-struct sock_stream_callbacks line_proto_sock_stream_callbacks = {
+// 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, struct sock_stream *sock, size_t buf_size,
-        const struct line_proto_callbacks *callbacks, void *cb_arg, struct error_info *err)
+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;
 
-    // allocate struct and buffers
+    // 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 = calloc(1, sizeof(*lp))) == NULL
-        ||  (lp->in_buf = malloc(buf_size)) == NULL
+            (lp->in_buf = malloc(buf_size)) == NULL
         ||  (lp->out_buf = malloc(buf_size)) == NULL
     )
         JUMP_SET_ERROR(err, ERR_CALLOC);
 
-    // store
-    lp->sock = sock;
-    lp->buf_len = buf_size;
-    lp->callbacks = *callbacks;
-    lp->cb_arg = cb_arg;
-
-    // initialize event-based stuff
-    if (
-            sock_stream_event_init(sock, &line_proto_sock_stream_callbacks, lp)
-        ||  line_proto_schedule_events(lp, EV_READ)
-    )
-        JUMP_SET_ERROR_INFO(err, &lp->err);
+    // 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;
@@ -153,8 +133,7 @@
 
 error:
     // cleanup the lp
-    if (lp)
-        line_proto_release(lp);
+    line_proto_destroy(lp);
 
     return ERROR_CODE(err);
 }
@@ -237,9 +216,8 @@
             return ERR_LINE_TOO_LONG;
         
         // otherwise, read more data
-        if ((ret = sock_stream_read(lp->sock, lp->in_buf + recv_offset, lp->buf_len - recv_offset)) < 0)
-            // store and return NULL on errors
-            RETURN_SET_ERROR_INFO(&lp->err, sock_stream_error(lp->sock));
+        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) {
@@ -271,11 +249,8 @@
         return -ERR_LINE_TOO_LONG;
     
     // try and write the line
-    if ((ret = sock_stream_write(lp->sock, line, len)) < 0) {
-        SET_ERROR_INFO(&lp->err, sock_stream_error(lp->sock));
-
+    if ((ret = transport_write(lp->transport, line, len, &lp->err)) < 0)
         return -ERROR_CODE(&lp->err);
-    }
 
     // length of the sent data
     ret_len = ret;
@@ -294,11 +269,7 @@
         // update offset
         lp->out_offset = trailing;
         
-        // register for EV_WRITE
-        if (line_proto_schedule_events(lp, EV_READ | EV_WRITE))
-            return -ERROR_CODE(&lp->err);
-
-        // buffered...
+        // buffered... transport should invoke on_write itself
         return 1;
 
     } else {
@@ -313,12 +284,11 @@
     int ret;
     size_t ret_len;
 
+    assert(lp->out_offset);
+
     // try and write the line
-    if ((ret = sock_stream_write(lp->sock, lp->out_buf, lp->out_offset)) < 0) {
-        SET_ERROR_INFO(&lp->err, sock_stream_error(lp->sock));
-
+    if ((ret = transport_write(lp->transport, lp->out_buf, lp->out_offset, &lp->err)) < 0)
         return -ERROR_CODE(&lp->err);
-    }
 
     ret_len = ret;
 
@@ -340,10 +310,6 @@
         lp->out_offset = remaining;
     }
 
-    // register for EV_WRITE
-    if (line_proto_schedule_events(lp, EV_READ | EV_WRITE))
-        return -ERROR_CODE(&lp->err);
-
     // ok
     return 1;
 }
@@ -354,15 +320,15 @@
     return &lp->err;
 }
 
-void line_proto_release (struct line_proto *lp)
+void line_proto_destroy (struct line_proto *lp)
 {
     // free buffers
     free(lp->in_buf);
     free(lp->out_buf);
 
     // socket?
-    if (lp->sock)
-        sock_stream_release(lp->sock);
+    if (lp->transport)
+        transport_destroy(lp->transport);
 
     // free the state itself
     free(lp);
--- a/src/line_proto.h	Fri Apr 24 23:01:34 2009 +0300
+++ b/src/line_proto.h	Mon May 04 20:55:43 2009 +0300
@@ -6,7 +6,7 @@
  *
  * Support for protocols that send/receive lines
  */
-#include "sock.h"
+#include "transport.h"
 #include "error.h"
 
 /**
@@ -30,20 +30,20 @@
  *
  * The incoming lines are buffered in a buffer of \a buf_size bytes. This imposes a maximum limit on the line length.
  *
- * Note that the given callbacks struct is copied.
+ * 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 sock the sock_stream to use
+ * @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, struct sock_stream *sock, size_t buf_size,
-        const struct line_proto_callbacks *callbacks, void *cb_arg, struct error_info *err);
+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 the socket recv() into our internal buffer. If a full line was received, a pointer to our internal bufffer is
+ * 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.
  *
@@ -64,6 +64,8 @@
 /**
  * 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);
 
@@ -73,10 +75,10 @@
 const struct error_info* line_proto_error (struct line_proto *lp);
 
 /**
- * Release any allocated buffers, and the underlying sock_stream.
+ * 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_release (struct line_proto *lp);
+void line_proto_destroy (struct line_proto *lp);
 
 #endif /* LINE_PROTO_H */
--- a/src/log.c	Fri Apr 24 23:01:34 2009 +0300
+++ b/src/log.c	Mon May 04 20:55:43 2009 +0300
@@ -124,7 +124,7 @@
     va_end(vargs);
 }
 
-void _log_err (enum log_level level, struct error_info *err, const char *func, const char *format, ...)
+void _log_err (enum log_level level, const error_t *err, const char *func, const char *format, ...)
 {
     va_list vargs;
 
--- a/src/log.h	Fri Apr 24 23:01:34 2009 +0300
+++ b/src/log.h	Mon May 04 20:55:43 2009 +0300
@@ -76,7 +76,7 @@
 /**
  * Log a message with the given level, appending the formatted error message
  */
-void _log_err (enum log_level level, struct error_info *err, const char *func, const char *format, ...)
+void _log_err (enum log_level level, const error_t *err, const char *func, const char *format, ...)
     __attribute__ ((format (printf, 4, 5)));
 
 /**
@@ -100,21 +100,21 @@
 /**
  * log_fatal + exit failure
  */
-#define FATAL(...) do { log_fatal(__VA_ARGS__); exit(EXIT_FAILURE); } while (0)
+#define FATAL(...) do { log_fatal(__VA_ARGS__); abort(); } while (0)
 
 /**
  * log_err + exit failure
  */
-#define FATAL_ERR(err_code, ...) do { _log_err_code(LOG_FATAL, err_code, __func__, __VA_ARGS__); exit(EXIT_FAILURE); } while (0)
+#define FATAL_ERR(err_code, ...) do { _log_err_code(LOG_FATAL, err_code, __func__, __VA_ARGS__); abort(); } while (0)
 
 /**
  * log_err_info + exit failure
  */
-#define FATAL_ERROR(err_info, ...) do { _log_err(LOG_FATAL, err_info, __func__, __VA_ARGS__); exit(EXIT_FAILURE); } while (0)
+#define FATAL_ERROR(err_info, ...) do { _log_err(LOG_FATAL, err_info, __func__, __VA_ARGS__); abort(); } while (0)
 
 /**
  * log_perr + exit failure
  */
-#define FATAL_PERROR(...) do { _log_perr(LOG_FATAL, __func__, __VA_ARGS__); exit(EXIT_FAILURE); } while (0)
+#define FATAL_PERROR(...) do { _log_perr(LOG_FATAL, __func__, __VA_ARGS__); abort(); } while (0)
 
 #endif /* LOG_H */
--- a/src/modules/logwatch.h	Fri Apr 24 23:01:34 2009 +0300
+++ b/src/modules/logwatch.h	Mon May 04 20:55:43 2009 +0300
@@ -5,8 +5,8 @@
  */
 #include <sys/queue.h>
 #include "../irc_chan.h"
-#include "../sock.h"
 #include "../line_proto.h"
+#include "../nexus.h"
 
 #include <event2/event.h>
 #include <pcre.h>
--- a/src/modules/logwatch_source.c	Fri Apr 24 23:01:34 2009 +0300
+++ b/src/modules/logwatch_source.c	Mon May 04 20:55:43 2009 +0300
@@ -1,4 +1,5 @@
 #include "logwatch.h"
+#include "../fifo.h"
 #include "../log.h"
 
 #include <stdlib.h>
@@ -42,23 +43,25 @@
 }
 
 /**
- * Initialize with the given sock
+ * Initialize with the given transport.
+ *
+ * In case of errors, this will free the source and transport.
  */
-static err_t logwatch_source_init (struct logwatch_source *source, struct logwatch *ctx, const char *name, struct sock_stream *stream, struct error_info *err)
+static err_t logwatch_source_init (struct logwatch_source *source, struct logwatch *ctx, const char *name, transport_t *transport, error_t *err)
 {
     // duplicate name?
     if (logwatch_source_lookup(ctx, name))
-        return SET_ERROR(err, ERR_DUP_NAME);
+        JUMP_SET_ERROR(err, ERR_DUP_NAME);
 
     // store
     source->ctx = ctx;
     
     // the name
     if ((source->name = strdup(name)) == NULL)
-        return SET_ERROR(err, ERR_STRDUP);
+        JUMP_SET_ERROR(err, ERR_STRDUP);
     
     // create the lp to wrap the sock
-    if (line_proto_create(&source->lp, stream, LOGWATCH_SOURCE_LINE_MAX, &lp_callbacks, source, err))
+    if (line_proto_create(&source->lp, transport, LOGWATCH_SOURCE_LINE_MAX, &lp_callbacks, source, err))
         goto error;
 
     // add to logwatch_sources
@@ -67,36 +70,35 @@
     // ok
     return SUCCESS;
 
-error:    
+error:
+    free(source);
+
     return ERROR_CODE(err);
 }
 
 struct logwatch_source* logwatch_open_fifo (struct logwatch *ctx, const char *path, struct error_info *err)
 {
     struct logwatch_source *source;
-    struct sock_stream *stream = NULL;
+    transport_t *transport = NULL;
 
     // alloc
     if ((source = calloc(1, sizeof(*source))) == NULL)
         JUMP_SET_ERROR(err, ERR_CALLOC);
     
     // open
-    if (fifo_open_read(&stream, path, err))
+    if (fifo_open_read(NULL, &transport, ctx->nexus->ev_base, path, err))
         goto error;
         
     // init
-    if (logwatch_source_init(source, ctx, path, stream, err))
-        goto error;
+    if (logwatch_source_init(source, ctx, path, transport, err))
+        return NULL;
 
     // ok
     return source;
 
-error:    
-    // cleanup
-    if (stream)
-        sock_stream_release(stream);
-    
+error:
     if (source)
+        // cleanup
         free(source);
 
     return NULL;
@@ -106,7 +108,7 @@
 {
     // release the line_proto
     if (source->lp)
-        line_proto_release(source->lp);
+        line_proto_destroy(source->lp);
         
     // remove from the list
     TAILQ_REMOVE(&source->ctx->sources, source, logwatch_sources);
--- a/src/signals.c	Fri Apr 24 23:01:34 2009 +0300
+++ b/src/signals.c	Mon May 04 20:55:43 2009 +0300
@@ -1,8 +1,9 @@
-#define _GNU_SOURCE
 
 #include "signals.h"
 #include "log.h"
 
+#define _GNU_SOURCE
+
 #include <string.h>
 #include <signal.h>
 #include <stdlib.h>
@@ -29,7 +30,7 @@
 
     (void) event;
 
-    log_info("caught %s: exiting", strsignal(signal));
+    log_info("caught %s: exiting", /* strsignal(signal) */ "xxx");
     
     if (event_base_loopexit(signals->ev_base, NULL))
         FATAL("event_base_loopexit");
--- a/src/sock.c	Fri Apr 24 23:01:34 2009 +0300
+++ b/src/sock.c	Mon May 04 20:55:43 2009 +0300
@@ -20,98 +20,3 @@
     return SUCCESS;
 }
 
-void sock_stream_init (struct sock_stream *sock, struct sock_stream_type *type, sock_stream_connect_cb cb_func, void *cb_arg)
-{
-    // be strict
-    assert(sock->type == NULL);
-
-    // store 
-    sock->type = type;
-    sock->conn_cb_func = cb_func;
-    sock->conn_cb_arg = cb_arg;
-}
-
-int sock_stream_read (struct sock_stream *sock, void *buf, size_t len)
-{
-    struct error_info *err = SOCK_ERR(sock);
-
-    // XXX: not readable
-    if (!sock->type->methods.read)
-        return -1;
-
-    // proxy off to method handler
-    if (sock->type->methods.read(sock, buf, &len, err))
-        return -ERROR_CODE(err);
-    
-    // return updated bytes-read len
-    return len;
-}
-
-int sock_stream_write (struct sock_stream *sock, const void *buf, size_t len)
-{
-    struct error_info *err = SOCK_ERR(sock);
-
-    // XXX: not writeable
-    if (!sock->type->methods.write)
-        return -1;
-
-    // proxy off to method handler
-    if (sock->type->methods.write(sock, buf, &len, err))
-        return -ERROR_CODE(err);
-
-    // return updated bytes-written len
-    return len;
-}
-
-err_t sock_stream_event_init (struct sock_stream *sock, const struct sock_stream_callbacks *callbacks, void *arg)
-{
-    // store
-    sock->cb_info = callbacks;
-    sock->cb_arg = arg;
-
-    // run method
-    return sock->type->methods.event_init(sock);
-}
-
-err_t sock_stream_event_enable (struct sock_stream *sock, short mask)
-{
-    // run method
-    return sock->type->methods.event_enable(sock, mask);
-}
-
-const struct error_info* sock_stream_error (struct sock_stream *sock)
-{
-    // return pointer
-    return SOCK_ERR(sock);
-}
-
-void sock_stream_invoke_callbacks (struct sock_stream *sock, short what)
-{
-    if (what & EV_READ && sock->cb_info->on_read)
-        sock->cb_info->on_read(sock, sock->cb_arg);
-
-    if (what & EV_WRITE && sock->cb_info->on_write)
-        sock->cb_info->on_read(sock, sock->cb_arg);
-}
-
-void sock_stream_invoke_conn_cb (struct sock_stream *sock, struct error_info *err, bool direct)
-{
-    if (!direct && sock->type->methods._conn_cb) {
-        // invoke indirectly
-        sock->type->methods._conn_cb(sock, err);
-
-    } else {
-        sock_stream_connect_cb cb_func = sock->conn_cb_func;
-
-        // invoke directly
-        sock->conn_cb_func = NULL;
-        cb_func(sock, err, sock->conn_cb_arg);
-        sock->conn_cb_arg = NULL;
-    }
-}
-
-void sock_stream_release (struct sock_stream *sock)
-{
-    sock->type->methods.release(sock);
-}
-
--- a/src/sock.h	Fri Apr 24 23:01:34 2009 +0300
+++ b/src/sock.h	Mon May 04 20:55:43 2009 +0300
@@ -4,54 +4,13 @@
 /**
  * @file
  *
- * Low-level socket-related functions
- *
- * XXX: not just sockets anymore
+ * General sock_* interface.
  */
 #include "error.h"
 #include <sys/types.h>
 #include <event2/event.h>
 
 /**
- * The generic stream socket handle
- */
-struct sock_stream;
-
-/**
- * Callback for connect_async completion notification. If err is NULL, the connection completed succesfully,
- * otherwise, it failed.
- */
-typedef void (*sock_stream_connect_cb) (struct sock_stream *sock, struct error_info *err, void *arg);
-
-/**
- * Async callbacks for socket operation
- */
-struct sock_stream_callbacks {
-    /** Socket is readable */
-    void (*on_read) (struct sock_stream *sock, void *arg);
-
-    /** Socket is writeable */
-    void (*on_write) (struct sock_stream *sock, void *arg);
-};
-
-/**
- * Socket function error codes
- */
-enum sock_error_code {
-    _ERR_SOCK_BEGIN = _ERR_SOCK,
-    ERR_SOCKET,         ///< socket(2) failed
-    ERR_CONNECT,        ///< connect(2) error - either direct or async
-    ERR_READ,           ///< read(2) error - will probably show up as an ERR_WRITE as well
-    ERR_READ_EOF,       ///< EOF on read(2)
-    ERR_WRITE,          ///< write(2) error - data was unsent, will probably show up as an ERR_READ as well
-    ERR_WRITE_EOF,      ///< write(2) gave EOF - zero bytes written
-    ERR_FCNTL,          ///< fcntl(2) failed
-    ERR_CLOSE,          ///< close(2) failed, some written data was probably not sent
-    ERR_GETSOCKOPT,     ///< getsockopt(2) failed
-    ERR_OPEN,           ///< open(2) failed
-};
-
-/**
  * 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.
@@ -59,106 +18,6 @@
  * @param ev_base the libevent base to use for events
  * @param err returned error info
  */
-err_t sock_init (struct event_base *ev_base, struct error_info *err);
-
-/**
- * A simple TCP connect to the given host/service, using getaddrinfo. The connected socket is returned via *sock_ptr. 
- * In case of errors, additional error information is stored in *err.
- *
- * @param sock_ptr the new sock_stream
- * @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 sock_tcp_connect (struct sock_stream **sock_ptr, const char *host, const char *service, struct error_info *err);
-
-/**
- * Start a non-blocking TCP connect to the given host/service. The socket will not yet be connected once the function
- * returns, but rather, the readyness of the socket will be indicated later using the given \a cb_func.
- *
- * Note that currently it is an error to call sock_stream_event_init before the cb_func has been called.
- *
- * XXX: blocking DNS resolution
- *
- * @param sock_ptr the new sock_stream
- * @param host the hostname to connect to
- * @param service the service name (i.e. port) to connect to
- * @param cb_func the callback used to handle the result of the async operation
- * @param cb_arg opaque context argument passed back to cb_func
- * @param err returned error info
- */
-err_t sock_tcp_connect_async (struct sock_stream **sock_ptr, const char *host, const char *service, 
-        sock_stream_connect_cb cb_func, void *cb_arg, struct error_info *err);
-
-/**
- * A read-only "socket" based on a FIFO, this provides nonblocking read operations by re-opening the FIFO on EOF.
- */
-err_t fifo_open_read (struct sock_stream **stream_ptr, const char *path, struct error_info *err);
-
-/**
- * Read a series of bytes from the socket 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 socket is nonblocking (i.e.
- * sock_stream_event_init() was set), and there is no data available, this returns zero, and one should use
- * sock_stream_event_enable() to wait for more data.
- *
- * On errors, this returns the negative err_t code, and the specific error information can be accessed using
- * sock_stream_error()..
- *
- * @param sock the socket to read the bytes from
- * @param buf the byte buffer to write the bytes into
- * @param len the number of bytes to read into the buffer
- * @return bytes read, zero if none available, -err_t
- */
-int sock_stream_read (struct sock_stream *sock, void *buf, size_t len);
-
-/**
- * Write a series of bytes from the given \a buf (containing \a len bytes) to the socket. If succesfull, this returns
- * the number of bytes written (which may be less than \a len if the OS write buffer was full). If the socket is
- * nonblocking (i.e. sock_stream_event_init() was set), and the operation would have blocked, no data was written, and
- * this returns zero, and one should use sock_stream_event_enable() to retry.
- *
- * On errors, this returns the negative err_t code, and the specific error information can be accessed using
- * sock_stream_error().
- *
- * @param sock the socket to write the bytes to
- * @param buf the byte buffer
- * @param len number of bytes to write
- * @return bytes written, zero if would have blocked, -err_t
- */
-int sock_stream_write (struct sock_stream *sock, const void *buf, size_t len);
-
-/**
- * Initialize event-based operation for this sock_stream. This will set the stream into nonblocking mode, and the given
- * callbacks will be fired once enabled using sock_stream_event_enable().
- *
- * Note that the callbacks struct isn't copied - it's used as-is-given.
- *
- * @param sock the socket to set up for nonblocking operation
- * @param callbacks the on_read/on_write callbacks to invoke
- * @param arg the context argument for the callbacks
- */
-err_t sock_stream_event_init (struct sock_stream *sock, const struct sock_stream_callbacks *callbacks, void *arg);
-
-/**
- * Enable some events for this sock, as set up earlier with event_init. Mask should contain EV_READ/EV_WRITE.
- *
- * The implementation of this is slightly hazy for complex protocols; this should only be used to map from
- * sock_stream_read/write to the corresponding sock_stream_callback. That is, if sock_stream_read returns zero, then
- * call event_enable(EV_READ), wherepon on_read will later be called. Other operations (such as calling
- * sock_stream_write with *different* data after it once returns zero) might result in errors.
- */
-err_t sock_stream_event_enable (struct sock_stream *sock, short mask);
-
-/**
- * Get current error_info for \a sock.
- */
-const struct error_info* sock_stream_error (struct sock_stream *sock);
-
-/**
- * Close and release the given socket, ignoring errors. It must not be used anymore after this.
- *
- * This is intended to be used to abort in case of errors, and does not close the connection cleanly.
- */
-void sock_stream_release (struct sock_stream *sock);
+err_t sock_init (struct event_base *ev_base, error_t *err);
 
 #endif
--- a/src/sock_fd.c	Fri Apr 24 23:01:34 2009 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,217 +0,0 @@
-#include "sock_fd.h"
-
-#include <fcntl.h>
-#include <unistd.h>
-#include <assert.h>
-
-void sock_fd_event_handler (evutil_socket_t fd, short what, void *arg) 
-{
-    struct sock_fd *sock = arg;
-
-    (void) fd;
-
-    // invoke appropriate callback
-    sock_stream_invoke_callbacks(SOCK_FD_BASE(sock), what);
-}
-
-err_t sock_fd_read (struct sock_stream *base_sock, void *buf, size_t *len, struct error_info *err)
-{
-    struct sock_fd *sock = SOCK_FROM_BASE(base_sock, struct sock_fd);
-    int ret;
-    
-    // read(), and detect non-EAGAIN or EOF
-    if ((ret = read(sock->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_READ_EOF);
-
-
-    if (ret < 0) {
-        // EAGAIN -> zero bytes
-        *len = 0;
-
-    } else {
-        // normal -> bytes read
-        *len = ret;
-    }
-
-    // ok
-    return SUCCESS;
-}
-
-err_t sock_fd_write (struct sock_stream *base_sock, const void *buf, size_t *len, struct error_info *err)
-{
-    struct sock_fd *sock = SOCK_FROM_BASE(base_sock, struct sock_fd);
-    int ret;
-    
-    // write(), and detect non-EAGAIN or EOF
-    if ((ret = write(sock->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;
-
-    } else {
-        // normal -> bytes read
-        *len = ret;
-    }
-
-    return SUCCESS;
-}
-
-err_t sock_fd_event_init (struct sock_stream *base_sock)
-{
-    struct sock_fd *sock = SOCK_FROM_BASE(base_sock, struct sock_fd);
-    err_t err;
-
-    // set nonblocking
-    if ((err = sock_fd_set_nonblock(sock, 1)))
-        return err;
-
-    // add ourselves as the event handler
-    if ((err = sock_fd_init_ev(sock, &sock_fd_event_handler, sock)))
-        return err;
-    
-    // done
-    return SUCCESS;
-}
-
-err_t sock_fd_event_enable (struct sock_stream *base_sock, short mask)
-{
-    struct sock_fd *sock = SOCK_FROM_BASE(base_sock, struct sock_fd);
-    
-    // implemented in sock_fd_add_event
-    return sock_fd_enable_events(sock, mask);
-}
-
-void sock_fd_init (struct sock_fd *sock, int fd)
-{
-    assert(!sock->ev_read && !sock->ev_write);
-
-    // initialize
-    sock->fd = fd;
-}
-
-err_t sock_fd_set_nonblock (struct sock_fd *sock, bool nonblock)
-{
-    // fcntl it
-    // XXX: maintain old flags?
-    if (fcntl(sock->fd, F_SETFL, nonblock ? O_NONBLOCK : 0) < 0)
-        RETURN_SET_ERROR_ERRNO(SOCK_FD_ERR(sock), ERR_FCNTL);
-
-    // ok
-    return SUCCESS;
-}
-
-err_t sock_fd_init_ev (struct sock_fd *sock, void (*ev_cb)(evutil_socket_t, short, void *), void *cb_arg)
-{
-    // require valid fd
-    assert(sock->fd >= 0);
-
-    // this is initialization
-    assert(sock->ev_read == NULL && sock->ev_write == NULL);
-
-    // store
-    sock->ev_cb = ev_cb;
-    sock->ev_arg = cb_arg;
-    
-    // create new event
-    if ((sock->ev_read = event_new(_sock_stream_ctx.ev_base, sock->fd, EV_READ, ev_cb, cb_arg)) == NULL)
-        return SET_ERROR(SOCK_FD_ERR(sock), ERR_EVENT_NEW);
-
-    if ((sock->ev_write = event_new(_sock_stream_ctx.ev_base, sock->fd, EV_WRITE, ev_cb, cb_arg)) == NULL)
-        return SET_ERROR(SOCK_FD_ERR(sock), ERR_EVENT_NEW);
-
-    // ok
-    return SUCCESS;
-}
-
-err_t sock_fd_enable_events (struct sock_fd *sock, short mask)
-{
-    // just add the appropraite events
-    if (mask & EV_READ && event_add(sock->ev_read, NULL))
-        return SET_ERROR(SOCK_FD_ERR(sock), ERR_EVENT_ADD);
- 
-    if (mask & EV_WRITE && event_add(sock->ev_write, NULL))
-        return SET_ERROR(SOCK_FD_ERR(sock), ERR_EVENT_ADD);
-    
-    // done
-    return SUCCESS;
-}
-
-static void sock_fd_free_ev (struct sock_fd *sock)
-{
-    if (sock->ev_read) {
-        event_free(sock->ev_read);
-
-        sock->ev_read = NULL;
-    }
-
-    if (sock->ev_write) {
-        event_free(sock->ev_write);
-        
-        sock->ev_write = NULL;
-    }
-}
-
-void sock_fd_deinit_ev (struct sock_fd *sock)
-{
-    sock_fd_free_ev(sock);
-    sock->ev_cb = NULL;
-    sock->ev_arg = NULL;
-}
-
-err_t sock_fd_set (struct sock_fd *sock, int fd)
-{
-    // close the old one?
-    if (sock->fd >= 0)
-        // XXX: warn on errors
-        close(sock->fd);
-    
-    // remove any old events
-    sock_fd_free_ev(sock);
-
-    // set the new one
-    sock->fd = fd;
-    
-    // restore them
-    if (sock->ev_cb)
-        return sock_fd_init_ev(sock, sock->ev_cb, sock->ev_arg);
-
-    // ok
-    return SUCCESS;
-}
-
-err_t sock_fd_close (struct sock_fd *sock)
-{
-    struct error_info *err = SOCK_FD_ERR(sock);
-    
-    // no errors yet
-    RESET_ERROR(err);
-
-    // must be connected
-    assert(sock->fd >= 0);
-
-    // kill any events
-    sock_fd_deinit_ev(sock);
-
-    // close the socket itself
-    if (close(sock->fd))
-        SET_ERROR_ERRNO(err, ERR_CLOSE);
-
-    // invalidate
-    sock->fd = -1;
-
-    return ERROR_CODE(err);
-}
-
--- a/src/sock_fd.h	Fri Apr 24 23:01:34 2009 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,111 +0,0 @@
-#ifndef SOCK_FD_H
-#define SOCK_FD_H
-/**
- * @file
- *
- * A generic sock_stream implementation for normal POSIX file descriptor based byte streams.
- */
-#include "sock_internal.h"
-#include <event2/event.h>
-#include <stdbool.h>
-
-/**
- * The fd-based sock_stream base implementation
- */
-struct sock_fd {
-    /** The base struct for sock_stream_* functions */
-    struct sock_stream base;
-    
-    /** The OS file descriptor */
-    int fd;
-
-    /** The callback and arg used for sock_fd_init_ev - required for sock_fd_set */
-    void (*ev_cb) (evutil_socket_t, short, void *);
-    void *ev_arg;
-
-    /** The IO events */
-    struct event *ev_read, *ev_write;
-
-};
-
-/**
- * Get a sock_stream pointer from a sock_fd
- */
-#define SOCK_FD_BASE(sock_ptr) (&(sock_ptr)->base)
-
-/**
- * Get the sock_stream.err pointer from a sock_fd
- */
-#define SOCK_FD_ERR(sock_ptr) SOCK_ERR(SOCK_FD_BASE(sock_ptr))
-
-
-
-/**
- * Callback suitable for use with sock_fd_init_ev, which just invoke's the sock_stream's callbacks as appropriate.
- */
-void sock_fd_event_handler (evutil_socket_t fd, short what, void *arg);
-
-/**
- * sock_stream_methods::read implementation.
- */
-err_t sock_fd_read (struct sock_stream *base_sock, void *buf, size_t *len, struct error_info *err);
-
-/**
- * sock_stream_methods::write implementation.
- */
-err_t sock_fd_write (struct sock_stream *base_sock, const void *buf, size_t *len, struct error_info *err);
-
-/**
- * sock_stream_methods::event_init implementation.
- */
-err_t sock_fd_event_init (struct sock_stream *base_sock);
-
-/**
- * sock_stream_methods::event_enable implementation.
- */
-err_t sock_fd_event_enable (struct sock_stream *base_sock, short mask);
-
-
-
-/**
- * Initialize the sock_fd with the given fd, or -1, if no valid fd yet.
- */
-void sock_fd_init (struct sock_fd *sock, int fd);
-
-/**
- * Set the socket's nonblock mode. This should not do anything (apart from an extraneous syscall) if non-blocking
- * mode is already set.
- */
-err_t sock_fd_set_nonblock (struct sock_fd *sock, bool nonblock);
-
-/**
- * Initialize sock_fd.ev_* to use the socket's fd with the given callback. The ev's are not activated yet.
- *
- * The sock_fd must *not* have any ev's set.
- */
-err_t sock_fd_init_ev (struct sock_fd *sock, void (*ev_cb) (evutil_socket_t, short, void *), void *arg);
-
-/**
- * event_add the specified ev_* events, so they are enabled and the callback will be executed.
- */
-err_t sock_fd_enable_events (struct sock_fd *sock, short mask);
-
-/**
- * The opposite of init_ev, this clears any set events, so that they can be re-initialized with init_ev.
- */
-void sock_fd_deinit_ev (struct sock_fd *sock);
-
-/**
- * Update a sock_fd's fd, also updating any events set with sock_fd_init_ev. If any events were enabled before, they
- * are not enabled anymore.
- */
-err_t sock_fd_set (struct sock_fd *sock, int fd);
-
-/**
- * Close an opened sock_fd, restoring it to a state suitable for sock_fd_init
- */
-err_t sock_fd_close (struct sock_fd *sock);
-
-
-
-#endif
--- a/src/sock_fifo.c	Fri Apr 24 23:01:34 2009 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,153 +0,0 @@
-/**
- * @file
- *
- * A read-only sock_stream implementation for linux fifo(7).
- */
-#include "sock_fd.h"
-
-#include <stdlib.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-#include <string.h>
-
-struct fifo {
-    /** The base fd operations */
-    struct sock_fd base_fd;
-    
-    /** The path to the fifo */
-    char *path;
-};
-
-/**
- * Get a sock_fd pointer from a sock_fifo pointer
- */
-#define FIFO_FD(sock_ptr) (&(sock_ptr)->base_fd)
-
-/**
- * Get a sock_base pointer from a sock_fifo pointer
- */
-#define FIFO_BASE(sock_ptr) SOCK_FD_BASE(FIFO_FD(sock_ptr))
-
-/**
- * Get the sock_stream.err pointer from a sock_fifo pointer
- */
-#define FIFO_ERR(sock_ptr) SOCK_ERR(FIFO_BASE(sock_ptr))
-
-
-
-/**
- * (re)open the fifo, closing it if already open, and keeping any event callbacks registered.
- */
-static err_t fifo_open (struct fifo *fifo, struct error_info *err)
-{
-    int fd;
-
-    // open(2) the path in non-blocking mode
-    // XXX: hardoded read-only
-    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) = sock_fd_set(FIFO_FD(fifo), fd)))
-        return ERROR_CODE(err);
-    
-    // ok
-    return SUCCESS;
-}
-
-/**
- * Destroy the fifo, releasing all resources
- */
-static void fifo_destroy (struct fifo *fifo)
-{
-    // close if open
-    if (FIFO_FD(fifo)->fd >= 0)
-        sock_fd_close(FIFO_FD(fifo));
-
-    // release the path
-    free(fifo->path);
-    free(fifo);
-}
-
-/**
- * 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 (struct sock_stream *base_sock, void *buf, size_t *len, struct error_info *err)
-{
-    struct fifo *fifo = SOCK_FROM_BASE(base_sock, struct fifo);
-
-    // passthru ERR_READ_EOF unless it's READ_EOF
-    if (sock_fd_read(base_sock, buf, len, err) != ERR_READ_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_release (struct sock_stream *base_sock)
-{
-    struct fifo *fifo = SOCK_FROM_BASE(base_sock, struct fifo);
-    
-    fifo_destroy(fifo);
-}
-
-/*
- * Our sock_stream_type
- */
-static struct sock_stream_type fifo_stream_type = {
-    .methods                = {
-        .read               = &fifo_read,
-        .write              = NULL,
-        .event_init         = &sock_fd_event_init,
-        .event_enable       = &sock_fd_event_enable,
-        .release            = &fifo_release,
-    },
-};
-
-err_t fifo_open_read (struct sock_stream **stream_ptr, const char *path, struct error_info *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
-    sock_stream_init(FIFO_BASE(fifo), &fifo_stream_type, NULL, NULL);
-    sock_fd_init(FIFO_FD(fifo), -1);
-
-    // open the fifo
-    if (fifo_open(fifo, err))
-        goto error;
-
-    // ok
-    *stream_ptr = FIFO_BASE(fifo);
-
-    return SUCCESS;
-
-error:
-    // cleanup
-    fifo_destroy(fifo);
-
-    return ERROR_CODE(err);
-}
--- a/src/sock_gnutls.c	Fri Apr 24 23:01:34 2009 +0300
+++ b/src/sock_gnutls.c	Mon May 04 20:55:43 2009 +0300
@@ -13,23 +13,24 @@
 #include <assert.h>
 
 /**
- * Register for events based on the session's gnutls_record_get_direction().
+ * Enable the TCP events based on the session's gnutls_record_get_direction().
  */
-static err_t sock_gnutls_ev_enable (struct sock_gnutls *sock, struct error_info *err)
+static err_t sock_gnutls_ev_enable (struct sock_gnutls *sock, 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(sock->session))) {
         case 0: 
             // read more data
-            sock_fd_enable_events(SOCK_GNUTLS_FD(sock), EV_READ); 
+            mask = TRANSPORT_READ;
             break;
         
         case 1:
             // write buffer full
-            sock_fd_enable_events(SOCK_GNUTLS_FD(sock), EV_WRITE);
+            mask = TRANSPORT_WRITE;
             break;
         
         default:
@@ -37,7 +38,11 @@
             RETURN_SET_ERROR_EXTRA(err, ERR_GNUTLS_RECORD_GET_DIRECTION, ret);
     }
     
-    // ok... wait
+    // do the enabling
+    if ((ERROR_CODE(err) = transport_fd_enable(SOCK_GNUTLS_FD(sock), mask)))
+        return ERROR_CODE(err);
+    
+
     return SUCCESS;
 }
 
@@ -72,7 +77,7 @@
  *
  * Based on the GnuTLS examples/ex-rfc2818.c
  */
-static err_t sock_gnutls_verify (struct sock_gnutls *sock, struct error_info *err)
+static err_t sock_gnutls_verify (struct sock_gnutls *sock, error_t *err)
 {
     unsigned int status;
     const gnutls_datum_t *cert_list;
@@ -138,7 +143,7 @@
  *
  * @return >0 for finished handshake, 0 for handshake-in-progress, -err_t for errors.
  */
-static int sock_gnutls_handshake (struct sock_gnutls *sock, struct error_info *err)
+static int sock_gnutls_handshake (struct sock_gnutls *sock, error_t *err)
 {
     int ret;
 
@@ -177,68 +182,70 @@
 }
 
 /**
- * Our SOCK_STREAM event handler. Drive the handshake if that's current, otherwise, invoke user callbacks.
- *
- * XXX: this is ugly. This sock_stream-level separation doesn't really work that well.
+ * Our transport_fd event handler. Drive the handshake if that's current, otherwise, invoke user callbacks.
  */
-static void sock_gnutls_event_handler (int fd, short what, void *arg)
+static void sock_gnutls_on_event (struct transport_fd *fd, short what, void *arg)
 {
     struct sock_gnutls *sock = arg;
-    struct error_info err;
+    error_t err;
 
     (void) fd;
+
+    // XXX: timeouts
     (void) what;
 
     // are we in the handshake cycle?
     if (sock->handshake) {
         RESET_ERROR(&err);
 
+        // perform the next handshake step
         if (sock_gnutls_handshake(sock, &err) == 0) {
-            // wait for the next handshake step
+            // handshake continues
         
-        } else if (SOCK_GNUTLS_BASE(sock)->conn_cb_func) {
+            // XXX: this state flag is completely wrong
+        } else if (SOCK_GNUTLS_TRANSPORT(sock)->connected) {
             // the async connect process has now completed, either succesfully or with an error
             // invoke the user connect callback directly with appropriate error
-            sock_stream_invoke_conn_cb(SOCK_GNUTLS_BASE(sock), ERROR_CODE(&err) ? &err : NULL, true);
+            transport_connected(SOCK_GNUTLS_TRANSPORT(sock), ERROR_CODE(&err) ? &err : NULL, true);
 
         } else {
-            // re-handshake completed, so continue with the sock_stream_callbacks, so the user can call sock_gnutls_read/write
-
             if (ERROR_CODE(&err))
-                // XXX: bad, since we can't report this directly... we need to let the user call _read/write, and get
-                // the error from there
-                log_warn_err(&err, "sock_gnutls_handshake failed");
-            
-            // continue where we left off
-            sock_stream_invoke_callbacks(SOCK_GNUTLS_BASE(sock), sock->ev_mask);
+                // the re-handshake failed, so this transport is dead
+                transport_error(SOCK_GNUTLS_TRANSPORT(sock), &err);
+        
+            else
+                // re-handshake completed, so continue with the transport_callbacks
+                transport_invoke(SOCK_GNUTLS_TRANSPORT(sock), what);
         }
 
     } else {
         // normal sock_stream operation
-        // gnutls might be able to proceed now, so ask user to try what didn't work before now, using the mask given to
-        // event_enable().
-        sock_stream_invoke_callbacks(SOCK_GNUTLS_BASE(sock), sock->ev_mask);
+        // gnutls might be able to proceed now, so invoke user callbacks
+        transport_invoke(SOCK_GNUTLS_TRANSPORT(sock), what);
     }
 }
 
-static err_t sock_gnutls_read (struct sock_stream *base_sock, void *buf, size_t *len, struct error_info *err)
+static err_t sock_gnutls_read (transport_t *transport, void *buf, size_t *len, error_t *err)
 {
-    struct sock_gnutls *sock = SOCK_FROM_BASE(base_sock, struct sock_gnutls);
+    struct sock_gnutls *sock = transport_check(transport, &sock_gnutls_type);
     int ret;
     
     // read gnutls record
-    ret = gnutls_record_recv(sock->session, buf, *len);
+    do {
+        ret = gnutls_record_recv(sock->session, buf, *len);
+
+    } while (ret == GNUTLS_E_INTERRUPTED);
     
     // errors
-    // XXX: E_INTERRUPTED, E_REHANDSHAKE?
+    // 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_READ_EOF);
+        return SET_ERROR(err, ERR_EOF);
 
 
-    // eagain?
+    // EAGAIN?
     if (ret < 0) {
         *len = 0;
 
@@ -251,20 +258,23 @@
     return SUCCESS;
 }
 
-static err_t sock_gnutls_write (struct sock_stream *base_sock, const void *buf, size_t *len, struct error_info *err)
+static err_t sock_gnutls_write (transport_t *transport, const void *buf, size_t *len, error_t *err)
 {
-    struct sock_gnutls *sock = SOCK_FROM_BASE(base_sock, struct sock_gnutls);
+    struct sock_gnutls *sock = transport_check(transport, &sock_gnutls_type);
     int ret;
  
     // read gnutls record
-    ret = gnutls_record_send(sock->session, buf, *len);
-    
+    do {
+        ret = gnutls_record_send(sock->session, buf, *len);
+   
+    } while (ret == GNUTLS_E_INTERRUPTED);
+
     // errors
     if (ret < 0 && ret != GNUTLS_E_AGAIN)
-        RETURN_SET_ERROR_EXTRA(err, ERR_GNUTLS_RECORD_RECV, ret);
+        RETURN_SET_ERROR_EXTRA(err, ERR_GNUTLS_RECORD_SEND, ret);
     
     else if (ret == 0)
-        return SET_ERROR(err, ERR_READ_EOF);
+        return SET_ERROR(err, ERR_WRITE_EOF);
 
 
     // eagain?
@@ -279,43 +289,21 @@
     return SUCCESS;
 }
 
-static err_t sock_gnutls_event_init (struct sock_stream *base_sock)
-{
-    struct sock_gnutls *sock = SOCK_FROM_BASE(base_sock, struct sock_gnutls);
-
-    (void) sock;
-
-    // already setup, ok
-    return SUCCESS;
-}
-
-static err_t sock_gnutls_event_enable (struct sock_stream *base_sock, short mask)
+static void _sock_gnutls_destroy (transport_t *transport)
 {
-    struct sock_gnutls *sock = SOCK_FROM_BASE(base_sock, struct sock_gnutls);
-    
-    // store the ev_mask. We don't care about it here, because we assume that event_enable is only called once read or
-    // write, respectively, return zero. This is really the only case we can handle with gnutls.
-    sock->ev_mask = mask;
+    struct sock_gnutls *sock = transport_check(transport, &sock_gnutls_type);
     
-    // then wait for the event
-    return sock_gnutls_ev_enable(sock, SOCK_GNUTLS_ERR(sock));
-}
-
-static void sock_gnutls_release (struct sock_stream *base_sock)
-{
-    struct sock_gnutls *sock = SOCK_FROM_BASE(base_sock, struct sock_gnutls);
-    
-    // DIEEEE
+    // die
     sock_gnutls_destroy(sock);
 }
 
 /**
  * Our sock_tcp-invoked connect handler
  */
-static void sock_gnutls_on_connect (struct sock_stream *base_sock, struct error_info *tcp_err)
+static void sock_gnutls__connected (transport_t *transport, const error_t *tcp_err)
 {
-    struct sock_gnutls *sock = SOCK_FROM_BASE(base_sock, struct sock_gnutls);
-    struct error_info err;
+    struct sock_gnutls *sock = transport_check(transport, &sock_gnutls_type);
+    error_t err;
 
     // trap errors to let the user handle them directly
     if (tcp_err)
@@ -325,7 +313,7 @@
     gnutls_transport_set_ptr(sock->session, (gnutls_transport_ptr_t) (long int) SOCK_GNUTLS_FD(sock)->fd);
 
     // add ourselves as the event handler
-    if ((ERROR_CODE(&err) = sock_fd_init_ev(SOCK_GNUTLS_FD(sock), &sock_gnutls_event_handler, sock)))
+    if ((ERROR_CODE(&err) = transport_fd_setup(SOCK_GNUTLS_FD(sock), sock_gnutls_on_event, sock)))
         goto error;
 
     // start handshake
@@ -338,20 +326,16 @@
 
 error:
     // tell the user
-    SOCK_GNUTLS_BASE(sock)->conn_cb_func(SOCK_GNUTLS_BASE(sock), &err, SOCK_GNUTLS_BASE(sock)->conn_cb_arg);
+    transport_connected(transport, &err, true);
 }
 
-/*
- * Our sock_stream_Type
- */
-struct sock_stream_type sock_gnutls_type = {
+struct transport_type sock_gnutls_type = {
+    .parent                 = &sock_tcp_type,
     .methods                = {
-        .read               = &sock_gnutls_read,
-        .write              = &sock_gnutls_write,
-        .event_init         = &sock_gnutls_event_init,
-        .event_enable       = &sock_gnutls_event_enable,
-        .release            = &sock_gnutls_release,
-        ._conn_cb           = &sock_gnutls_on_connect,
+        .read               = sock_gnutls_read,
+        .write              = sock_gnutls_write,
+        .destroy            = _sock_gnutls_destroy,
+        ._connected         = sock_gnutls__connected,
     },
 };
 
@@ -366,7 +350,7 @@
     printf("gnutls: %d: %s", level, msg);
 }
 
-err_t sock_gnutls_global_init (struct error_info *err)
+err_t sock_gnutls_global_init (error_t *err)
 {
     // global init
     if ((ERROR_EXTRA(err) = gnutls_global_init()) < 0)
@@ -395,7 +379,7 @@
 err_t sock_ssl_client_cred_create (struct sock_ssl_client_cred **ctx_cred,
         const char *cafile_path, bool verify,
         const char *cert_path, const char *pkey_path,
-        struct error_info *err
+        error_t *err
 ) {
     struct sock_ssl_client_cred *cred;
 
@@ -453,11 +437,10 @@
         sock_ssl_client_cred_destroy(cred);
 }
 
-err_t sock_ssl_connect_async (struct sock_stream **sock_ptr, 
+err_t sock_ssl_connect (const struct transport_info *info, transport_t **transport_ptr, 
         const char *hostname, const char *service,
         struct sock_ssl_client_cred *cred,
-        sock_stream_connect_cb cb_func, void *cb_arg, 
-        struct error_info *err
+        error_t *err
     )
 {
     struct sock_gnutls *sock = NULL;
@@ -467,7 +450,7 @@
         return SET_ERROR(err, ERR_CALLOC);
 
     // initialize base
-    sock_stream_init(SOCK_GNUTLS_BASE(sock), &sock_gnutls_type, cb_func, cb_arg);
+    transport_init(SOCK_GNUTLS_TRANSPORT(sock), &sock_gnutls_type, info);
 
     if (!cred) {
         // default credentials
@@ -487,6 +470,9 @@
     if ((sock->hostname = strdup(hostname)) == NULL)
         JUMP_SET_ERROR(err, ERR_STRDUP);
 
+    // initialize TCP
+    sock_tcp_init(SOCK_GNUTLS_TCP(sock));
+
     // initialize client session
     if ((ERROR_EXTRA(err) = gnutls_init(&sock->session, GNUTLS_CLIENT)) < 0)
         JUMP_SET_ERROR(err, ERR_GNUTLS_INIT);
@@ -503,11 +489,11 @@
         JUMP_SET_ERROR(err, ERR_GNUTLS_CRED_SET);
 
     // TCP connect
-    if (sock_tcp_connect_async_begin(SOCK_GNUTLS_TCP(sock), hostname, service, err))
+    if (sock_tcp_connect_async(SOCK_GNUTLS_TCP(sock), hostname, service, err))
         goto error;
 
     // done, wait for the connect to complete
-    *sock_ptr = SOCK_GNUTLS_BASE(sock);
+    *transport_ptr = SOCK_GNUTLS_TRANSPORT(sock);
 
     return SUCCESS;
 
@@ -520,18 +506,17 @@
 
 void sock_gnutls_destroy (struct sock_gnutls *sock)
 {
-    // terminate the TCP transport
-    sock_fd_close(SOCK_GNUTLS_FD(sock));
-
     // close the session rudely
     gnutls_deinit(sock->session);
-    
+ 
+    // terminate the TCP transport
+    sock_tcp_destroy(SOCK_GNUTLS_TCP(sock));
+   
     if (sock->cred)
         // drop the cred ref
         sock_ssl_client_cred_put(sock->cred);
 
     // free
     free(sock->hostname);
-    free(sock);
 }
 
--- a/src/sock_gnutls.h	Fri Apr 24 23:01:34 2009 +0300
+++ b/src/sock_gnutls.h	Mon May 04 20:55:43 2009 +0300
@@ -7,8 +7,7 @@
  * A sock_stream implementation using GnuTLS for SSL
  */
 
-#include "sock_internal.h"
-#include "sock_tcp.h"
+#include "sock_tcp_internal.h"
 
 #include <gnutls/gnutls.h>
 
@@ -33,6 +32,11 @@
     ERR_GNUTLS_CERT_SET_X509_KEY_FILE,
 };
 
+/*
+ * Our transport_type
+ */
+extern struct transport_type sock_gnutls_type;
+
 /**
  * GnuTLS credentials for client sockets.
  */
@@ -86,12 +90,7 @@
 /**
  * Cast a sock_gnutls to a sock_stream.
  */
-#define SOCK_GNUTLS_BASE(sock_ptr) SOCK_TCP_BASE(SOCK_GNUTLS_TCP(sock_ptr))
-
-/**
- * Get a pointer to the sock_gnutls's error_info.
- */
-#define SOCK_GNUTLS_ERR(sock_ptr) SOCK_ERR(SOCK_GNUTLS_BASE(sock_ptr))
+#define SOCK_GNUTLS_TRANSPORT(sock_ptr) SOCK_TCP_TRANSPORT(SOCK_GNUTLS_TCP(sock_ptr))
 
 /**
  * Initialize the global gnutls state
--- a/src/sock_internal.h	Fri Apr 24 23:01:34 2009 +0300
+++ b/src/sock_internal.h	Mon May 04 20:55:43 2009 +0300
@@ -7,116 +7,14 @@
  * internal sock_* interface
  */
 #include "sock.h"
-#include <stdbool.h>
-
-/**
- * General state for all sock_stream's
- */
-struct sock_stream_ctx {
-    /** libevent core */
-    struct event_base *ev_base;
-
-};
 
 /**
  * Global sock_stream_ctx used for sock_init() and all sock_stream's
  */
-extern struct sock_stream_ctx _sock_stream_ctx;
-
-/**
- * Socket implementation type methods
- */
-struct sock_stream_methods {
-    /** As read(2) */
-    err_t (*read) (struct sock_stream *sock, void *buf, size_t *len, struct error_info *err);
-
-    /** As write(2) */
-    err_t (*write) (struct sock_stream *sock, const void *buf, size_t *len, struct error_info *err);
-
-    /** Initialize events. cb_info/cb_arg are already updated */
-    err_t (*event_init) (struct sock_stream *sock);
-
-    /** Enable events as specified */
-    err_t (*event_enable) (struct sock_stream *sock, short mask);
-    
-    /** Release all resources and free the sock_stream */
-    void (*release) (struct sock_stream *sock);
-
-    /** The type's connect_cb handler, defaults to just invoke conn_cb_func */
-    void (*_conn_cb) (struct sock_stream *sock, struct error_info *err);
-};
-
-/**
- * The base type struct, which defines the method table.
- */
-struct sock_stream_type {
-    /** Method table */
-    struct sock_stream_methods methods;
-};
-
-/**
- * The base sock_stream type, as used by the sock_stream_* functions.
- *
- * The specific implementations should embed this at the start of their type-specific struct, and then cast around
- * as appropriate.
- */
-struct sock_stream {
-    /** The sock_stream_type for this socket */
-    struct sock_stream_type *type;
-
-    /** Last error info, XXX: remove this */
-    struct error_info err;
+extern struct sock_stream_ctx {
+    /** libevent core */
+    struct event_base *ev_base;
 
-    /** Callbacks */
-    const struct sock_stream_callbacks *cb_info;
-
-    /** Callback arg */
-    void *cb_arg;
-
-    /** Connection callback function */
-    sock_stream_connect_cb conn_cb_func;
-
-    /** Connection callback context argument */
-    void *conn_cb_arg;
-};
-
-/**
- * Convert a `struct sock_stream*` to the given type.
- */
-#define SOCK_FROM_BASE(sock, type) ((type*) sock)
-
-/**
- * Get a pointer to the sock_stream's error_info field.
- */
-#define SOCK_ERR(sock) (&(sock)->err)
-
-/**
- * Initialize a sock_stream with the given sock_stream_type.
- *
- * The sock_stream should be initialized to zero. It is a bug to call this twice.
- *
- * @param sock the new sock_stream
- * @param type the sock_stream_type defining the implementation used
- * @param cb_func the optional connect_async callback function
- * @param cb_arg the optional context argument for cb_func
- */
-void sock_stream_init (struct sock_stream *sock, struct sock_stream_type *type, sock_stream_connect_cb cb_func, void *cb_arg);
-
-/**
- * Invoke the appropriate callbacks for the given EV_* bitmask.
- *
- * @param sock the sock_stream
- * @param what combination of EV_* bits describing what callbacks to invoke
- */
-void sock_stream_invoke_callbacks (struct sock_stream *sock, short what);
-
-/**
- * Invoke the sock_stream_conn_cb callback with the given error param.
- *
- * This invokes the sock_stream_methods::_conn_cb if present and \a direct is not given, otherwise the callback directly
- *
- * @param direct force the conn_cb to be called directly
- */
-void sock_stream_invoke_conn_cb (struct sock_stream *sock, struct error_info *err, bool direct);
+} _sock_stream_ctx;
 
 #endif /* SOCK_INTERNAL_H */
--- a/src/sock_ssl.h	Fri Apr 24 23:01:34 2009 +0300
+++ b/src/sock_ssl.h	Mon May 04 20:55:43 2009 +0300
@@ -4,7 +4,7 @@
 /**
  * @file
  *
- * SSL-specific functionality as related to sock.h
+ * SSL transport implementation.
  */
 #include "sock.h"
 
@@ -36,7 +36,7 @@
 err_t sock_ssl_client_cred_create (struct sock_ssl_client_cred **ctx_cred,
         const char *cafile_path, bool verify,
         const char *cert_path, const char *pkey_path,
-        struct error_info *err
+        error_t *err
 );
 
 /**
@@ -58,19 +58,17 @@
  * or a sock_ssl_client_cred allocated using sock_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 sock_ptr the new sock_stream
+ * @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 cb_func the callback for connection/handshake completion
- * @param cb_arg the callback context argument
  * @param err returned error info
  */
-err_t sock_ssl_connect_async (struct sock_stream **sock_ptr, 
+err_t sock_ssl_connect (const struct transport_info *info, transport_t **transport_ptr, 
         const char *hostname, const char *service,
         struct sock_ssl_client_cred *cred,
-        sock_stream_connect_cb cb_func, void *cb_arg, 
-        struct error_info *err
+        error_t *err
     );
 
 
--- a/src/sock_tcp.c	Fri Apr 24 23:01:34 2009 +0300
+++ b/src/sock_tcp.c	Mon May 04 20:55:43 2009 +0300
@@ -1,5 +1,6 @@
 
-#include "sock_tcp.h"
+#include "sock_tcp_internal.h"
+#include "sock_internal.h"
 #include "log.h"
 
 #include <stdlib.h>
@@ -7,166 +8,211 @@
 #include <sys/socket.h>
 #include <string.h>
 
-static void sock_tcp_release (struct sock_stream *base_sock)
+/**
+ * Start connecting to the given address in a non-blocking fashion. Returns any errors that immediately crop up,
+ * otherwise eventually calls sock_tcp_connect_done().
+ */
+static err_t sock_tcp_connect_addr (struct sock_tcp *sock, struct addrinfo *addr, error_t *err);
+
+
+
+/**
+ * Our transport_methods
+ */
+static void _sock_tcp_destroy (transport_t *transport)
 {
-    struct sock_tcp *sock = SOCK_FROM_BASE(base_sock, struct sock_tcp);
+    struct sock_tcp *sock = transport_check(transport, &sock_tcp_type);
     
-    // close and free
-    sock_fd_close(SOCK_TCP_FD(sock));
-    sock_tcp_free(sock);
+    // proxy
+    sock_tcp_destroy(sock);
 }
 
 /*
- * Our sock_stream_type
+ * Our transport_type
  */
-static struct sock_stream_type sock_tcp_type = {
+struct transport_type sock_tcp_type = {
+    .parent                 = &transport_fd_type,
     .methods                = {
-        .read               = &sock_fd_read,
-        .write              = &sock_fd_write,
-        .event_init         = &sock_fd_event_init,
-        .event_enable       = &sock_fd_event_enable,
-        .release            = &sock_tcp_release,
+        .read               = transport_fd_methods_read,
+        .write              = transport_fd_methods_write,
+        .events             = transport_fd_methods_events,
+        .destroy            = _sock_tcp_destroy,
     },
 };
 
-static err_t sock_tcp_alloc (struct sock_tcp **sock_ptr, sock_stream_connect_cb cb_func, void *cb_arg)
-{
-    // alloc
-    if ((*sock_ptr = calloc(1, sizeof(**sock_ptr))) == NULL)
-        return ERR_CALLOC;
-    
-    // initialize base with sock_tcp_type
-    sock_stream_init(SOCK_TCP_BASE(*sock_ptr), &sock_tcp_type, cb_func, cb_arg);
-
-    // init without any fd
-    sock_fd_init(SOCK_TCP_FD(*sock_ptr), -1);
-
-    // done
-    return SUCCESS;
-}
-
-err_t sock_tcp_init_socket (struct sock_tcp *sock, struct addrinfo *addr, struct error_info *err)
+/**
+ * Create a new socket() using the given addr's family/socktype/protocol, and update our transport_fd state.
+ */
+static err_t sock_tcp_create_socket (struct sock_tcp *sock, struct addrinfo *addr, error_t *err)
 {
     int fd;
 
-    // call socket
+    // call socket()
     if ((fd = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol)) < 0)
         RETURN_SET_ERROR_ERRNO(err, ERR_SOCKET);
 
-    // ok
-    sock_fd_init(SOCK_TCP_FD(sock), fd);
+    // ok, update transport_fd
+    transport_fd_set(SOCK_TCP_FD(sock), fd);
+
 
     return SUCCESS;
 }
 
 /**
- * Attempt to connect to the given addrinfo, or the next one, if that fails, etc.
+ * Read the socket's error code, if any.
+ *
+ * The read error number is stored in err->extra on SUCCESS, unless reading the error fails, in which case
+ * err contains the normal error info.
+ *
+ * @return error code on failure
  */
-static err_t sock_tcp_connect_async_continue (struct sock_tcp *sock, struct addrinfo *addr, struct error_info *err)
+static err_t sock_tcp_read_error (struct sock_tcp *sock, error_t *err)
 {
-    // no more addresses left?
-    if (!addr)
-        // XXX: rename error
-        return SET_ERROR(err, ERR_GETADDRINFO_EMPTY);
-
-    // try and connect to each one until we find one that works
-    do {
-        // attempt to start connect
-        if (sock_tcp_connect_async_addr(sock, addr, err) == SUCCESS)
-            break;
-
-        // try the next one
-        log_warn("sock_tcp_connect_async_addr(%s): %s", addr->ai_canonname, error_msg(err));
-
-    } while ((addr = addr->ai_next));
-    
-
-    if (addr) {
-        // we succesfully did a sock_tcp_connect_async_addr on valid address
-        return SUCCESS;
-
-    } else {
-        // all of the connect_async_addr's failed, return the last error
-        return ERROR_CODE(err);
-    }
-}
-
-/**
- * Our async connect operation has completed, clean up addrinfos and events, and call the user callback. The given
- * \a err should be NULL for successful completion, or the error for unsuccesfully completion.
- */
-static void sock_tcp_connect_async_done (struct sock_tcp *sock, struct error_info *err)
-{
-    struct sock_stream *sock_base = SOCK_TCP_BASE(sock);
-
-    // free the addrinfo
-    freeaddrinfo(sock->async_res); 
-    sock->async_res = sock->async_cur = NULL;
-
-    // remove our event handler so the user can install their own
-    sock_fd_deinit_ev(SOCK_TCP_FD(sock));
-
-    // ok, run callback
-    sock_stream_invoke_conn_cb(sock_base, err, false);
-}
-
-/**
- * Our start_connect callback
- */
-static void sock_tcp_connect_cb (int fd, short what, void *arg)
-{
-    struct sock_tcp *sock = arg;
     int optval;
     socklen_t optlen;
-    struct error_info err;
-    err_t tmp;
 
-    // XXX: timeouts
-    (void) what;
+    RESET_ERROR(err);
 
     // init params
     optval = 0;
     optlen = sizeof(optval);
 
     // read error code
-    if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &optval, &optlen))
-        JUMP_SET_ERROR_ERRNO(&err, ERR_GETSOCKOPT);
+    if (getsockopt(SOCK_TCP_FD(sock)->fd, 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))
-        JUMP_SET_ERROR_EXTRA(&err, ERR_GETSOCKOPT, EINVAL);
-        
-    // did the connect complete succesfully or not?
-    if (optval)
-        JUMP_SET_ERROR_EXTRA(&err, ERR_CONNECT, optval);
+        RETURN_SET_ERROR_EXTRA(err, ERR_GETSOCKOPT, EINVAL);
     
-    // done
-    return sock_tcp_connect_async_done(sock, NULL);
+    // then store the system error code
+    ERROR_EXTRA(err) = optval;
+
+    // ok
+    return SUCCESS;
+}
+
+/**
+ * Attempt to connect to the given addrinfo, or the next one, if that fails, etc.
+ *
+ * This does not call transport_connected().
+ */
+static err_t sock_tcp_connect_continue (struct sock_tcp *sock, struct addrinfo *addr, struct error_info *err)
+{
+    if (!addr)
+        // no more addresses left to try
+        return SET_ERROR(err, ERR_GETADDRINFO_EMPTY);
+
+    // try and connect to each one until we find one that works
+    do {
+        // attempt to start connect
+        if (sock_tcp_connect_addr(sock, addr, err) == SUCCESS)
+            break;
+
+        // log a warning on the failed connect
+        log_warn("sock_tcp_connect_addr(%s): %s", addr->ai_canonname, error_msg(err));
+
+    } while ((addr = addr->ai_next));
+    
+
+    if (addr)
+        // we succesfully did a sock_tcp_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 sock_tcp_connect_cleanup (struct sock_tcp *sock)
+{
+    // free the addrinfo
+    freeaddrinfo(sock->async_res); 
+    sock->async_res = sock->async_cur = NULL;
+    
+    // remove our event handler
+    transport_fd_clear(SOCK_TCP_FD(sock));
+}
+
+/**
+ * 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 sock_tcp_connect_done (struct sock_tcp *sock, struct error_info *conn_err)
+{
+    error_t err;
+
+    // cleanup
+    sock_tcp_connect_cleanup(sock);
+
+    if (conn_err)
+        // passthrough errors
+        JUMP_SET_ERROR_INFO(&err, conn_err);
+    
+    // set up for default transport event-based operation
+    if ((ERROR_CODE(&err) = transport_fd_defaults(SOCK_TCP_FD(sock))))
+        goto error;
+
+    // ok, no error
+
+error:                
+    // pass on to transport
+    transport_connected(SOCK_TCP_TRANSPORT(sock), IS_ERROR(&err) ? &err : NULL, false);
+}
+
+/**
+ * Our async connect callback
+ */
+static void sock_tcp_on_connect (struct transport_fd *fd, short what, void *arg)
+{
+    struct sock_tcp *sock = arg;
+    struct error_info err;
+    err_t tmp;
+
+    // XXX: timeouts
+    (void) what;
+    
+    // read socket error code
+    if (sock_tcp_read_error(sock, &err))
+        goto error;
+
+    // did the connect fail?
+    if (ERROR_EXTRA(&err))
+        JUMP_SET_ERROR(&err, ERR_CONNECT);
+    
+    // done, success
+    return sock_tcp_connect_done(sock, NULL);
 
 error:
     // close the socket
-    if ((tmp = sock_fd_close(SOCK_TCP_FD(sock))))
+    if ((tmp = transport_fd_close(fd)))
         log_warn("error closing socket after connect error: %s", error_name(tmp));
 
     // log a warning
     log_warn("connect to '%s' failed: %s", sock->async_cur->ai_canonname, error_msg(&err));
 
     // try the next one or fail completely
-    if (sock_tcp_connect_async_continue(sock, sock->async_cur->ai_next, &err))
-        sock_tcp_connect_async_done(sock, &err);
+    if (sock_tcp_connect_continue(sock, sock->async_cur->ai_next, &err))
+        sock_tcp_connect_done(sock, &err);
 }
 
-err_t sock_tcp_connect_async_addr (struct sock_tcp *sock, struct addrinfo *addr, struct error_info *err)
+static err_t sock_tcp_connect_addr (struct sock_tcp *sock, struct addrinfo *addr, error_t *err)
 {
     int ret;
     err_t tmp;
 
     // first, create the socket
-    if (sock_tcp_init_socket(sock, addr, err))
+    if (sock_tcp_create_socket(sock, addr, err))
         return ERROR_CODE(err);
 
     // then, set it up as nonblocking
-    if ((ERROR_CODE(err) = sock_fd_set_nonblock(SOCK_TCP_FD(sock), true)))
+    if ((ERROR_CODE(err) = transport_fd_nonblock(SOCK_TCP_FD(sock), true)))
         goto error;
 
     // then, initiate the connect
@@ -174,20 +220,21 @@
         JUMP_SET_ERROR_ERRNO(err, ERR_CONNECT);
     
     if (ret < 0) {
+        // set the "current" address in case it fails and we need to try the next one
+        sock->async_cur = addr;
+
         // ok, connect started, setup our completion callback
-        if ((ERROR_CODE(err) = sock_fd_init_ev(SOCK_TCP_FD(sock), &sock_tcp_connect_cb, sock)))
+        if ((ERROR_CODE(err) = transport_fd_setup(SOCK_TCP_FD(sock), sock_tcp_on_connect, sock)))
             goto error;
     
         // enable for write
-        if ((ERROR_CODE(err) = sock_fd_enable_events(SOCK_TCP_FD(sock), EV_WRITE)))
+        if ((ERROR_CODE(err) = transport_fd_enable(SOCK_TCP_FD(sock), TRANSPORT_WRITE)))
             goto error;
 
-        // set the "current" address in case it fails and we need to try the next one
-        sock->async_cur = addr;
-
     } 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);
     }
     
@@ -196,28 +243,40 @@
 
 error:
     // close the stuff we did open
-    if ((tmp = sock_fd_close(SOCK_TCP_FD(sock))))
+    if ((tmp = transport_fd_close(SOCK_TCP_FD(sock))))
         log_warn("error closing socket after connect error: %s", error_name(tmp));
 
     return ERROR_CODE(err);
 }
 
-err_t sock_tcp_connect_async_begin (struct sock_tcp *sock, const char *hostname, const char *service, struct error_info *err)
+/**
+ * External interface
+ */
+void sock_tcp_init (struct sock_tcp *sock)
+{
+    struct event_base *ev_base = _sock_stream_ctx.ev_base;
+
+    // init without any fd
+    transport_fd_init(SOCK_TCP_FD(sock), ev_base, TRANSPORT_FD_INVALID);
+
+}
+
+err_t sock_tcp_connect_async (struct sock_tcp *sock, const char *hostname, const char *service, error_t *err)
 {
     struct addrinfo hints;
     int ret;
 
-    // hints
+    // build hints
     memset(&hints, 0, sizeof(hints));
     hints.ai_family = AF_UNSPEC;
     hints.ai_socktype = SOCK_STREAM;
 
-    // resolve
+    // resolve (blocking)
     if ((ret = getaddrinfo(hostname, service, &hints, &sock->async_res)))
         RETURN_SET_ERROR_EXTRA(err, ERR_GETADDRINFO, ret);
     
-    // start connecting
-    if (sock_tcp_connect_async_continue(sock, sock->async_res, err))
+    // start connecting on the first result
+    if (sock_tcp_connect_continue(sock, sock->async_res, err))
         goto error;
 
     // ok
@@ -225,128 +284,54 @@
 
 error:
     // cleanup
-    if (sock->async_res) {
-        freeaddrinfo(sock->async_res);
-        sock->async_res = NULL;
-    }
-
+    if (sock->async_res)
+        sock_tcp_connect_cleanup(sock);
+    
     return ERROR_CODE(err);
 }
 
-err_t sock_tcp_connect_blocking (struct sock_tcp *sock, const char *hostname, const char *service, struct error_info *err)
+void sock_tcp_destroy (struct sock_tcp *sock)
 {
-    struct addrinfo hints, *res, *r;
-    int ret;
-    
-    // zero error code
-    RESET_ERROR(err);
+    // cleanup our stuff
+    if (sock->async_res)
+        sock_tcp_connect_cleanup(sock);
     
-    // hints
-    memset(&hints, 0, sizeof(hints));
-    hints.ai_family = AF_UNSPEC;
-    hints.ai_socktype = SOCK_STREAM;
-
-    // resolve
-    if ((ret = getaddrinfo(hostname, service, &hints, &res)))
-        RETURN_SET_ERROR_EXTRA(err, ERR_GETADDRINFO, ret);
-
-    // try each result in turn
-    for (r = res; r; r = r->ai_next) {
-        // create the socket
-        if ((SOCK_TCP_FD(sock)->fd = socket(r->ai_family, r->ai_socktype, r->ai_protocol)) < 0) {
-            // remember error
-            SET_ERROR_ERRNO(err, ERR_SOCKET);
-
-            // skip to next one
-            continue;
-        }
-        
-        // connect to remote address
-        if (connect(SOCK_TCP_FD(sock)->fd, r->ai_addr, r->ai_addrlen)) {
-            // remember error
-            SET_ERROR_ERRNO(err, ERR_CONNECT);
-            
-            // close/invalidate socket
-            sock_fd_close(SOCK_TCP_FD(sock));
-
-            // skip to next one
-            continue;
-        }
-        
-        // valid socket, use this
-        break;
-    }
-    
-    // ensure we got some valid socket, else return last error code
-    if (SOCK_TCP_FD(sock)->fd < 0) {
-        // did we hit some error?
-        if (IS_ERROR(err))
-            // return last error
-            return ERROR_CODE(err);
-        
-        else
-            // no results
-            return SET_ERROR(err, ERR_GETADDRINFO_EMPTY);
-    }
-
-    // ok, done
-    return 0;    
+    // destroy lower level
+    transport_fd_destroy(SOCK_TCP_FD(sock)); 
 }
 
-void sock_tcp_free (struct sock_tcp *sock)
-{
-    // free
-    free(sock);
-}
-
-err_t sock_tcp_connect (struct sock_stream **sock_ptr, const char *host, const char *service, struct error_info *err)
+/**
+ * Public interface
+ */
+err_t sock_tcp_connect (const struct transport_info *info, transport_t **transport_ptr, 
+        const char *host, const char *service, error_t *err)
 {
     struct sock_tcp *sock;
+ 
+    // alloc
+    if ((sock = calloc(1, sizeof(*sock))) == NULL)
+        return ERR_CALLOC;
     
-    // allocate
-    if ((ERROR_CODE(err) = sock_tcp_alloc(&sock, NULL, NULL)))
-        return ERROR_CODE(err);
+    // initialize transport
+    transport_init(SOCK_TCP_TRANSPORT(sock), &sock_tcp_type, info);
     
+    // init our state
+    sock_tcp_init(sock);
+ 
     // connect    
-    if (sock_tcp_connect_blocking(sock, host, service, err))
+    if (sock_tcp_connect_async(sock, host, service, err))
         goto error;
 
     // good
-    *sock_ptr = SOCK_TCP_BASE(sock);
+    *transport_ptr = SOCK_TCP_TRANSPORT(sock);
 
     return 0;
 
 error:
     // cleanup
-    sock_tcp_free(sock);
+    sock_tcp_destroy(sock);
         
     // return error code
     return ERROR_CODE(err);
 }
 
-err_t sock_tcp_connect_async (struct sock_stream **sock_ptr, const char *host, const char *service, 
-        sock_stream_connect_cb cb_func, void *cb_arg, struct error_info *err)
-{
-    struct sock_tcp *sock;
-    
-    // allocate and init
-    if ((ERROR_CODE(err) = sock_tcp_alloc(&sock, cb_func, cb_arg)))
-        return ERROR_CODE(err);
-    
-    // connect    
-    if (sock_tcp_connect_async_begin(sock, host, service, err))
-        goto error;
-
-    // good
-    *sock_ptr = SOCK_TCP_BASE(sock);
-
-    return 0;
-
-error:
-    // cleanup
-    sock_tcp_free(sock);
-        
-    // return error code
-    return ERROR_CODE(err);
-}
-
--- a/src/sock_tcp.h	Fri Apr 24 23:01:34 2009 +0300
+++ b/src/sock_tcp.h	Mon May 04 20:55:43 2009 +0300
@@ -4,79 +4,25 @@
 /**
  * @file
  *
- * TCP implementation of sock_stream interface.
- */
-#include "sock_internal.h"
-#include "sock_fd.h"
-#include <netdb.h>
-
-/**
- * Contains the base sock_stream struct, and the file descriptor
+ * TCP transport implementation.
+ *
+ * XXX: provide some TCP-specific type/functions?
  */
-struct sock_tcp {
-    /** The base struct for sock_stream_* functions */
-    struct sock_fd base_fd;
-    
-    /** The current connect_async resolved address */
-    struct addrinfo *async_res, *async_cur;
-};
-
-/**
- * Get a sock_fd pointer from a sock_tcp pointer
- */
-#define SOCK_TCP_FD(sock_ptr) (&(sock_ptr)->base_fd)
-
-/**
- * Get a sock_base pointer from a sock_tcp pointer
- */
-#define SOCK_TCP_BASE(sock_ptr) SOCK_FD_BASE(SOCK_TCP_FD(sock_ptr))
-
-/**
- * Get the sock_stream.err pointer from a sock_tcp pointer
- */
-#define SOCK_TCP_ERR(sock_ptr) SOCK_ERR(SOCK_TCP_BASE(sock_ptr))
+#include "transport.h"
 
 /**
- * Initialize a blank sock_tcp by creating a new socket (using the socket() syscall), but doesn't do anything further.
+ * 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.
  *
- * This uses the ai_family, ai_socktype and ai_protocol fields from the given addrinfo.
- */
-err_t sock_tcp_init_socket (struct sock_tcp *sock, struct addrinfo *addr, struct error_info *err);
-
-/**
- * Initiate an async connection operation on the given socket to the given address. Once the connect() completes,
- * either the on_connect or the on_error callback will be called.
+ * XXX: blocking DNS resolution
  *
- * If, for some weird reason, this ends up doing a blocking connect(), the on_connect callback will be called directly.
- * If the async connect fails, this just returns the error.
- *
- * @param sock the unconnected TCP sockect to connect with
- * @param addr the address to connect to
+ * @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 sock_tcp_connect_async_addr (struct sock_tcp *sock, struct addrinfo *addr, struct error_info *err);
+err_t sock_tcp_connect (const struct transport_info *info, transport_t **transport_ptr, 
+        const char *host, const char *service, error_t *err);
 
-/**
- * Attempt to connect asyncronously to the given hostname/service. Once a connection has been established, the
- * on_connect() callback will be called.
- *
- * In case of errors, either on_error() will be called, or an error returned - depending on when the error happaned.
- *
- * @param sock the unconnected TCP socket to connect with
- * @param hostname the hostname to resolve
- * @param service the service to connect to
- * @param err returned error info
- */
-err_t sock_tcp_connect_async_begin (struct sock_tcp *sock, const char *hostname, const char *service, struct error_info *err);
-
-/**
- * Initialize a blank sock_tcp by connecting in a blocking fashion.
- */
-err_t sock_tcp_connect_blocking (struct sock_tcp *sock, const char *hostname, const char *service, struct error_info *err);
-
-/**
- * Free a non-connected sock_tcp
- */
-void sock_tcp_free (struct sock_tcp *sock);
-
-#endif /* SOCK_TCP_H */
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/sock_tcp_internal.h	Mon May 04 20:55:43 2009 +0300
@@ -0,0 +1,65 @@
+#ifndef SOCK_TCP_INTERNAL_H
+#define SOCK_TCP_INTERNAL_H
+
+/**
+ * @file
+ *
+ * Internal interface of the sock_tcp transport implementation.
+ */
+#include "sock_tcp.h"
+#include "transport_fd.h"
+#include <netdb.h>
+
+/**
+ * Our transport type struct
+ */
+extern struct transport_type sock_tcp_type;
+
+/**
+ * TCP transport state
+ */
+struct sock_tcp {
+    /** Base fd-based transport state */
+    struct transport_fd base_fd;
+    
+    /** The resolver state for the async connect process */
+    struct addrinfo *async_res, *async_cur;
+};
+
+/**
+ * Get a transport_fd pointer from a sock_tcp pointer
+ */
+#define SOCK_TCP_FD(sock_ptr) (&(sock_ptr)->base_fd)
+
+/**
+ * Get a transport pointer from a sock_tcp pointer
+ */
+#define SOCK_TCP_TRANSPORT(sock_ptr) TRANSPORT_FD_BASE(SOCK_TCP_FD(sock_ptr))
+
+/**
+ * Initialize the sock_tcp state
+ */
+void sock_tcp_init (struct sock_tcp *sock);
+
+/**
+ * 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 sock the unconnected TCP 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 sock_tcp_connect_async (struct sock_tcp *sock, const char *hostname, const char *service, error_t *err);
+
+/**
+ * Destroy the sock_tcp's state, including the transport_fd state.
+ */
+void sock_tcp_destroy (struct sock_tcp *sock);
+
+#endif /* SOCK_TCP_INTERNAL_H */
--- a/src/sock_test.c	Fri Apr 24 23:01:34 2009 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,288 +0,0 @@
-#include "sock_test.h"
-
-#include <stdlib.h>
-#include <string.h>
-#include <assert.h>
-
-/**
- * Grow buf->vecs as needed, to ensure that buf->write_vec is valid
- */
-static err_t sock_test_grow_buf (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;
-    }
-
-    // set vec
-    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++) {
-        v->buf = NULL;
-        v->len = 0;
-    }
-
-    // ok
-    return SUCCESS;
-}
-
-static err_t sock_test_read (struct sock_stream *base_sock, void *buf_ptr, size_t *len, struct error_info *err)
-{
-    struct sock_test *sock = SOCK_FROM_BASE(base_sock, struct sock_test);
-    struct io_buf *buf = &sock->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 (sock->nonblocking && !sock->eof) {
-            // wait for more to be fed in
-            *len = 0;
-            return SUCCESS;
-
-        } else {
-            // EOF!
-            return SET_ERROR(err, ERR_READ_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);
-    
-    // consumed the whole vec?
-    if (to_read < available) {
-        // move offset
-        buf->off += to_read;
-
-    } else {
-        // next vector
-        buf->read_vec++;
-    }
-
-    // update len
-    *len = to_read;
-
-    // ok
-    return SUCCESS;
-}
-
-static err_t sock_test_write (struct sock_stream *base_sock, const void *buf_ptr, size_t *len, struct error_info *err)
-{
-    struct sock_test *sock = SOCK_FROM_BASE(base_sock, struct sock_test);
-    struct io_buf *buf = &sock->send_buf;
-
-    // ensure there's room
-    if ((ERROR_CODE(err) = sock_test_grow_buf(buf)))
-        goto error;
-    
-    // the next buffer
-    struct io_vec *vec = buf->write_vec;
-
-    // store
-    vec->len = *len;
-    assert((vec->buf = malloc(vec->len)));
-    memcpy(vec->buf, buf_ptr, vec->len);
-
-    // move vec onwards
-    buf->write_vec++;
-
-    // ok
-    return SUCCESS;
-
-error:
-    return ERROR_CODE(err);
-}
-
-static err_t sock_test_event_init (struct sock_stream *base_sock)
-{
-    struct sock_test *sock = SOCK_FROM_BASE(base_sock, struct sock_test);
-
-    // set the nonblocking flag
-    sock->nonblocking = true;
-
-    return SUCCESS;
-}
-
-static err_t sock_test_event_enable (struct sock_stream *base_sock, short mask)
-{
-    struct sock_test *sock = SOCK_FROM_BASE(base_sock, struct sock_test);
-
-    // store mask
-    sock->ev_mask = mask;
-
-    return SUCCESS;
-}
-
-static void sock_test_release (struct sock_stream *base_sock)
-{
-    struct sock_test *sock = SOCK_FROM_BASE(base_sock, struct sock_test);
-
-    sock_test_destroy(sock);
-}
-
-/*
- * Our sock_stream_type
- */
-static struct sock_stream_type sock_test_type = {
-    .methods                = {
-        .read               = &sock_test_read,
-        .write              = &sock_test_write,
-        .event_init         = &sock_test_event_init,
-        .event_enable       = &sock_test_event_enable,
-        .release            = &sock_test_release,
-    },
-};
-
-struct sock_test* sock_test_create (void)
-{
-    struct sock_test *sock;
-
-    // allocate
-    assert((sock = calloc(1, sizeof(*sock))));
-    
-    // initialize base with our sock_stream_type
-    sock_stream_init(SOCK_TEST_BASE(sock), &sock_test_type, NULL, NULL);
-
-    // ok
-    return sock;
-}
-
-void sock_test_destroy (struct sock_test *sock)
-{
-    size_t i;
-    struct io_buf *sbuf = &sock->send_buf, *rbuf = &sock->recv_buf;
-    
-    // free the send buffer
-    for (i = 0; i < sbuf->count; i++) {
-        free(sbuf->vecs[i].buf);
-    }
-
-    // free the buffer vector lists
-    free(sbuf->vecs);
-    free(rbuf->vecs);
-    
-    // free the sock itself
-    free(sock);
-}
-
-void sock_test_set_recv_buffer (struct sock_test *sock, struct io_vec *vecs, size_t count, bool eof)
-{
-    struct io_buf *buf = &sock->recv_buf;
-
-    // allocate + copy
-    assert((buf->vecs = calloc(count, sizeof(struct io_vec))));
-    memcpy(buf->vecs, vecs, count * sizeof(struct io_vec));
-    
-    // set
-    buf->count = count;
-    buf->read_vec = buf->vecs;
-    buf->write_vec = buf->vecs + count;
-    buf->off = 0;
-    
-    // set EOF flag?
-    if (eof)
-        sock->eof = true;
-}
-
-void sock_test_notify_events (struct sock_test *sock)
-{
-    // notify if events are enabled
-    if (sock->ev_mask) {
-        // zero mask
-        int mask = sock->ev_mask;
-        sock->ev_mask = 0;
-
-        sock_stream_invoke_callbacks(SOCK_TEST_BASE(sock), mask);
-    }
-}
-
-void sock_test_add_recv_vec (struct sock_test *sock, struct io_vec new_vec)
-{
-    struct io_buf *buf = &sock->recv_buf;
-
-    // ensure there's room
-    assert(sock_test_grow_buf(buf) == SUCCESS);
-    
-    // copy    
-    *(buf->write_vec++) = new_vec;
-    
-    // notify
-    sock_test_notify_events(sock);
-}
-
-void sock_test_add_recv_str (struct sock_test *sock, const char *str)
-{
-    struct io_vec vec = {
-        (char*) str, strlen(str)
-    };
-
-    sock_test_add_recv_vec(sock, vec);
-}
-
-void sock_test_set_recv_eof (struct sock_test *sock)
-{
-    sock->eof = true;
-
-    sock_test_notify_events(sock);
-}
-
-void sock_test_get_send_data (struct sock_test *sock, char **buf_ptr, size_t *len_ptr)
-{
-    struct io_buf *buf = &sock->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;
-}
-
--- a/src/sock_test.h	Fri Apr 24 23:01:34 2009 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,113 +0,0 @@
-#ifndef SOCK_TEST_H
-#define SOCK_TEST_H
-
-/**
- * @file
- *
- * Dummy sock_stream implemention for local testing.
- */
-#include "sock_internal.h"
-#include <stdbool.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;
-};
-
-/**
- * A dummy sock_stream implementation intended for testing purposes.
- */
-struct sock_test {
-    /** The base struct for sock_stream_* functions */
-    struct sock_stream base;
-
-    /** The send/recieve buffers */
-    struct io_buf send_buf, recv_buf;
-
-    /** non-blocking mode? */
-    bool nonblocking;
-
-    /** No more data is going to be added, return EOF once all the rest is consumed */
-    bool eof;
-
-    /** event flags */
-    int ev_mask;
-};
-
-/**
- * Get a sock_stream pointer from a sock_tcp pointer
- */
-#define SOCK_TEST_BASE(sock_ptr) (&(sock_ptr)->base)
-
-/**
- * Get the sock_stream.err pointer from a sock_tcp pointer
- */
-#define SOCK_TEST_ERR(sock_ptr) SOCK_ERR(SOCK_TEST_BASE(sock_ptr))
-
-/**
- * A dummy stream socket intended for testing purposes.
- */
-struct sock_test* sock_test_create (void);
-
-/**
- * Destroy the sock buffer, releasing any resource we allocated ourself
- */
-void sock_test_destroy (struct sock_test *sock);
-
-/**
- * Set the recieve buffer contents.
- *
- * The vectors themselves are copied, but the data they contain is not.
- *
- * If the EOF flag is given, it indicates that no more data will be added, otherwise the eof status is unchanged.
- */
-void sock_test_set_recv_buffer (struct sock_test *sock, struct io_vec *vecs, size_t count, bool eof);
-
-/**
- * Add some data to the recieve buffer.
- *
- * If events are enabled, they are triggered.
- */
-void sock_test_add_recv_vec (struct sock_test *sock, struct io_vec vec);
-
-/**
- * Add a string to the recieve buffer using sock_test_add_recv_vec()
- */
-void sock_test_add_recv_str (struct sock_test *sock, const char *str);
-
-/**
- * Set EOF on recv, and trigger events.
- */
-void sock_test_set_recv_eof (struct sock_test *sock);
-
-/**
- * Get the send buffer contents as a single string, free() after use if you care about that.
- *
- * Clears the send buffer, so this doesn't return the same data twice.
- */
-void sock_test_get_send_data (struct sock_test *sock, char **buf, size_t *len);
-
-#endif
--- a/src/test.c	Fri Apr 24 23:01:34 2009 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1341 +0,0 @@
-/**
- * The main test code entry point
- */
-#include "sock_test.h"
-#include "line_proto.h"
-#include "irc_queue.h"
-#include "irc_conn.h"
-#include "irc_net.h"
-#include "log.h"
-#include "str.h"
-#include "error.h"
-
-#include <stdlib.h>
-#include <string.h>
-#include <getopt.h>
-#include <assert.h>
-#include <ctype.h>
-
-#define DUMP_STR_BUF 1024
-#define DUMP_STR_COUNT 8
-#define DUMP_STR_TAIL 10
-
-/**
- * Global test-running state
- */
-struct test_ctx {
-    /** The event_base that we have setup */
-    struct event_base *ev_base;
-
-} _test_ctx;
-
-
-/**
- * This re-formats the given string to escape values, and returns a pointer to an internal static buffer.
- *
- * If len is given as >= 0, only the given number of chars will be dumped from str.
- *
- * The buffer cycles a bit, so the returned pointers remain valid across DUMP_STR_COUNT calls.
- *
- * The resulting string is truncated to (DUMP_STR_BUF - DUMP_STR_TAIL) bytes, not including the ending "...'\0".
- *
- * @param str the string to dump, should be NUL-terminated unless len is given
- * @param len if negative, ignored, otherwise, only this many bytes are dumped from str
- * @param return a pointer to a static buffer that remains valid across DUMP_STR_COUNT calls to this function
- */
-const char *dump_strn (const char *str, ssize_t len)
-{
-    static char dump_buf[DUMP_STR_COUNT][DUMP_STR_BUF];
-    static size_t dump_idx = 0;
-    
-    // pick a buffer to use
-    char *buf = dump_buf[dump_idx++];
-    
-    // cycle
-    if (dump_idx >= DUMP_STR_COUNT)
-        dump_idx = 0;
-    
-    str_quote(buf, DUMP_STR_BUF, str, len);
-
-    // ok
-    return buf;
-}
-
-const char *dump_str (const char *str) 
-{
-    return dump_strn(str, -1);
-}
-
-void assert_null (const void *ptr)
-{
-    if (ptr)
-        FATAL("%p != NULL", ptr);
-}
-
-void assert_strcmp (const char *is, const char *should_be)
-{
-    if (!is || strcmp(is, should_be))
-        FATAL("%s != %s", dump_str(is), dump_str(should_be));
-}
-
-void assert_strncmp (const char *is, const char *should_be, size_t n)
-{   
-    if (!is || strncmp(is, should_be, n))
-        FATAL("%s:%u != %s", dump_strn(is, n), (unsigned) n, dump_strn(should_be, n));
-}
-
-void assert_strlen (const char *str, size_t n)
-{
-    if (!str || strlen(str) != n)
-        FATAL("strlen(%s) != %u", dump_str(str), (unsigned) n);
-}
-
-void assert_strnull (const char *str)
-{
-    if (str != NULL)
-        FATAL("%s != NULL", dump_str(str));
-}
-
-void assert_success (err_t err)
-{
-    if (err != SUCCESS)
-        FATAL("error: %s", error_name(err));
-}
-
-void assert_err (err_t err, err_t target)
-{
-    if (err != target)
-        FATAL("error: <%s> != <%s>", error_name(err), error_name(target));
-}
-
-void assert_error_info (struct error_info *is, struct error_info *should_be)
-{
-    if (ERROR_CODE(is) != ERROR_CODE(should_be) || ERROR_EXTRA(is) != ERROR_EXTRA(should_be))
-        FATAL("error: <%s> != <%s>", error_msg(is), error_msg(should_be));
-}
-
-void assert_sock_read (struct sock_stream *sock, const char *str)
-{
-    char buf[strlen(str)];
-
-    log_debug("read: %p: %s", sock, dump_str(str));
-    
-    // read it
-    assert(sock_stream_read(sock, buf, strlen(str)) == (int) strlen(str));
-
-    // cmp
-    assert_strncmp(buf, str, strlen(str));
-}
-
-void assert_sock_write (struct sock_stream *sock, const char *str)
-{
-    log_debug("write: %p: %s", sock, dump_str(str));
-
-    // write it
-    assert(sock_stream_write(sock, str, strlen(str)) == (int) strlen(str));
-}
-
-void assert_sock_eof (struct sock_stream *sock)
-{
-    char buf;
-
-    log_debug("eof: %p", sock);
-
-    assert_err(-sock_stream_read(sock, &buf, 1), ERR_READ_EOF);
-}
-
-/**
- * Maximum amount that can be pushed using test_sock_push
- */
-#define TEST_SOCK_FMT_MAX 1024
-
-void assert_sock_data (struct sock_test *sock, const char *fmt, ...)
-{
-    char buf[TEST_SOCK_FMT_MAX];
-    va_list vargs;
-    size_t len;
-    
-    va_start(vargs, fmt);
-
-    if ((len = vsnprintf(buf, TEST_SOCK_FMT_MAX, fmt, vargs)) >= TEST_SOCK_FMT_MAX)
-        FATAL("input too long: %zu bytes", len);
-
-    va_end(vargs);
-
-    // get the data out
-    char *out;
-    
-    sock_test_get_send_data(sock, &out, &len);
-    
-    log_debug("get_send_data: %s", dump_strn(out, len));
-    
-    // should be the same
-    assert_strncmp(out, buf, len);
-    assert_strlen(buf, len);
-
-    // cleanup
-    free(out);
-}
-
-/**
- * Nicer name for test_sock_add_recv_str, also supports formatted data.
- *
- * The formatted result is limited to TEST_SOCK_PUSH_MAX bytes
- */
-void test_sock_push (struct sock_test *sock, const char *fmt, ...)
-{
-    char buf[TEST_SOCK_FMT_MAX];
-    va_list vargs;
-    size_t len;
-    
-    va_start(vargs, fmt);
-
-    if ((len = vsnprintf(buf, TEST_SOCK_FMT_MAX, fmt, vargs)) >= TEST_SOCK_FMT_MAX)
-        FATAL("output too long: %zu bytes", len);
-
-    va_end(vargs);
-
-    return sock_test_add_recv_str(sock, buf);
-}
-
-/**
- * Setup the global sock_stream state
- */
-struct event_base* setup_sock (void)
-{
-    struct event_base *ev_base;
-    struct error_info err;
-
-    assert((ev_base = event_base_new()));
-    assert_success(sock_init(ev_base, &err));
-
-    return ev_base;
-}
-
-/**
- * Create an empty sock_test
- */
-struct sock_test* setup_sock_test (void)
-{
-    struct sock_test *sock;
-   
-    assert ((sock = sock_test_create()) != NULL);
-
-    return sock;
-}
-
-void assert_str_quote (size_t buf_size, const char *data, ssize_t len, const char *target, size_t out)
-{
-    char buf[buf_size];
-    
-    size_t ret = str_quote(buf, buf_size, data, len);
-
-    log_debug("str_quote(%zu, %zd) -> %s:%zu / %s:%zu", buf_size, len, buf, ret, target, out);
-
-    assert_strcmp(buf, target);
-    assert(ret == out);
-}
-
-void test_str_quote (void)
-{
-    log_info("testing str_quote()");
-
-    assert_str_quote(5,     NULL,           -1,     "NULL",         4   );
-    assert_str_quote(16,    "foo",          -1,     "'foo'",        5   );
-    assert_str_quote(16,    "foobar",       3,      "'foo'",        5   );
-    assert_str_quote(16,    "\r\n",         -1,     "'\\r\\n'",     6   );
-    assert_str_quote(16,    "\x13",         -1,     "'\\x13'",      6   );
-    assert_str_quote(16,    "x'y",          -1,     "'x\\'y'",      6   );
-    assert_str_quote(7,     "1234567890",   -1,     "'1'...",       12  );
-    assert_str_quote(9,     "1234567890",   -1,     "'123'...",     12  );
-}
-
-struct str_format_ctx {
-    const char *name;
-
-    const char *value;
-};
-
-err_t test_str_format_cb (const char *name, const char **value, ssize_t *value_len, void *arg)
-{
-    struct str_format_ctx *ctx = arg;
-
-    assert_strcmp(name, ctx->name);
-
-    *value = ctx->value;
-    *value_len = -1;
-
-    return SUCCESS;
-}
-
-void assert_str_format (const char *format, const char *name, const char *value, const char *out)
-{
-    struct str_format_ctx ctx = { name, value };
-    char buf[512];
-
-    assert_success(str_format(buf, sizeof(buf), format, test_str_format_cb, &ctx));
-    
-    log_debug("str_format(%s), { %s:%s } -> %s / %s", format, name, value, buf, out);
-
-    assert_strcmp(buf, out);
-}
-
-void test_str_format (void)
-{
-    log_info("test str_format()");
-
-    assert_str_format("foo", NULL, NULL, "foo");
-    assert_str_format("foo {bar} quux", "bar", "XXX", "foo XXX quux");
-}
-
-void test_dump_str (void)
-{
-    log_info("dumping example strings on stdout:");
-
-    log_debug("normal: %s", dump_str("Hello World"));
-    log_debug("escapes: %s", dump_str("foo\r\nbar\a\001"));
-    log_debug("length: %s", dump_strn("<-->**", 4));
-    log_debug("overflow: %s", dump_str( "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"));
-    log_debug("null: %s", dump_str(NULL));
-    log_debug("quote: %s", dump_str("foo\\bar'quux"));
-}
-
-void test_sock_test (void)
-{
-    struct sock_test *sock = sock_test_create();
-    struct io_vec _read_data[] = {
-        {   "foo",      3   },
-        {   "barx",     4   }
-    };
-    const char *_write_data = "test data";
-    
-    // put the read data
-    log_debug("set_recv_buffer: %p, %d", _read_data, 2);
-    sock_test_set_recv_buffer(sock, _read_data, 2, true);
-    
-    // read it out
-    log_info("test sock_test_read");
-
-    assert_sock_read(SOCK_TEST_BASE(sock), "foo");
-    assert_sock_read(SOCK_TEST_BASE(sock), "ba");
-    assert_sock_read(SOCK_TEST_BASE(sock), "rx");
-    assert_sock_eof(SOCK_TEST_BASE(sock));
-
-    // write the data in
-    log_info("test sock_test_write");
-
-    assert_sock_write(SOCK_TEST_BASE(sock), "test ");
-    assert_sock_write(SOCK_TEST_BASE(sock), "data");
-    
-    // check output
-    assert_sock_data(sock, _write_data);
-
-    // check output
-    assert_sock_data(sock, "");
-
-    // cleanup
-    sock_test_destroy(sock);
-}
-
-void assert_read_line (struct line_proto *lp, const char *line_str)
-{
-    char *line_buf;
-    
-    log_debug("expect: %s", dump_str(line_str));
-
-    assert_success(line_proto_recv(lp, &line_buf));
-
-    if (line_str) {
-        assert_strcmp(line_buf, line_str);
-
-    } else {
-        assert_strnull(line_buf);
-
-    }
-}
-
-/**
- * Context info for test_line_proto callbacks
- */
-struct _lp_test_ctx {
-    /** Expected line */
-    const char *line;
-
-    /** Expected error */
-    struct error_info err;
-};
-
-static void _lp_on_line (char *line, void *arg)
-{
-    struct _lp_test_ctx *ctx = arg;
-
-    log_debug("%s", dump_str(line));
-
-    assert_strcmp(line, ctx->line);
-
-    ctx->line = NULL;
-}
-
-static void _lp_on_error (struct error_info *err, void *arg)
-{
-    struct _lp_test_ctx *ctx = arg;
-
-    assert_error_info(err, &ctx->err);
-}
-
-static struct line_proto_callbacks _lp_callbacks = {
-    .on_line        = &_lp_on_line,
-    .on_error       = &_lp_on_error,
-};
-
-void test_line_proto (void)
-{
-    struct sock_test *sock = sock_test_create();
-    struct io_vec _read_data[] = {
-        {   "hello\r\n",    7   },
-        {   "world\n",      6   },
-        {   "this ",        5   },
-        {   "is a line\r",  10  },
-        {   "\nfragment",   9   },
-    }, _trailing_data = {   "\r\n",     2 };
-    struct line_proto *lp;
-    struct _lp_test_ctx ctx;
-    struct error_info err;
-    
-    // put the read data
-    log_debug("set_recv_buffer: %p, %d", _read_data, 5);
-    sock_test_set_recv_buffer(sock, _read_data, 5, false);
-    
-    // create the lp
-    assert_success(line_proto_create(&lp, SOCK_TEST_BASE(sock), 128, &_lp_callbacks, &ctx, &err));
-    
-    log_info("test line_proto_recv");
-
-    // then read some lines from it
-    assert_read_line(lp, "hello");
-    assert_read_line(lp, "world");
-    assert_read_line(lp, "this is a line");
-    assert_read_line(lp, NULL);
-
-    // then add a final bit to trigger on_line
-    log_info("test on_line");
-
-    ctx.line = "fragment";
-    sock_test_add_recv_vec(sock, _trailing_data);
-    assert_strnull(ctx.line);
-
-    // test writing
-    log_info("test line_proto_send");
-    assert_success(-line_proto_send(lp, "foobar\r\n"));
-    assert_success(-line_proto_send(lp, "quux\r\n"));
-    assert_sock_data(sock, "foobar\r\nquux\r\n");
-
-    // XXX: test partial writes
-
-    // cleanup
-    line_proto_release(lp);
-}
-
-void test_irc_queue (void)
-{
-    struct sock_test *sock = sock_test_create();
-    struct line_proto *lp;
-    struct irc_queue *queue;
-    struct irc_queue_entry *queue_entry;
-    struct error_info err;
-
-    // create the lp
-    assert_success(line_proto_create(&lp, SOCK_TEST_BASE(sock), 128, &_lp_callbacks, NULL, &err));
-
-    // create the queue
-    assert_success(irc_queue_create(&queue, lp, &err));
-
-    struct irc_line line = {
-        NULL, "TEST", { "fooX" }
-    };
-
-    // then test simple writes, we should be able to push five lines directly
-    log_info("test irc_queue_process (irc_queue_send_direct)");
-    line.args[0] = "foo0"; assert_success(irc_queue_process(queue, &line));
-    line.args[0] = "foo1"; assert_success(irc_queue_process(queue, &line));
-    line.args[0] = "foo2"; assert_success(irc_queue_process(queue, &line));
-    line.args[0] = "foo3"; assert_success(irc_queue_process(queue, &line));
-    line.args[0] = "foo4"; assert_success(irc_queue_process(queue, &line));
-
-    // they should all be output
-    assert_sock_data(sock, 
-            "TEST foo0\r\n"
-            "TEST foo1\r\n"
-            "TEST foo2\r\n"
-            "TEST foo3\r\n"
-            "TEST foo4\r\n"
-    );
-
-    // then enqueue
-    log_info("test irc_queue_process (irc_queue_put)");
-    line.args[0] = "foo5"; assert_success(irc_queue_process(queue, &line));
-
-    // ensure it was enqueued
-    assert((queue_entry = TAILQ_FIRST(&queue->list)) != NULL);
-    assert_strcmp(queue_entry->line_buf, "TEST foo5\r\n");
-
-    // ensure timer is set
-    assert(event_pending(queue->ev, EV_TIMEOUT, NULL));
-
-    // run the event loop to let the timer run
-    log_info("running the event loop once...");
-    assert(event_base_loop(_test_ctx.ev_base, EVLOOP_ONCE) == 0);
-
-    // test to check that the line was now sent
-    log_info("checking that the delayed line was sent...");
-    assert_sock_data(sock, "TEST foo5\r\n");
-    assert(TAILQ_EMPTY(&queue->list));
-    assert(!event_pending(queue->ev, EV_TIMEOUT, NULL));
-
-    // cleanup
-    irc_queue_destroy(queue);
-}
-
-struct test_conn_ctx {
-    /** Callback flags */
-    bool on_registered, on_TEST, on_error, on_quit;
-};
-
-static void _conn_on_registered (struct irc_conn *conn, void *arg)
-{
-    struct test_conn_ctx *ctx = arg;
-
-    (void) conn;
-
-    if (ctx) ctx->on_registered = true;
-
-    log_debug("registered");
-}
-
-static void _conn_on_error (struct irc_conn *conn, struct error_info *err, void *arg)
-{
-    struct test_conn_ctx *ctx = arg;
-    
-    (void) conn;
-    (void) err;
-
-    if (ctx) ctx->on_error = true;
-
-    log_debug("on_error");
-}
-
-static void _conn_on_quit (struct irc_conn *conn, void *arg)
-{
-    struct test_conn_ctx *ctx = arg;
-
-    (void) conn;
-
-    if (ctx) ctx->on_quit = true;
-
-    log_debug("on_quit");
-}
-
-static void _conn_on_TEST (const struct irc_line *line, void *arg)
-{
-    struct test_conn_ctx *ctx = arg;
-
-    assert_null(line->source);
-    assert_strcmp(line->command, "TEST");
-    assert_strcmp(line->args[0], "arg0");
-    assert_strnull(line->args[1]);
-
-    if (ctx) ctx->on_TEST = true;
-
-    log_debug("on_TEST");
-}
-
-static struct irc_conn_callbacks _conn_callbacks = {
-    .on_registered      = &_conn_on_registered,
-    .on_error           = &_conn_on_error,
-    .on_quit            = &_conn_on_quit,
-};
-
-static struct irc_cmd_handler _conn_handlers[] = {
-    {   "TEST",         &_conn_on_TEST  },
-    {   NULL,           NULL            }
-};
-
-/**
- * Create and return a new irc_conn with the given ctx (will be initialized to zero).
- */
-struct irc_conn* setup_irc_conn (struct sock_test *sock, bool noisy, struct test_conn_ctx *ctx)
-{
-    struct irc_conn *conn;
-    struct error_info err;
-    struct irc_conn_register_info register_info = {
-        "nick", "user", "realname"
-    };
-
-    // init the ctx
-    memset(ctx, 0, sizeof(*ctx));
-
-    // create the irc_conn
-    assert_success(irc_conn_create(&conn, SOCK_TEST_BASE(sock), &_conn_callbacks, ctx, &err));
-
-    // test register
-    if (noisy) log_info("test irc_conn_register");
-    assert_success(irc_conn_register(conn, &register_info));
-    assert_sock_data(sock, "NICK nick\r\nUSER user 0 * realname\r\n");
- 
-    // test on_register callback    
-    if (noisy) log_info("test irc_conn_callbacks.on_register");
-    test_sock_push(sock, "001 mynick :Blaa blaa blaa\r\n");
-    if (ctx) assert(ctx->on_registered);
-    assert_strcmp(conn->nickname, "mynick");
-   
-    // ok
-    return conn;
-}
-
-void test_irc_conn (void)
-{
-    struct test_conn_ctx ctx;
-    struct sock_test *sock = setup_sock_test();
-    struct irc_conn *conn = setup_irc_conn(sock, true, &ctx);
-
-    // add our test handlers
-    assert_success(irc_conn_add_cmd_handlers(conn, _conn_handlers, &ctx));
-
-    // test on_TEST handler
-    // XXX: come up with a better prefix
-    log_info("test irc_conn.handlers");
-    test_sock_push(sock, ":foobar-prefix TEST arg0\r\n");
-    assert(ctx.on_TEST);
-
-    // test PING/PONG
-    log_info("test PING/PONG");
-    test_sock_push(sock, "PING foo\r\n");
-    assert_sock_data(sock, "PONG foo\r\n");
-
-    // quit nicely
-    log_info("test QUIT");
-    assert_success(irc_conn_QUIT(conn, "bye now"));
-    assert_sock_data(sock, "QUIT :bye now\r\n");
-    assert(conn->quitting);
-
-    test_sock_push(sock, "ERROR :Closing Link: Quit\r\n");
-    sock_test_set_recv_eof(sock);
-    assert(conn->quit && !conn->quitting && !conn->registered);
-    assert(ctx.on_quit);
-    assert(!ctx.on_error);
-
-    // destroy it
-    irc_conn_destroy(conn);
-}
-
-void test_irc_conn_self_nick (void)
-{
-    struct test_conn_ctx ctx;
-    struct sock_test *sock = setup_sock_test();
-    struct irc_conn *conn = setup_irc_conn(sock, false, &ctx);
-    
-    log_info("test irc_conn_on_NICK");
-    test_sock_push(sock, ":mynick!user@somehost NICK mynick2\r\n");
-    assert_strcmp(conn->nickname, "mynick2");
-
-    // cleanup
-    irc_conn_destroy(conn);
-}
-
-struct test_chan_ctx {
-    /** The channel name */
-    const char *channel;
-
-    /** The channel we're supposed to be testing */
-    struct irc_chan *chan;
-    
-    /** Flags for callbacks called*/
-    bool on_chan_self_join, on_chan_self_part, on_chan_join, on_chan_part;
-
-};
-
-void _on_chan_self_join (struct irc_chan *chan, void *arg)
-{
-    struct test_chan_ctx *ctx = arg;
-
-    assert(chan == ctx->chan);
-    
-    ctx->on_chan_self_join = true;
-
-    log_debug("on_self_join");
-}
-
-void _on_chan_join (struct irc_chan *chan, const struct irc_nm *source, void *arg)
-{
-    struct test_chan_ctx *ctx = arg;
-
-    assert(chan == ctx->chan);
-
-    // XXX: verify source
-
-    ctx->on_chan_join = true;
-
-    log_debug("on_join");
-}
-
-void _on_chan_part (struct irc_chan *chan, const struct irc_nm *source, const char *msg, void *arg)
-{
-    struct test_chan_ctx *ctx = arg;
-
-    assert(chan == ctx->chan);
-
-    // XXX: verify source
-    // XXX: verify msg
-
-    ctx->on_chan_part = true;
-
-    log_debug("on_part");
-}
-
-
-struct irc_chan_callbacks _chan_callbacks = {
-    .on_self_join       = &_on_chan_self_join,
-    .on_join            = &_on_chan_join,
-    .on_part            = &_on_chan_part,
-};
-
-/**
- * Setup an irc_net using the given socket, and consume the register request output, but do not push the RPL_WELCOME
- */
-struct irc_net* setup_irc_net_unregistered (struct sock_test *sock)
-{
-    struct irc_net *net;
-    struct irc_net_info net_info = {
-        .register_info = {
-            "nick", "user", "realname"
-        },
-    };
-    struct error_info err;
-
-    // create the irc_net
-    net_info.raw_sock = SOCK_TEST_BASE(sock);
-    assert_success(irc_net_create(&net, &net_info, &err));
-
-    // test register output
-    assert_sock_data(sock, "NICK nick\r\nUSER user 0 * realname\r\n");
-    
-    // ok
-    return net; 
-}
-
-/**
- * Push to RPL_WELCOME reply, and test state
- */
-void do_irc_net_welcome (struct sock_test *sock, struct irc_net *net)
-{
-    // registration reply
-    test_sock_push(sock, "001 mynick :Blaa blaa blaa\r\n");
-    assert(net->conn->registered);
-    assert_strcmp(net->conn->nickname, "mynick");
-
-}
-
-/**
- * Creates an irc_net and puts it into the registered state
- */
-struct irc_net* setup_irc_net (struct sock_test *sock)
-{
-    struct irc_net *net;   
-
-    net = setup_irc_net_unregistered(sock);
-    do_irc_net_welcome(sock, net);
-    
-    // ok
-    return net;
-}
-
-/**
- * General test for irc_net to handle startup
- */
-void test_irc_net (void)
-{
-    struct sock_test *sock = setup_sock_test();
-    
-    // create the network
-    log_info("test irc_net_create");
-    struct irc_net *net = setup_irc_net_unregistered(sock);
-
-    // send the registration reply
-    log_info("test irc_conn_on_RPL_WELCOME");
-    do_irc_net_welcome(sock, net);
-
-    // test errors by setting EOF
-    log_info("test irc_net_error");
-    sock_test_set_recv_eof(sock);
-    assert(net->conn == NULL);
-
-    // cleanup
-    irc_net_destroy(net);
-}
-
-/**
- * Ensure that an irc_chan_user exists/doesn't exist for the given channel/nickname, and return it
- */
-struct irc_chan_user* check_chan_user (struct irc_chan *chan, const char *nickname, bool exists)
-{
-    struct irc_chan_user *chan_user = irc_chan_get_user(chan, nickname);
-    
-    if (exists && chan_user == NULL)
-        FATAL("user %s not found in channel %s", dump_str(nickname), dump_str(irc_chan_name(chan)));
-    
-    if (!exists && chan_user)
-        FATAL("user %s should not be on channel %s anymore", dump_str(nickname), dump_str(irc_chan_name(chan)));
-
-    log_debug("%s, exists=%d -> %p: user=%p, nickname=%s", 
-            nickname, exists, chan_user, chan_user ? chan_user->user : NULL, chan_user ? chan_user->user->nickname : NULL);
-    
-    if (chan_user)
-        assert_strcmp(chan_user->user->nickname, nickname);
-
-    return chan_user;
-}
-
-/**
- * Creates an irc_chan on the given irc_net, but does not check any output (useful for testing offline add).
- *
- * You must pass a test_chan_ctx for use with later operations, this will be initialized for you.
- */
-struct irc_chan* setup_irc_chan_raw (struct irc_net *net, const char *channel, struct test_chan_ctx *ctx)
-{
-    struct irc_chan *chan;
-    struct irc_chan_info chan_info = {
-        .channel        = channel,
-    };
-    struct error_info err;
-    
-    // initialize the given ctx
-    memset(ctx, 0, sizeof(*ctx));
-    ctx->channel = channel;
-
-    // add a channel
-    assert_success(irc_net_add_chan(net, &chan, &chan_info, &err));
-    assert(!chan->joined);
-    assert_success(irc_chan_add_callbacks(chan, &_chan_callbacks, ctx));
-    ctx->chan = chan;
-    
-    // ok
-    return chan;
-}
-
-/**
- * Checks that the JOIN request for a channel was sent, and sends the basic JOIN reply
- */
-void do_irc_chan_join (struct sock_test *sock, struct test_chan_ctx *ctx)
-{
-    // JOIN request
-    assert(ctx->chan->joining);
-    assert_sock_data(sock, "JOIN %s\r\n", ctx->channel);
-
-    // JOIN reply
-    test_sock_push(sock, ":mynick!user@host JOIN %s\r\n", ctx->channel);
-    assert(!ctx->chan->joining && ctx->chan->joined);
-    assert(ctx->on_chan_self_join);
-}
-
-/**
- * Sends a short RPL_NAMREPLY/RPL_ENDOFNAMES reply and checks that the users list matches
- */
-void do_irc_chan_namreply (struct sock_test *sock, struct test_chan_ctx *ctx)
-{
-    // RPL_NAMREPLY
-    test_sock_push(sock, "353 mynick = %s :mynick userA +userB @userC\r\n", ctx->channel);
-    test_sock_push(sock, "353 mynick = %s :trailingspace \r\n", ctx->channel);
-    test_sock_push(sock, "366 mynick %s :End of NAMES\r\n", ctx->channel);
-    
-    // XXX: this should be an exclusive test, i.e. these should be the only ones...
-    check_chan_user(ctx->chan, "mynick", true);
-    check_chan_user(ctx->chan, "userA", true);
-    check_chan_user(ctx->chan, "userB", true);
-    check_chan_user(ctx->chan, "userC", true);
-}
-
-/**
- * Creates an irc_chan on the given irc_net, and checks up to the JOIN reply
- */
-struct irc_chan* setup_irc_chan_join (struct sock_test *sock, struct irc_net *net, const char *channel, struct test_chan_ctx *ctx)
-{
-    setup_irc_chan_raw(net, channel, ctx);
-    do_irc_chan_join(sock, ctx);
-
-    // ok
-    return ctx->chan;
-}
-
-/**
- * Creates an irc_chan on the given irc_net, sends the JOIN stuff plus RPL_NAMREPLY
- */
-struct irc_chan* setup_irc_chan (struct sock_test *sock, struct irc_net *net, const char *channel, struct test_chan_ctx *ctx)
-{
-    setup_irc_chan_raw(net, channel, ctx);
-    do_irc_chan_join(sock, ctx);
-    do_irc_chan_namreply(sock, ctx);
-
-    // ok
-    return ctx->chan;
-}
-
-
-/**
- * Call irc_net_add_chan while offline, and ensure that we send the JOIN request after RPL_WELCOME, and handle the join
- * reply OK.
- */
-void test_irc_chan_add_offline (void)
-{
-    struct test_chan_ctx ctx;
-
-    struct sock_test *sock = setup_sock_test();
-
-    log_info("test irc_net_create");
-    struct irc_net *net = setup_irc_net_unregistered(sock);
-
-    // add an offline channel
-    log_info("test offline irc_net_add_chan");
-    struct irc_chan *chan = setup_irc_chan_raw(net, "#test", &ctx);
-    assert(!chan->joining && !chan->joined);
-
-    // send the registration reply
-    log_info("test irc_conn_on_RPL_WELCOME");
-    do_irc_net_welcome(sock, net);
-    
-    // test the join sequence
-    log_info("test irc_chan_join/irc_chan_on_JOIN");
-    do_irc_chan_join(sock, &ctx);
-    
-    // cleanup
-    irc_net_destroy(net);
-}
-
-void test_irc_chan_namreply (void)
-{
-    struct test_chan_ctx ctx;
-    struct sock_test *sock = setup_sock_test();
-    struct irc_net *net = setup_irc_net(sock);
-    setup_irc_chan_join(sock, net, "#test", &ctx);
-
-    log_info("test irc_chan_on_RPL_NAMREPLY");
-    do_irc_chan_namreply(sock, &ctx);
-
-    // cleanup
-    irc_net_destroy(net);
-}
-
-void test_irc_chan_user_join (void)
-{
-    struct test_chan_ctx ctx;
-    struct sock_test *sock = setup_sock_test();
-    struct irc_net *net = setup_irc_net(sock);
-    struct irc_chan *chan = setup_irc_chan(sock, net, "#test", &ctx);
-
-    // have a user join
-    log_info("test irc_chan_on_JOIN");
-    test_sock_push(sock, ":newuser!someone@somewhere JOIN %s\r\n", "#test");
-    assert(ctx.on_chan_join);
-    check_chan_user(chan, "newuser", true);
-
-    // cleanup
-    irc_net_destroy(net);
-}
-
-void test_irc_chan_user_part (void)
-{
-    struct test_chan_ctx ctx;
-    struct sock_test *sock = setup_sock_test();
-    struct irc_net *net = setup_irc_net(sock);
-    struct irc_chan *chan = setup_irc_chan(sock, net, "#test", &ctx);
-
-    // have a user join
-    log_info("test irc_chan_on_PART");
-    test_sock_push(sock, ":userA!someone@somewhere PART %s\r\n", "#test");
-    assert(ctx.on_chan_part); ctx.on_chan_part = NULL;
-    check_chan_user(chan, "userA", false);
-
-    // cleanup
-    irc_net_destroy(net);
-}
-
-void test_irc_chan_user_kick (void)
-{
-    struct test_chan_ctx ctx;
-    struct sock_test *sock = setup_sock_test();
-    struct irc_net *net = setup_irc_net(sock);
-    struct irc_chan *chan = setup_irc_chan(sock, net, "#test", &ctx);
-
-    // kick a user
-    log_info("test irc_chan_on_KICK (other)");
-    test_sock_push(sock, ":userA!someone@somewhere KICK %s userB\r\n", "#test");
-    check_chan_user(chan, "userA", true);
-    check_chan_user(chan, "userB", false);
-
-    // cleanup
-    irc_net_destroy(net);
-}
-
-void test_irc_chan_self_kick (void)
-{
-    struct test_chan_ctx ctx;
-    struct sock_test *sock = setup_sock_test();
-    struct irc_net *net = setup_irc_net(sock);
-    struct irc_chan *chan = setup_irc_chan(sock, net, "#test", &ctx);
-
-    // kick a user
-    log_info("test irc_chan_on_KICK (self)");
-    test_sock_push(sock, ":userA!someone@somewhere KICK %s mynick foobar\r\n", "#test");
-    assert(!chan->joined);
-    assert(chan->kicked);
-
-    // cleanup
-    irc_net_destroy(net);
-}
-
-void test_irc_chan_user_nick (void)
-{
-    struct test_chan_ctx ctx;
-    struct sock_test *sock = setup_sock_test();
-    struct irc_net *net = setup_irc_net(sock);
-    struct irc_chan *chan = setup_irc_chan(sock, net, "#test", &ctx);
-
-    // rename one of the users
-    log_info("test irc_net_on_chanuser");
-    test_sock_push(sock, ":userA!someone@somewhere NICK userA2\r\n");
-    check_chan_user(chan, "userA", false);
-    check_chan_user(chan, "userB", true);
-
-    // cleanup
-    irc_net_destroy(net);
-}
-
-void test_irc_chan_user_quit (void)
-{
-    struct test_chan_ctx ctx;
-    struct sock_test *sock = setup_sock_test();
-    struct irc_net *net = setup_irc_net(sock);
-    struct irc_chan *chan = setup_irc_chan(sock, net, "#test", &ctx);
-
-    // rename one of the users
-    log_info("test irc_net_on_chanuser");
-    test_sock_push(sock, ":userA!someone@somewhere QUIT foo\r\n");
-    check_chan_user(chan, "userA", false);
-
-    // cleanup
-    irc_net_destroy(net);
-}
-
-void _test_irc_chan_on_CTCP_ACTION (const struct irc_line *line, void *arg)
-{
-    bool *flag = arg;
-
-    log_debug("CTCP ACTION");
-
-    *flag = true;
-}
-
-static struct irc_cmd_handler _test_irc_chan_handlers[] = {
-    {   "CTCP ACTION",      &_test_irc_chan_on_CTCP_ACTION  },
-    {   NULL,               NULL                            }
-};
-
-void test_irc_chan_CTCP_ACTION (void)
-{
-    struct test_chan_ctx ctx;
-    struct sock_test *sock = setup_sock_test();
-    struct irc_net *net = setup_irc_net(sock);
-    struct irc_chan *chan = setup_irc_chan(sock, net, "#test", &ctx);
-    bool cb_ok = false;
-
-    // add our handler
-    assert_success(irc_cmd_add(&chan->handlers, _test_irc_chan_handlers, &cb_ok));
-
-    // rename one of the users
-    log_info("test irc_conn_on_CTCP_ACTION");
-    test_sock_push(sock, ":userA!someone@somewhere PRIVMSG #test \001ACTION hello  world\001\r\n");
-    assert(cb_ok);
-
-    // cleanup
-    irc_net_destroy(net);
-}
-
-void test_irc_chan_privmsg (void)
-{
-    struct test_chan_ctx ctx;
-    struct sock_test *sock = setup_sock_test();
-    struct irc_net *net = setup_irc_net(sock);
-    struct irc_chan *chan = setup_irc_chan(sock, net, "#test", &ctx);
-
-    // rename one of the users
-    log_info("test irc_chan_PRIVMSG");
-    assert_success(irc_chan_PRIVMSG(chan, "foobar quux"));
-    assert_sock_data(sock, "PRIVMSG #test :foobar quux\r\n");
-
-    // cleanup
-    irc_net_destroy(net);
-}
-
-// XXX: needs to be split off into its own test_fifo.c
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-#include <unistd.h>
-struct test_fifo_ctx {
-    /** Path to the fifo */
-    const char *path;
-
-    /** The write end */
-    int fd;
-
-    /** callback invoked? */
-    bool on_read;
-
-    /** Still running? */
-    bool run;
-};
-
-/**
- * Open the FIFO and write the test string to it
- */
-static void test_fifo_open_write (struct test_fifo_ctx *ctx)
-{
-    // ...raw, for writing
-    if ((ctx->fd = open(ctx->path, O_WRONLY)) < 0)
-        FATAL_PERROR("open");
-
-    // write something into it
-    if (write(ctx->fd, "test", 4) != 4)
-        FATAL_PERROR("write");
- 
-}
-
-static void test_fifo_close (struct test_fifo_ctx *ctx)
-{
-    close(ctx->fd);
-    ctx->fd = -1;
-}
-
-static void test_fifo_on_read (struct sock_stream *fifo, void *arg)
-{
-    int ret;
-    char buf[16];
-    struct test_fifo_ctx *ctx = arg;
-
-    // read it back out
-    log_info("test fifo_read");
-    if ((ret = sock_stream_read(fifo, buf, 16)) < 0)
-        assert_success(-ret);
-
-    assert(ret == 4);
-    assert_strncmp(buf, "test", 4);
- 
-    if (ctx->on_read) {
-        test_fifo_close(ctx);
-        ctx->run = false;
-        return;
-    }
-   
-    // re-open the fifo
-    log_info("test fifo-re-open");
-    test_fifo_close(ctx);
-    test_fifo_open_write(ctx);
-
-    assert_success(sock_stream_event_enable(fifo, EV_READ));
-        
-    ctx->on_read = true;
-}
-
-static struct sock_stream_callbacks test_fifo_callbacks = {
-    .on_read = test_fifo_on_read,
-};
-
-void test_fifo (void)
-{
-    struct sock_stream *fifo;
-    struct error_info err;
-    struct test_fifo_ctx _ctx, *ctx = &_ctx; memset(ctx, 0, sizeof(*ctx));
-
-    // XXX: requires that this be run in a suitable CWD
-    ctx->path = "test.fifo";
-
-    // create the fifo
-    if ((mkfifo(ctx->path, 0600) < 0) && (errno != EEXIST))
-        FATAL_PERROR("mkfifo");
-
-    // open it
-    log_info("test fifo_open_read");
-    assert_success(fifo_open_read(&fifo, ctx->path, &err));
-    assert_success(sock_stream_event_init(fifo, &test_fifo_callbacks, ctx));
-    assert_success(sock_stream_event_enable(fifo, EV_READ));
-    
-    // put some data into it
-    test_fifo_open_write(ctx);
-   
-    // run the event loop
-    log_debug("running the event loop...");
-    ctx->run = true;
-
-    while (ctx->run)
-        assert(event_base_loop(_test_ctx.ev_base, EVLOOP_ONCE) == 0);
-
-    // check
-    assert(ctx->fd < 0);
-    
-    // cleanup
-    sock_stream_release(fifo);
-}
-
-/**
- * Test definition
- */
-struct test {
-    /** Test name */
-    const char *name;
-
-    /** Test func */
-    void (*func) (void);
-    
-    bool optional;
-};
-
-#define DEF_TEST(name) { #name, &test_ ## name, false }
-#define DEF_TEST_OPTIONAL(name) { #name, &test_ ## name, true }
-#define DEF_TEST_END { NULL, NULL, false }
-
-static struct test _tests[] = {
-    DEF_TEST(           str_quote                   ),
-    DEF_TEST(           str_format                  ),
-    DEF_TEST(           dump_str                    ),
-    DEF_TEST(           sock_test                   ),
-    DEF_TEST(           line_proto                  ),
-    DEF_TEST(           irc_queue                   ),
-    // XXX: irc_line_parse_invalid_prefix
-    DEF_TEST(           irc_conn                    ),
-    DEF_TEST(           irc_conn_self_nick          ),
-    DEF_TEST(           irc_net                     ),
-    DEF_TEST(           irc_chan_add_offline        ),
-    DEF_TEST(           irc_chan_namreply           ),
-    DEF_TEST(           irc_chan_user_join          ),
-    DEF_TEST(           irc_chan_user_part          ),
-    DEF_TEST(           irc_chan_user_kick          ),
-    DEF_TEST(           irc_chan_self_kick          ),
-    DEF_TEST(           irc_chan_user_nick          ),
-    DEF_TEST(           irc_chan_user_quit          ),
-    DEF_TEST(           irc_chan_CTCP_ACTION        ),
-    DEF_TEST(           irc_chan_privmsg            ),
-    DEF_TEST_OPTIONAL(  fifo                        ),
-    DEF_TEST_END
-};
-
-/**
- * Command-line option codes
- */
-enum option_code {
-    OPT_HELP            = 'h',
-    OPT_DEBUG           = 'd',
-    OPT_QUIET           = 'q',
-    OPT_LIST            = 'l',
-    
-    /** Options without short names */
-    _OPT_EXT_BEGIN      = 0x00ff,
-};
-
-/**
- * Command-line option definitions
- */
-static struct option options[] = {
-    {"help",            0,  NULL,   OPT_HELP        },
-    {"debug",           0,  NULL,   OPT_DEBUG       },
-    {"quiet",           0,  NULL,   OPT_QUIET       },
-    {"list",            0,  NULL,   OPT_LIST        },
-    {0,                 0,  0,      0               },
-};
-
-/**
- * Display --help output on stdout
- */
-static void usage (const char *exe) 
-{
-    printf("Usage: %s [OPTIONS]\n", exe);
-    printf("\n");
-    printf(" --help / -h            display this message\n");
-    printf(" --debug / -d           display DEBUG log messages\n");
-    printf(" --quiet / -q           supress INFO log messages\n");
-    printf(" --list / -l            list all tests\n");
-}
-
-static void list_tests (struct test *tests)
-{
-    struct test *test;
-    
-    printf("Available tests:\n");
-
-    for (test = tests; test->name; test++) {
-        printf("\t%s\n", test->name);
-    }
-}
-
-int main (int argc, char **argv)
-{
-    struct test *test;
-    size_t test_count = 0;
-    
-    int opt, option_index;
-    const char *filter = NULL;
-
-    // parse options
-    while ((opt = getopt_long(argc, argv, "hdql", options, &option_index)) != -1) {
-        switch (opt) {
-            case OPT_HELP:
-                usage(argv[0]);
-                exit(EXIT_SUCCESS);
-            
-            case OPT_DEBUG:
-                set_log_level(LOG_DEBUG);
-                break;
-
-            case OPT_QUIET:
-                set_log_level(LOG_WARN);
-                break;
-           
-            case OPT_LIST:
-                list_tests(_tests);
-                exit(EXIT_SUCCESS);
-
-            case '?':
-                usage(argv[0]);
-                exit(EXIT_FAILURE);
-        }
-    }
-
-    if (optind < argc) {
-        if (optind == argc - 1) {
-            // filter
-            filter = argv[optind];
-            
-            log_info("only running tests: %s", filter);
-        } else {
-            FATAL("too many arguments");
-        }
-    }
-
-    // setup the sockets stuff
-    _test_ctx.ev_base = setup_sock();
-
-    // run tests
-    for (test = _tests; test->name; test++) {
-        if ((filter && strcmp(test->name, filter)) || (!filter && test->optional))
-            continue;
-
-        log_info("Running test: %s", test->name);
-        
-        test_count++;
-        test->func();
-    }
-
-    // no tests run?
-    if (test_count == 0)
-        FATAL("no tests run");
-
-    log_info("done, ran %zu tests", test_count);
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/test/assert.c	Mon May 04 20:55:43 2009 +0300
@@ -0,0 +1,63 @@
+#include "assert.h"
+#include "util.h"
+
+#include <string.h>
+
+#define ASSERT_FAIL(...) do { log_fatal(__VA_ARGS__); abort(); } while (0)
+
+void assert_true (bool cond, const char *msg)
+{
+    if (!cond)
+        ASSERT_FAIL("%s", msg);
+}
+
+void assert_null (const void *ptr)
+{
+    if (ptr)
+        ASSERT_FAIL("%p != NULL", ptr);
+}
+
+void assert_strcmp (const char *is, const char *should_be)
+{
+    if (!is || strcmp(is, should_be))
+        ASSERT_FAIL("%s != %s", dump_str(is), dump_str(should_be));
+}
+
+void assert_strncmp (const char *is, const char *should_be, size_t n)
+{   
+    if (!is || strncmp(is, should_be, n))
+        ASSERT_FAIL("%s:%u != %s", dump_strn(is, n), (unsigned) n, dump_strn(should_be, n));
+}
+
+void assert_strlen (const char *str, size_t n)
+{
+    if (!str || strlen(str) != n)
+        ASSERT_FAIL("strlen(%s) != %u", dump_str(str), (unsigned) n);
+}
+
+void assert_strnull (const char *str)
+{
+    if (str != NULL)
+        ASSERT_FAIL("%s != NULL", dump_str(str));
+}
+
+void assert_success (err_t err)
+{
+    if (err != SUCCESS)
+        ASSERT_FAIL("error: %s", error_name(err));
+}
+
+void assert_err (err_t err, err_t target)
+{
+    if (err != target)
+        ASSERT_FAIL("error: <%s> != <%s>", error_name(err), error_name(target));
+}
+
+void assert_error (error_t *is, error_t *should_be)
+{
+    if (ERROR_CODE(is) != ERROR_CODE(should_be) || ERROR_EXTRA(is) != ERROR_EXTRA(should_be))
+        // XXX: dual use of error_msg
+        ASSERT_FAIL("error: <%s> != <%s>", error_msg(is), error_msg(should_be));
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/test/assert.h	Mon May 04 20:55:43 2009 +0300
@@ -0,0 +1,63 @@
+#ifndef TEST_ASSERT_H
+#define TEST_ASSERT_H
+
+/**
+ * @file
+ *
+ * Various general assert-condition tests used to fail tests
+ */
+#include "../error.h"
+#include "../log.h"
+
+/*
+ * Also accept the existance of the system assert() function
+ */
+#include <assert.h>
+#include <stdbool.h>
+
+/**
+ * Assert that the given condition is true, and fail with the given error if not
+ */
+void assert_true (bool cond, const char *msg);
+
+/**
+ * Assert that the given pointer value is NULL.
+ */
+void assert_null (const void *ptr);
+
+/**
+ * Assert that the given NUL-terminated string matches the given target string exactly.
+ */
+void assert_strcmp (const char *is, const char *should_be);
+
+/**
+ * Assert that the first \a n chars of the first string matches the second string exactly.
+ */
+void assert_strncmp (const char *is, const char *should_be, size_t n);
+
+/**
+ * Assert that the given \a str is \a n chars long.
+ */
+void assert_strlen (const char *str, size_t n);
+
+/**
+ * Assert that the given \a str is NULL.
+ */
+void assert_strnull (const char *str);
+
+/**
+ * Assert that the given error code is SUCCESS.
+ */
+void assert_success (err_t err);
+
+/**
+ * Assert that the given actual error code \a err matches the expected error code \target.
+ */
+void assert_err (err_t err, err_t target);
+
+/**
+ * Assert that the given actual error \a is matches the expected error \a should_be
+ */ 
+void assert_error (error_t *is, error_t *should_be);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/test/fifo.c	Mon May 04 20:55:43 2009 +0300
@@ -0,0 +1,111 @@
+#include "test.h"
+#include "../fifo.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+struct test_fifo_ctx {
+    /** Path to the fifo */
+    const char *path;
+
+    /** The write end */
+    int fd;
+
+    /** callback invoked? */
+    bool on_read;
+
+    /** Still running? */
+    bool run;
+};
+
+/**
+ * Open the FIFO and write the test string to it
+ */
+static void test_fifo_open_write (struct test_fifo_ctx *ctx)
+{
+    // ...raw, for writing
+    if ((ctx->fd = open(ctx->path, O_WRONLY)) < 0)
+        FATAL_PERROR("open");
+
+    // write something into it
+    if (write(ctx->fd, "test", 4) != 4)
+        FATAL_PERROR("write");
+ 
+}
+
+static void test_fifo_close (struct test_fifo_ctx *ctx)
+{
+    close(ctx->fd);
+    ctx->fd = -1;
+}
+
+static void test_fifo_on_read (transport_t *fifo, void *arg)
+{
+    int ret;
+    char buf[16];
+    struct test_fifo_ctx *ctx = arg;
+    error_t err;
+
+    // read it back out
+    log_info("test fifo_read");
+    if ((ret = transport_read(fifo, buf, 16, &err)) < 0)
+        assert_success(-ret);
+
+    assert(ret == 4);
+    assert_strncmp(buf, "test", 4);
+ 
+    if (ctx->on_read) {
+        test_fifo_close(ctx);
+        ctx->run = false;
+        return;
+    }
+   
+    // re-open the fifo
+    log_info("test fifo-re-open");
+    test_fifo_close(ctx);
+    test_fifo_open_write(ctx);
+
+    ctx->on_read = true;
+}
+
+static struct transport_callbacks test_fifo_callbacks = {
+    .on_read = test_fifo_on_read,
+};
+
+void test_fifo (void)
+{
+    transport_t *fifo;
+    struct error_info err;
+    struct test_fifo_ctx _ctx, *ctx = &_ctx; memset(ctx, 0, sizeof(*ctx));
+    struct transport_info info = { &test_fifo_callbacks, ctx, TRANSPORT_READ };
+
+    // XXX: requires that this be run in a suitable CWD
+    ctx->path = "test.fifo";
+
+    // create the fifo
+    if ((mkfifo(ctx->path, 0600) < 0) && (errno != EEXIST))
+        FATAL_PERROR("mkfifo");
+
+    // open it
+    log_info("test fifo_open_read");
+    assert_success(fifo_open_read(&info, &fifo, _test_ctx.ev_base, ctx->path, &err));
+    
+    // put some data into it
+    test_fifo_open_write(ctx);
+   
+    // run the event loop
+    log_debug("running the event loop...");
+    ctx->run = true;
+
+    while (ctx->run)
+        assert(event_base_loop(_test_ctx.ev_base, EVLOOP_ONCE) == 0);
+
+    // check
+    assert(ctx->fd < 0);
+    
+    // cleanup
+    transport_destroy(fifo);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/test/irc_chan.c	Mon May 04 20:55:43 2009 +0300
@@ -0,0 +1,340 @@
+#include "irc_chan.h"
+#include "irc_net.h"
+#include "test.h"
+
+
+static void _on_chan_self_join (struct irc_chan *chan, void *arg)
+{
+    struct test_chan_ctx *ctx = arg;
+
+    assert(chan == ctx->chan);
+    
+    ctx->on_chan_self_join = true;
+
+    log_debug("on_self_join");
+}
+
+static void _on_chan_join (struct irc_chan *chan, const struct irc_nm *source, void *arg)
+{
+    struct test_chan_ctx *ctx = arg;
+
+    assert(chan == ctx->chan);
+
+    // XXX: verify source
+
+    ctx->on_chan_join = true;
+
+    log_debug("on_join");
+}
+
+static void _on_chan_part (struct irc_chan *chan, const struct irc_nm *source, const char *msg, void *arg)
+{
+    struct test_chan_ctx *ctx = arg;
+
+    assert(chan == ctx->chan);
+
+    // XXX: verify source
+    // XXX: verify msg
+
+    ctx->on_chan_part = true;
+
+    log_debug("on_part");
+}
+
+
+struct irc_chan_callbacks _chan_callbacks = {
+    .on_self_join       = _on_chan_self_join,
+    .on_join            = _on_chan_join,
+    .on_part            = _on_chan_part,
+};
+
+
+struct irc_chan_user* check_chan_user (struct irc_chan *chan, const char *nickname, bool exists)
+{
+    struct irc_chan_user *chan_user = irc_chan_get_user(chan, nickname);
+    
+    if (exists && chan_user == NULL)
+        FATAL("user %s not found in channel %s", dump_str(nickname), dump_str(irc_chan_name(chan)));
+    
+    if (!exists && chan_user)
+        FATAL("user %s should not be on channel %s anymore", dump_str(nickname), dump_str(irc_chan_name(chan)));
+
+    log_debug("%s, exists=%d -> %p: user=%p, nickname=%s", 
+            nickname, exists, chan_user, chan_user ? chan_user->user : NULL, chan_user ? chan_user->user->nickname : NULL);
+    
+    if (chan_user)
+        assert_strcmp(chan_user->user->nickname, nickname);
+
+    return chan_user;
+}
+
+/**
+ * Creates an irc_chan on the given irc_net, but does not check any output (useful for testing offline add).
+ *
+ * You must pass a test_chan_ctx for use with later operations, this will be initialized for you.
+ */
+static struct irc_chan* setup_irc_chan_raw (struct irc_net *net, const char *channel, struct test_chan_ctx *ctx)
+{
+    struct irc_chan *chan;
+    struct irc_chan_info chan_info = {
+        .channel        = channel,
+    };
+    struct error_info err;
+    
+    // initialize the given ctx
+    memset(ctx, 0, sizeof(*ctx));
+    ctx->channel = channel;
+
+    // add a channel
+    assert_success(irc_net_add_chan(net, &chan, &chan_info, &err));
+    assert(!chan->joined);
+    assert_success(irc_chan_add_callbacks(chan, &_chan_callbacks, ctx));
+    ctx->chan = chan;
+    
+    // ok
+    return chan;
+}
+
+/**
+ * Checks that the JOIN request for a channel was sent, and sends the basic JOIN reply
+ */
+static void do_irc_chan_join (struct transport_test *tp, struct test_chan_ctx *ctx)
+{
+    // JOIN request
+    assert(ctx->chan->joining);
+    assert_transport_data(tp, "JOIN %s\r\n", ctx->channel);
+
+    // JOIN reply
+    transport_test_push_fmt(tp, ":mynick!user@host JOIN %s\r\n", ctx->channel);
+    assert(!ctx->chan->joining && ctx->chan->joined);
+    assert(ctx->on_chan_self_join);
+}
+
+/**
+ * Sends a short RPL_NAMREPLY/RPL_ENDOFNAMES reply and checks that the users list matches
+ */
+static void do_irc_chan_namreply (struct transport_test *tp, struct test_chan_ctx *ctx)
+{
+    // RPL_NAMREPLY
+    transport_test_push_fmt(tp, "353 mynick = %s :mynick userA +userB @userC\r\n", ctx->channel);
+    transport_test_push_fmt(tp, "353 mynick = %s :trailingspace \r\n", ctx->channel);
+    transport_test_push_fmt(tp, "366 mynick %s :End of NAMES\r\n", ctx->channel);
+    
+    // XXX: this should be an exclusive test, i.e. these should be the only ones...
+    check_chan_user(ctx->chan, "mynick", true);
+    check_chan_user(ctx->chan, "userA", true);
+    check_chan_user(ctx->chan, "userB", true);
+    check_chan_user(ctx->chan, "userC", true);
+}
+
+/**
+ * Creates an irc_chan on the given irc_net, and checks up to the JOIN reply
+ */
+static struct irc_chan* setup_irc_chan_join (struct transport_test *tp, struct irc_net *net, const char *channel, struct test_chan_ctx *ctx)
+{
+    setup_irc_chan_raw(net, channel, ctx);
+    do_irc_chan_join(tp, ctx);
+
+    // ok
+    return ctx->chan;
+}
+
+struct irc_chan* setup_irc_chan (struct transport_test *tp, struct irc_net *net, const char *channel, struct test_chan_ctx *ctx)
+{
+    setup_irc_chan_raw(net, channel, ctx);
+    do_irc_chan_join(tp, ctx);
+    do_irc_chan_namreply(tp, ctx);
+
+    // ok
+    return ctx->chan;
+}
+
+
+void test_irc_chan_add_offline (void)
+{
+    struct test_chan_ctx ctx;
+    struct transport_test *tp = setup_transport_test();
+    struct irc_net *net = setup_irc_net_unregistered(tp);
+
+    // add an offline channel
+    log_info("test offline irc_net_add_chan");
+    struct irc_chan *chan = setup_irc_chan_raw(net, "#test", &ctx);
+    assert(!chan->joining && !chan->joined);
+
+    // send the registration reply
+    log_info("test irc_conn_on_RPL_WELCOME");
+    test_irc_net_welcome(tp, net);
+    
+    // test the join sequence
+    log_info("test irc_chan_join/irc_chan_on_JOIN");
+    do_irc_chan_join(tp, &ctx);
+    
+    // cleanup
+    irc_net_destroy(net);
+}
+
+void test_irc_chan_namreply (void)
+{
+    struct test_chan_ctx ctx;
+    struct transport_test *tp = setup_transport_test();
+    struct irc_net *net = setup_irc_net(tp);
+    setup_irc_chan_join(tp, net, "#test", &ctx);
+
+    log_info("test irc_chan_on_RPL_NAMREPLY");
+    do_irc_chan_namreply(tp, &ctx);
+
+    // cleanup
+    irc_net_destroy(net);
+}
+
+void test_irc_chan_user_join (void)
+{
+    struct test_chan_ctx ctx;
+    struct transport_test *tp = setup_transport_test();
+    struct irc_net *net = setup_irc_net(tp);
+    struct irc_chan *chan = setup_irc_chan(tp, net, "#test", &ctx);
+
+    // have a user join
+    log_info("test irc_chan_on_JOIN");
+    transport_test_push_fmt(tp, ":newuser!someone@somewhere JOIN %s\r\n", "#test");
+    assert(ctx.on_chan_join);
+    check_chan_user(chan, "newuser", true);
+
+    // cleanup
+    irc_net_destroy(net);
+}
+
+void test_irc_chan_user_part (void)
+{
+    struct test_chan_ctx ctx;
+    struct transport_test *tp = setup_transport_test();
+    struct irc_net *net = setup_irc_net(tp);
+    struct irc_chan *chan = setup_irc_chan(tp, net, "#test", &ctx);
+
+    // have a user join
+    log_info("test irc_chan_on_PART");
+    transport_test_push_fmt(tp, ":userA!someone@somewhere PART %s\r\n", "#test");
+    assert(ctx.on_chan_part); ctx.on_chan_part = NULL;
+    check_chan_user(chan, "userA", false);
+
+    // cleanup
+    irc_net_destroy(net);
+}
+
+void test_irc_chan_user_kick (void)
+{
+    struct test_chan_ctx ctx;
+    struct transport_test *tp = setup_transport_test();
+    struct irc_net *net = setup_irc_net(tp);
+    struct irc_chan *chan = setup_irc_chan(tp, net, "#test", &ctx);
+
+    // kick a user
+    log_info("test irc_chan_on_KICK (other)");
+    transport_test_push_fmt(tp, ":userA!someone@somewhere KICK %s userB\r\n", "#test");
+    check_chan_user(chan, "userA", true);
+    check_chan_user(chan, "userB", false);
+
+    // cleanup
+    irc_net_destroy(net);
+}
+
+void test_irc_chan_self_kick (void)
+{
+    struct test_chan_ctx ctx;
+    struct transport_test *tp = setup_transport_test();
+    struct irc_net *net = setup_irc_net(tp);
+    struct irc_chan *chan = setup_irc_chan(tp, net, "#test", &ctx);
+
+    // kick a user
+    log_info("test irc_chan_on_KICK (self)");
+    transport_test_push_fmt(tp, ":userA!someone@somewhere KICK %s mynick foobar\r\n", "#test");
+    assert(!chan->joined);
+    assert(chan->kicked);
+
+    // cleanup
+    irc_net_destroy(net);
+}
+
+void test_irc_chan_user_nick (void)
+{
+    struct test_chan_ctx ctx;
+    struct transport_test *tp = setup_transport_test();
+    struct irc_net *net = setup_irc_net(tp);
+    struct irc_chan *chan = setup_irc_chan(tp, net, "#test", &ctx);
+
+    // rename one of the users
+    log_info("test irc_net_on_chanuser");
+    transport_test_push_fmt(tp, ":userA!someone@somewhere NICK userA2\r\n");
+    check_chan_user(chan, "userA", false);
+    check_chan_user(chan, "userB", true);
+
+    // cleanup
+    irc_net_destroy(net);
+}
+
+void test_irc_chan_user_quit (void)
+{
+    struct test_chan_ctx ctx;
+    struct transport_test *tp = setup_transport_test();
+    struct irc_net *net = setup_irc_net(tp);
+    struct irc_chan *chan = setup_irc_chan(tp, net, "#test", &ctx);
+
+    // rename one of the users
+    log_info("test irc_net_on_chanuser");
+    transport_test_push_fmt(tp, ":userA!someone@somewhere QUIT foo\r\n");
+    check_chan_user(chan, "userA", false);
+
+    // cleanup
+    irc_net_destroy(net);
+}
+
+void _test_irc_chan_on_CTCP_ACTION (const struct irc_line *line, void *arg)
+{
+    bool *flag = arg;
+
+    log_debug("CTCP ACTION");
+
+    *flag = true;
+}
+
+static struct irc_cmd_handler _test_irc_chan_handlers[] = {
+    {   "CTCP ACTION",      &_test_irc_chan_on_CTCP_ACTION  },
+    {   NULL,               NULL                            }
+};
+
+void test_irc_chan_CTCP_ACTION (void)
+{
+    struct test_chan_ctx ctx;
+    struct transport_test *tp = setup_transport_test();
+    struct irc_net *net = setup_irc_net(tp);
+    struct irc_chan *chan = setup_irc_chan(tp, net, "#test", &ctx);
+    bool cb_ok = false;
+
+    // add our handler
+    assert_success(irc_cmd_add(&chan->handlers, _test_irc_chan_handlers, &cb_ok));
+
+    // rename one of the users
+    log_info("test irc_conn_on_CTCP_ACTION");
+    transport_test_push_fmt(tp, ":userA!someone@somewhere PRIVMSG #test \001ACTION hello  world\001\r\n");
+    assert(cb_ok);
+
+    // cleanup
+    irc_net_destroy(net);
+}
+
+void test_irc_chan_privmsg (void)
+{
+    struct test_chan_ctx ctx;
+    struct transport_test *tp = setup_transport_test();
+    struct irc_net *net = setup_irc_net(tp);
+    struct irc_chan *chan = setup_irc_chan(tp, net, "#test", &ctx);
+
+    // rename one of the users
+    log_info("test irc_chan_PRIVMSG");
+    assert_success(irc_chan_PRIVMSG(chan, "foobar quux"));
+    assert_transport_data(tp, "PRIVMSG #test :foobar quux\r\n");
+
+    // cleanup
+    irc_net_destroy(net);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/test/irc_chan.h	Mon May 04 20:55:43 2009 +0300
@@ -0,0 +1,38 @@
+#ifndef TEST_IRC_CHAN_H
+#define TEST_IRC_CHAN_H
+
+/**
+ * @file
+ *
+ * Utility functions for testing irc_chan
+ */
+#include "../irc_chan.h"
+#include "transport.h"
+
+/**
+ * Callback context
+ */
+struct test_chan_ctx {
+    /** The channel name */
+    const char *channel;
+
+    /** The channel we're supposed to be testing */
+    struct irc_chan *chan;
+    
+    /** Flags for callbacks called*/
+    bool on_chan_self_join, on_chan_self_part, on_chan_join, on_chan_part;
+
+};
+
+/**
+ * Ensure that an irc_chan_user exists/doesn't exist for the given channel/nickname, and return it.
+ */
+struct irc_chan_user* check_chan_user (struct irc_chan *chan, const char *nickname, bool exists);
+
+/**
+ * Creates an irc_chan on the given irc_net, sends the JOIN stuff plus RPL_NAMREPLY
+ */
+struct irc_chan* setup_irc_chan (struct transport_test *tp, struct irc_net *net, const char *channel, struct test_chan_ctx *ctx);
+
+
+#endif /* TEST_IRC_CHAN_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/test/irc_conn.c	Mon May 04 20:55:43 2009 +0300
@@ -0,0 +1,142 @@
+#include "irc_conn.h"
+#include "test.h"
+
+static void _conn_on_registered (struct irc_conn *conn, void *arg)
+{
+    struct test_conn_ctx *ctx = arg;
+
+    (void) conn;
+
+    if (ctx) ctx->on_registered = true;
+
+    log_debug("registered");
+}
+
+static void _conn_on_error (struct irc_conn *conn, struct error_info *err, void *arg)
+{
+    struct test_conn_ctx *ctx = arg;
+    
+    (void) conn;
+    (void) err;
+
+    if (ctx) ctx->on_error = true;
+
+    log_debug("on_error");
+}
+
+static void _conn_on_quit (struct irc_conn *conn, void *arg)
+{
+    struct test_conn_ctx *ctx = arg;
+
+    (void) conn;
+
+    if (ctx) ctx->on_quit = true;
+
+    log_debug("on_quit");
+}
+
+static void _conn_on_TEST (const struct irc_line *line, void *arg)
+{
+    struct test_conn_ctx *ctx = arg;
+
+    assert(line->source);
+    assert(!line->source->nickname && !line->source->username && line->source->hostname);
+    assert_strcmp(line->command, "TEST");
+    assert_strcmp(line->args[0], "arg0");
+    assert_strnull(line->args[1]);
+
+    if (ctx) ctx->on_TEST = true;
+
+    log_debug("on_TEST");
+}
+
+static struct irc_conn_callbacks _conn_callbacks = {
+    .on_registered      = &_conn_on_registered,
+    .on_error           = &_conn_on_error,
+    .on_quit            = &_conn_on_quit,
+};
+
+static struct irc_cmd_handler _conn_handlers[] = {
+    {   "TEST",         &_conn_on_TEST  },
+    {   NULL,           NULL            }
+};
+
+struct irc_conn* setup_irc_conn (struct transport_test *tp, bool noisy, struct test_conn_ctx *ctx)
+{
+    struct irc_conn *conn;
+    struct error_info err;
+    struct irc_conn_register_info register_info = {
+        "nick", "user", "realname"
+    };
+
+    // init the ctx
+    memset(ctx, 0, sizeof(*ctx));
+
+    // create the irc_conn
+    assert_success(irc_conn_create(&conn, transport_test_cast(tp), &_conn_callbacks, ctx, &err));
+
+    // test register
+    if (noisy) log_info("test irc_conn_register");
+    assert_success(irc_conn_register(conn, &register_info));
+    assert_transport_data(tp, "NICK nick\r\nUSER user 0 * realname\r\n");
+ 
+    // test on_register callback    
+    if (noisy) log_info("test irc_conn_callbacks.on_register");
+    transport_test_push_str(tp, "001 mynick :Blaa blaa blaa\r\n");
+    if (ctx) assert(ctx->on_registered);
+    assert_strcmp(conn->nickname, "mynick");
+   
+    // ok
+    return conn;
+}
+
+void test_irc_conn (void)
+{
+    struct test_conn_ctx ctx;
+    struct transport_test *tp = setup_transport_test();
+    struct irc_conn *conn = setup_irc_conn(tp, true, &ctx);
+
+    // add our test handlers
+    assert_success(irc_conn_add_cmd_handlers(conn, _conn_handlers, &ctx));
+
+    // test on_TEST handler
+    // XXX: come up with a better prefix
+    log_info("test irc_conn.handlers");
+    transport_test_push_str(tp, ":foobar-prefix TEST arg0\r\n");
+    assert(ctx.on_TEST);
+
+    // test PING/PONG
+    log_info("test PING/PONG");
+    transport_test_push_str(tp, "PING foo\r\n");
+    assert_transport_data(tp, "PONG foo\r\n");
+
+    // quit nicely
+    log_info("test QUIT");
+    assert_success(irc_conn_QUIT(conn, "bye now"));
+    assert_transport_data(tp, "QUIT :bye now\r\n");
+    assert(conn->quitting);
+
+    transport_test_push_str(tp, "ERROR :Closing Link: Quit\r\n");
+    transport_test_push_eof(tp);
+    assert(conn->quit && !conn->quitting && !conn->registered);
+    assert(ctx.on_quit);
+    assert(!ctx.on_error);
+
+    // destroy it
+    irc_conn_destroy(conn);
+}
+
+void test_irc_conn_self_nick (void)
+{
+    struct test_conn_ctx ctx;
+    struct transport_test *tp = setup_transport_test();
+    struct irc_conn *conn = setup_irc_conn(tp, false, &ctx);
+    
+    log_info("test irc_conn_on_NICK");
+    transport_test_push_fmt(tp, ":mynick!user@somehost NICK mynick2\r\n");
+    assert_strcmp(conn->nickname, "mynick2");
+
+    // cleanup
+    irc_conn_destroy(conn);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/test/irc_conn.h	Mon May 04 20:55:43 2009 +0300
@@ -0,0 +1,29 @@
+#ifndef TEST_IRC_CONN_H
+#define TEST_IRC_CONN_H
+
+/**
+ * @file
+ *
+ * Utility test functions for irc_conn related tests
+ */
+#include "../irc_conn.h"
+#include "transport.h"
+
+/**
+ * Callback flags
+ */
+struct test_conn_ctx {
+    /** Callback flags */
+    bool on_registered, on_TEST, on_error, on_quit;
+};
+
+/**
+ * Create and return a new irc_conn with the given ctx (will be initialized to zero).
+ *
+ * The returned irc_conn will be in the registered state.
+ *
+ * Use irc_conn_destroy to clean up the returned irc_conn.
+ */
+struct irc_conn* setup_irc_conn (struct transport_test *tp, bool noisy, struct test_conn_ctx *ctx);
+
+#endif /* TEST_IRC_CONN_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/test/irc_net.c	Mon May 04 20:55:43 2009 +0300
@@ -0,0 +1,67 @@
+#include "irc_net.h"
+#include "test.h"
+
+struct irc_net* setup_irc_net_unregistered (struct transport_test *tp)
+{
+    struct irc_net *net;
+    struct irc_net_info net_info = {
+        .register_info = {
+            "nick", "user", "realname"
+        },
+    };
+    struct error_info err;
+
+    // create the irc_net
+    net_info.transport = transport_test_cast(tp);
+    assert_success(irc_net_create(&net, &net_info, &err));
+
+    // test register output
+    assert_transport_data(tp, "NICK nick\r\nUSER user 0 * realname\r\n");
+    
+    // ok
+    return net; 
+}
+
+void test_irc_net_welcome (struct transport_test *tp, struct irc_net *net)
+{
+    // registration reply
+    transport_test_push_fmt(tp, "001 mynick :Blaa blaa blaa\r\n");
+    assert(net->conn->registered);
+    assert_strcmp(net->conn->nickname, "mynick");
+
+}
+
+struct irc_net* setup_irc_net (struct transport_test *tp)
+{
+    struct irc_net *net;   
+
+    net = setup_irc_net_unregistered(tp);
+    test_irc_net_welcome(tp, net);
+    
+    // ok
+    return net;
+}
+
+void test_irc_net (void)
+{
+    struct transport_test *tp = setup_transport_test();
+
+    // XXX: test connected/connecting/disconnected/etc stuff
+    
+    // create the network
+    log_info("test irc_net_create");
+    struct irc_net *net = setup_irc_net_unregistered(tp);
+
+    // send the registration reply
+    log_info("test irc_conn_on_RPL_WELCOME");
+    test_irc_net_welcome(tp, net);
+
+    // test errors by setting EOF
+    log_info("test irc_net_error");
+    transport_test_push_eof(tp);
+    assert(net->conn == NULL);
+
+    // cleanup
+    irc_net_destroy(net);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/test/irc_net.h	Mon May 04 20:55:43 2009 +0300
@@ -0,0 +1,28 @@
+#ifndef TEST_IRC_NET_H
+#define TEST_IRC_NET_H
+
+/**
+ * @file
+ *
+ * Functionality for testing irc_net
+ */
+#include "../irc_net.h"
+#include "transport.h"
+
+/**
+ * Setup an irc_net using the given socket, and consume the register request output, but do not push the RPL_WELCOME.
+ */
+struct irc_net* setup_irc_net_unregistered (struct transport_test *tp);
+
+/**
+ * Push the RPL_WELCOME reply.
+ */
+void test_irc_net_welcome (struct transport_test *tp, struct irc_net *net);
+
+/**
+ * Creates an irc_net and puts it into the registered state
+ */
+struct irc_net* setup_irc_net (struct transport_test *tp);
+
+
+#endif /* TEST_IRC_NET_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/test/irc_queue.c	Mon May 04 20:55:43 2009 +0300
@@ -0,0 +1,76 @@
+/**
+ * @file
+ *
+ * Test functions for the irc_queue module
+ */
+#include "test.h"
+#include "transport.h"
+
+#include "../irc_queue.h"
+
+static struct line_proto_callbacks _lp_callbacks = {
+    .on_line    = NULL,
+    .on_error   = NULL
+};
+
+void test_irc_queue (void)
+{
+    struct transport_test *tp = transport_test_create(NULL);
+    transport_t *transport = transport_test_cast(tp);
+    struct line_proto *lp;
+    struct irc_queue *queue;
+    struct irc_queue_entry *queue_entry;
+    struct error_info err;
+
+    // create the lp
+    assert_success(line_proto_create(&lp, transport, 128, &_lp_callbacks, NULL, &err));
+
+    // create the queue
+    assert_success(irc_queue_create(&queue, _test_ctx.ev_base, lp, &err));
+
+    struct irc_line line = {
+        NULL, "TEST", { "fooX" }
+    };
+
+    // then test simple writes, we should be able to push five lines directly
+    log_info("test irc_queue_process (irc_queue_send_direct)");
+    line.args[0] = "foo0"; assert_success(irc_queue_process(queue, &line));
+    line.args[0] = "foo1"; assert_success(irc_queue_process(queue, &line));
+    line.args[0] = "foo2"; assert_success(irc_queue_process(queue, &line));
+    line.args[0] = "foo3"; assert_success(irc_queue_process(queue, &line));
+    line.args[0] = "foo4"; assert_success(irc_queue_process(queue, &line));
+
+    // they should all be output
+    assert_transport_data(tp,
+            "TEST foo0\r\n"
+            "TEST foo1\r\n"
+            "TEST foo2\r\n"
+            "TEST foo3\r\n"
+            "TEST foo4\r\n"
+    );
+
+    // then enqueue
+    log_info("test irc_queue_process (irc_queue_put)");
+    line.args[0] = "foo5"; assert_success(irc_queue_process(queue, &line));
+
+    // ensure it was enqueued
+    assert((queue_entry = TAILQ_FIRST(&queue->list)) != NULL);
+    assert_strcmp(queue_entry->line_buf, "TEST foo5\r\n");
+
+    // ensure timer is set
+    assert(event_pending(queue->ev, EV_TIMEOUT, NULL));
+
+    // run the event loop to let the timer run
+    log_info("running the event loop once...");
+    assert(event_base_loop(_test_ctx.ev_base, EVLOOP_ONCE) == 0);
+
+    // test to check that the line was now sent
+    log_info("checking that the delayed line was sent...");
+    assert_transport_data(tp, "TEST foo5\r\n");
+    assert(TAILQ_EMPTY(&queue->list));
+    assert(!event_pending(queue->ev, EV_TIMEOUT, NULL));
+
+    // cleanup
+    irc_queue_destroy(queue);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/test/line_proto.c	Mon May 04 20:55:43 2009 +0300
@@ -0,0 +1,107 @@
+/**
+ * @file
+ *
+ * Test functions for the line_proto module
+ */
+#include "test.h"
+#include "transport.h"
+
+#include "../line_proto.h"
+
+void assert_read_line (struct line_proto *lp, const char *line_str)
+{
+    char *line_buf;
+    
+    log_debug("expect: %s", dump_str(line_str));
+
+    assert_success(line_proto_recv(lp, &line_buf));
+
+    if (line_str) {
+        assert_strcmp(line_buf, line_str);
+
+    } else {
+        assert_strnull(line_buf);
+
+    }
+}
+
+/**
+ * Context info for test_line_proto callbacks
+ */
+struct _lp_test_ctx {
+    /** Expected line */
+    const char *line;
+
+    /** Expected error */
+    struct error_info err;
+};
+
+static void _lp_on_line (char *line, void *arg)
+{
+    struct _lp_test_ctx *ctx = arg;
+
+    log_debug("%s", dump_str(line));
+
+    assert_strcmp(line, ctx->line);
+
+    ctx->line = NULL;
+}
+
+static void _lp_on_error (struct error_info *err, void *arg)
+{
+    struct _lp_test_ctx *ctx = arg;
+
+    assert_error(err, &ctx->err);
+}
+
+static struct line_proto_callbacks _lp_callbacks = {
+    .on_line        = &_lp_on_line,
+    .on_error       = &_lp_on_error,
+};
+
+void test_line_proto (void)
+{
+    struct transport_test *tp = transport_test_create(NULL);
+    transport_t *transport = transport_test_cast(tp);
+    struct line_proto *lp;
+    struct _lp_test_ctx ctx;
+    struct error_info err;
+    
+    // put the read data
+    log_debug("transport_test_push_*");
+    transport_test_push_str(tp, "hello\r\n");
+    transport_test_push_str(tp, "world\n");
+    transport_test_push_str(tp, "this ");
+    transport_test_push_str(tp, "is a line\r");
+    transport_test_push_str(tp, "\nfragment");
+
+    // create the lp
+    assert_success(line_proto_create(&lp, transport, 128, &_lp_callbacks, &ctx, &err));
+    
+    log_info("test line_proto_recv");
+
+    // then read some lines from it
+    assert_read_line(lp, "hello");
+    assert_read_line(lp, "world");
+    assert_read_line(lp, "this is a line");
+    assert_read_line(lp, NULL);
+
+    // then add a final bit to trigger on_line
+    log_info("test on_line");
+
+    ctx.line = "fragment";
+    transport_test_push_str(tp, "\r\n");
+    assert_strnull(ctx.line);
+
+    // test writing
+    log_info("test line_proto_send");
+    assert_success(-line_proto_send(lp, "foobar\r\n"));
+    assert_success(-line_proto_send(lp, "quux\r\n"));
+    assert_transport_data(tp, "foobar\r\nquux\r\n");
+
+    // XXX: test partial writes
+
+    // cleanup
+    line_proto_destroy(lp);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/test/str.c	Mon May 04 20:55:43 2009 +0300
@@ -0,0 +1,83 @@
+/**
+ * @file
+ *
+ * Test functions for the str module.
+ */
+#include "assert.h"
+
+void assert_str_quote (size_t buf_size, const char *data, ssize_t len, const char *target, size_t out)
+{
+    char buf[buf_size];
+    
+    size_t ret = str_quote(buf, buf_size, data, len);
+
+    log_debug("str_quote(%zu, %zd) -> %s:%zu / %s:%zu", buf_size, len, buf, ret, target, out);
+
+    assert_strcmp(buf, target);
+    assert(ret == out);
+}
+
+void test_str_quote (void)
+{
+    log_info("testing str_quote()");
+
+    assert_str_quote(5,     NULL,           -1,     "NULL",         4   );
+    assert_str_quote(16,    "foo",          -1,     "'foo'",        5   );
+    assert_str_quote(16,    "foobar",       3,      "'foo'",        5   );
+    assert_str_quote(16,    "\r\n",         -1,     "'\\r\\n'",     6   );
+    assert_str_quote(16,    "\x13",         -1,     "'\\x13'",      6   );
+    assert_str_quote(16,    "x'y",          -1,     "'x\\'y'",      6   );
+    assert_str_quote(7,     "1234567890",   -1,     "'1'...",       12  );
+    assert_str_quote(9,     "1234567890",   -1,     "'123'...",     12  );
+}
+
+struct str_format_ctx {
+    const char *name;
+
+    const char *value;
+};
+
+err_t test_str_format_cb (const char *name, const char **value, ssize_t *value_len, void *arg)
+{
+    struct str_format_ctx *ctx = arg;
+
+    assert_strcmp(name, ctx->name);
+
+    *value = ctx->value;
+    *value_len = -1;
+
+    return SUCCESS;
+}
+
+void assert_str_format (const char *format, const char *name, const char *value, const char *out)
+{
+    struct str_format_ctx ctx = { name, value };
+    char buf[512];
+
+    assert_success(str_format(buf, sizeof(buf), format, test_str_format_cb, &ctx));
+    
+    log_debug("str_format(%s), { %s:%s } -> %s / %s", format, name, value, buf, out);
+
+    assert_strcmp(buf, out);
+}
+
+void test_str_format (void)
+{
+    log_info("test str_format()");
+
+    assert_str_format("foo", NULL, NULL, "foo");
+    assert_str_format("foo {bar} quux", "bar", "XXX", "foo XXX quux");
+}
+
+void test_dump_str (void)
+{
+    log_info("dumping example strings on stdout:");
+
+    log_debug("normal: %s", dump_str("Hello World"));
+    log_debug("escapes: %s", dump_str("foo\r\nbar\a\001"));
+    log_debug("length: %s", dump_strn("<-->**", 4));
+    log_debug("overflow: %s", dump_str( "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"));
+    log_debug("null: %s", dump_str(NULL));
+    log_debug("quote: %s", dump_str("foo\\bar'quux"));
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/test/test.c	Mon May 04 20:55:43 2009 +0300
@@ -0,0 +1,160 @@
+#include "test.h"
+
+#include "../sock.h"
+
+#include <getopt.h>
+
+/**
+ * The global state
+ */
+struct test_ctx _test_ctx;
+
+/**
+ * Setup the global sock_stream state
+ */
+static struct event_base* setup_sock (void)
+{
+    struct event_base *ev_base;
+    struct error_info err;
+
+    assert((ev_base = event_base_new()));
+    assert_success(sock_init(ev_base, &err));
+
+    return ev_base;
+}
+
+/**
+ * Command-line option codes
+ */
+enum option_code {
+    OPT_HELP            = 'h',
+    OPT_DEBUG           = 'd',
+    OPT_QUIET           = 'q',
+    OPT_LIST            = 'l',
+    
+    /** Options without short names */
+    _OPT_EXT_BEGIN      = 0x00ff,
+};
+
+/**
+ * Command-line option definitions
+ */
+static struct option options[] = {
+    {"help",            0,  NULL,   OPT_HELP        },
+    {"debug",           0,  NULL,   OPT_DEBUG       },
+    {"quiet",           0,  NULL,   OPT_QUIET       },
+    {"list",            0,  NULL,   OPT_LIST        },
+    {0,                 0,  0,      0               },
+};
+
+/**
+ * Display --help output on stdout
+ */
+static void usage (const char *exe) 
+{
+    printf("Usage: %s [OPTIONS]\n", exe);
+    printf("\n");
+    printf(" --help / -h            display this message\n");
+    printf(" --debug / -d           display DEBUG log messages\n");
+    printf(" --quiet / -q           supress INFO log messages\n");
+    printf(" --list / -l            list all tests\n");
+}
+
+/**
+ * Output the given list of tests on stdout
+ */
+static void list_tests (const struct test *tests)
+{
+    const struct test *test;
+    
+    printf("Available tests:\n");
+
+    for (test = tests; test->name; test++) {
+        printf("\t%s\n", test->name);
+    }
+}
+
+/**
+ * Run the given NULL-terminated list of tests, optionally filtering against the given filter.
+ *
+ * Returns the number of tests run, which may be zero.
+ */
+static size_t run_tests (const struct test tests[], const char *filter)
+{
+    size_t test_count = 0;
+    const struct test *test;
+
+    // run each test in turn
+    for (test = tests; test->name; test++) {
+        // filter out if given
+        if ((filter && strcmp(test->name, filter)) || (!filter && test->optional))
+            continue;
+
+        log_info("Running test: %s", test->name);
+        
+        // count and run
+        test_count++;
+        test->func();
+    }
+    
+    return test_count;
+}
+
+int main (int argc, char **argv)
+{
+    int opt, option_index;
+    const char *filter = NULL;
+
+    size_t test_count;
+
+    // parse options
+    while ((opt = getopt_long(argc, argv, "hdql", options, &option_index)) != -1) {
+        switch (opt) {
+            case OPT_HELP:
+                usage(argv[0]);
+                exit(EXIT_SUCCESS);
+            
+            case OPT_DEBUG:
+                set_log_level(LOG_DEBUG);
+                break;
+
+            case OPT_QUIET:
+                set_log_level(LOG_WARN);
+                break;
+           
+            case OPT_LIST:
+                list_tests(_tests);
+                exit(EXIT_SUCCESS);
+
+            case '?':
+                usage(argv[0]);
+                exit(EXIT_FAILURE);
+        }
+    }
+
+    // parse positional arguments
+    if (optind < argc) {
+        if (optind == argc - 1) {
+            // filter
+            filter = argv[optind];
+            
+            log_info("only running tests: %s", filter);
+        } else {
+            FATAL("too many arguments");
+        }
+    }
+
+    // setup the sockets stuff
+    _test_ctx.ev_base = setup_sock();
+
+    // run tests
+    if ((test_count = run_tests(_tests, filter)) == 0)
+        FATAL("no tests run");
+    
+    // log
+    log_info("done, ran %zu tests", test_count);
+    
+    // ok
+    return EXIT_SUCCESS;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/test/test.h	Mon May 04 20:55:43 2009 +0300
@@ -0,0 +1,41 @@
+#ifndef TEST_TEST_H
+#define TEST_TEST_H
+
+/**
+ * @file
+ *
+ * General test-related functionality
+ */
+#include "assert.h"
+#include "util.h"
+
+#include <event2/event.h>
+#include <string.h>
+
+/**
+ * Global test-running state
+ */
+extern struct test_ctx {
+    /** The event_base that we have setup */
+    struct event_base *ev_base;
+
+} _test_ctx;
+
+
+/**
+ * Global list of test definitions
+ */
+extern const struct test {
+    /** Test name */
+    const char *name;
+
+    /** Test func */
+    void (*func) (void);
+    
+    /** Do not run by default */    
+    bool optional;
+} _tests[];
+
+
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/test/test_list.c	Mon May 04 20:55:43 2009 +0300
@@ -0,0 +1,21 @@
+#include "test.h"
+
+/**
+ * Test function prototypes
+ */
+#define TEST(name) extern void test_ ##name (void);
+
+    #include "test_list.inc"
+
+
+/**
+ * The array of test structs
+ */
+#define TEST(name) { #name, test_ ## name, false },
+#define TEST_OPTIONAL(name) { #name, test_ ## name, true },
+#define TEST_END { NULL, NULL, false }
+
+const struct test _tests[] = {
+    #include "test_list.inc"
+};
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/test/test_list.inc	Mon May 04 20:55:43 2009 +0300
@@ -0,0 +1,59 @@
+/**
+ * This include file acts as a "supermacro", calling a macro (TEST) for each defined test.
+ *
+ * All tests must be added to this list.
+ *
+ * TEST() macro signature:
+ *  #define TEST(name)
+ *  #define TEST_OPTIONAL(name)
+ *  #define TEST_END
+ */
+
+#ifndef TEST
+    /* Required */
+    #error TEST macro not defined
+#endif
+
+#ifndef TEST_OPTIONAL
+    /* Default to the same value as TEST() */
+    #define TEST_OPTIONAL(name) TEST(name)
+#endif
+
+
+/* Tests*/
+TEST ( str_quote                    )
+TEST ( str_format                   )
+TEST ( dump_str                     )
+TEST ( transport_test               )
+TEST ( line_proto                   )
+TEST ( irc_queue                    )
+TEST ( irc_conn                     )
+TEST ( irc_conn_self_nick           )
+TEST ( irc_net                      )
+TEST ( irc_chan_add_offline         )
+TEST ( irc_chan_namreply            )
+TEST ( irc_chan_user_join           )
+TEST ( irc_chan_user_part           )
+TEST ( irc_chan_user_kick           )
+TEST ( irc_chan_self_kick           )
+TEST ( irc_chan_user_nick           )
+TEST ( irc_chan_user_quit           )
+TEST ( irc_chan_CTCP_ACTION         )
+TEST ( irc_chan_privmsg             )
+
+/* Optional tests */
+TEST_OPTIONAL ( fifo                )
+
+/*
+ * End of list
+ */
+#ifdef TEST_END
+    TEST_END
+#endif    
+
+/*
+ * Cleanup
+ */
+#undef TEST
+#undef TEST_OPTIONAL
+#undef TEST_END
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/test/transport.c	Mon May 04 20:55:43 2009 +0300
@@ -0,0 +1,114 @@
+#include "transport.h"
+#include "test.h"
+
+void assert_transport_read (transport_t *transport, const char *str)
+{
+    size_t len = strlen(str);
+    char buf[len];
+    error_t err;
+
+    log_debug("read: %p: %s", transport, dump_str(str));
+    
+    // read it
+    assert(transport_read(transport, buf, len, &err) == (int) len);
+
+    // cmp
+    assert_strncmp(buf, str, len);
+}
+
+void assert_transport_write (transport_t *transport, const char *str)
+{
+    size_t len = strlen(str);
+    error_t err;
+
+    log_debug("write: %p: %s", transport, dump_str(str));
+
+    // write it
+    assert(transport_write(transport, str, len, &err) == (int) len);
+}
+
+void assert_transport_eof (transport_t *transport)
+{
+    char buf;
+    error_t err;
+
+    log_debug("eof: %p", transport);
+
+    assert_err(-transport_read(transport, &buf, 1, &err), ERR_EOF);
+}
+
+void assert_transport_data (struct transport_test *tp, const char *fmt, ...)
+{
+    char buf[TRANSPORT_TEST_FMT_MAX];
+    va_list vargs;
+    size_t len;
+    
+    va_start(vargs, fmt);
+
+    if ((len = vsnprintf(buf, sizeof(buf), fmt, vargs)) >= sizeof(buf))
+        FATAL("input too long: %zu bytes", len);
+
+    va_end(vargs);
+
+    // get the data out
+    char *out;
+    
+    transport_test_pull_buf(tp, &out, &len);
+    
+    log_debug("pull_buf: %s", dump_strn(out, len));
+    
+    // should be the same
+    assert_strncmp(out, buf, len);
+    assert_strlen(buf, len);
+
+    // cleanup
+    free(out);
+}
+
+struct transport_test* setup_transport_test (void)
+{
+    struct transport_test *tp;
+   
+    assert ((tp = transport_test_create(NULL)) != NULL);
+
+    return tp;
+}
+
+void test_transport_test (void)
+{
+    struct transport_info info = { NULL, NULL, 0 };
+    struct transport_test *tp = transport_test_create(&info);
+    transport_t *transport = transport_test_cast(tp);
+
+    // put the read data
+    log_info("test transport_test_push_*");
+    transport_test_push_buf(tp, "foo", 3);
+    transport_test_push_str(tp, "barx");
+    transport_test_push_fmt(tp, "xx %s xx", "quux");
+    transport_test_push_eof(tp);
+    
+    // read it out
+    log_info("test transport_test_read");
+
+    assert_transport_read(transport, "foo");
+    assert_transport_read(transport, "ba");
+    assert_transport_read(transport, "rx");
+    assert_transport_read(transport, "xx quux xx");
+    assert_transport_eof(transport);
+
+    // write some data in
+    log_info("test transport_test_write");
+
+    assert_transport_write(transport, "test ");
+    assert_transport_write(transport, "data");
+    
+    // check output
+    log_info("test transport_test_pull_*");
+
+    assert_transport_data(tp, "test data");
+    assert_transport_data(tp, "");
+
+    // cleanup
+    transport_test_destroy(tp);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/test/transport.h	Mon May 04 20:55:43 2009 +0300
@@ -0,0 +1,44 @@
+#ifndef TEST_TRANSPORT_H
+#define TEST_TRANSPORT_H
+
+/**
+ * @file
+ *
+ * Functions for interacting with transports
+ */
+#include "../transport_test.h"
+
+/**
+ * Read strlen(str) bytes from the given transport, and assert that they match the given string.
+ *
+ * Note that this only performs one transport_read.
+ */
+void assert_transport_read (transport_t *transport, const char *str);
+
+/**
+ * Write strlen(str) bytes to the given transport, and assert that they all get written.
+ *
+ * Note that this only performs one transport_write.
+ */
+void assert_transport_write (transport_t *transport, const char *str);
+
+/**
+ * Attempt to read a single byte from the given transport, and assert that the attempt returns ERR_EOF.
+ */
+void assert_transport_eof (transport_t *transport);
+
+/**
+ * Compare the written data stored in the given transport_test with the string obtained using the given format and args.
+ *
+ * This will pull /all/ of the data in the transport.
+ */
+void assert_transport_data (struct transport_test *tp, const char *fmt, ...);
+
+/**
+ * Create an empty transport_test, with NULL callbacks.
+ *
+ * You must bind a new set of callbacks to the transport before pushing any data to it.
+ */
+struct transport_test* setup_transport_test (void);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/test/util.c	Mon May 04 20:55:43 2009 +0300
@@ -0,0 +1,27 @@
+#include "util.h"
+#include "../str.h"
+
+const char *dump_strn (const char *str, ssize_t len)
+{
+    static char dump_buf[DUMP_STR_COUNT][DUMP_STR_BUF];
+    static size_t dump_idx = 0;
+    
+    // pick a buffer to use
+    char *buf = dump_buf[dump_idx++];
+    
+    // cycle
+    if (dump_idx >= DUMP_STR_COUNT)
+        dump_idx = 0;
+    
+    // write the quoted string into the selected buf
+    str_quote(buf, DUMP_STR_BUF, str, len);
+
+    // ok
+    return buf;
+}
+
+const char *dump_str (const char *str) 
+{
+    return dump_strn(str, -1);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/test/util.h	Mon May 04 20:55:43 2009 +0300
@@ -0,0 +1,34 @@
+#ifndef TEST_UTIL_H
+#define TEST_UTIL_H
+
+/**
+ * @file
+ *
+ * General utility functions for tests
+ */
+#include <sys/types.h>
+
+#define DUMP_STR_BUF 1024
+#define DUMP_STR_COUNT 8
+
+/**
+ * This re-formats the given string to escape values, and returns a pointer to an internal static buffer.
+ *
+ * If len is given as >= 0, only the given number of chars will be dumped from str.
+ *
+ * The buffer cycles a bit, so the returned pointers remain valid across DUMP_STR_COUNT calls.
+ *
+ * The resulting string is truncated to DUMP_STR_BUF bytes, including the ending "...'\0".
+ *
+ * @param str the string to dump, should be NUL-terminated unless len is given
+ * @param len if negative, ignored, otherwise, only this many bytes are dumped from str
+ * @param return a pointer to a static buffer that remains valid across DUMP_STR_COUNT calls to this function
+ */
+const char *dump_strn (const char *str, ssize_t len);
+
+/**
+ * As dump_strn(), with str as a NUL-terminated string
+ */
+const char *dump_str (const char *str); 
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/transport.c	Mon May 04 20:55:43 2009 +0300
@@ -0,0 +1,146 @@
+#include "transport_internal.h"
+
+#include <assert.h>
+
+/*
+ * Internal API
+ */
+void transport_init (transport_t *transport, const struct transport_type *type, const struct transport_info *info)
+{
+    // not already bound
+    assert(!transport->type);
+
+    // store
+    transport->type = type;
+
+    if (info)
+        transport->info = *info;
+}
+
+void* transport_check (transport_t *transport, const struct transport_type *type)
+{
+    const struct transport_type *tp_type;
+
+    // sanity check
+    assert(type);
+
+    // look for a matching type in the transport's type list
+    for (tp_type = transport->type; tp_type; tp_type = tp_type->parent)
+        if (tp_type == type)
+            break;
+    
+    // make sure we found one
+    assert(tp_type);
+    
+    // ok
+    return transport;
+}
+
+void transport_connected (transport_t *transport, const error_t *err, bool direct)
+{
+    // update state
+    transport->connected = true;
+
+    if (direct || !transport->type->methods._connected) {
+        // user callback
+        if (err)
+            // connect failed
+            transport->info.cb_tbl->on_error(transport, err, transport->info.cb_arg);
+        else
+            // connect succesfull
+            transport->info.cb_tbl->on_connect(transport, transport->info.cb_arg);
+
+    } else {
+        // wrapper method
+        transport->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)
+{
+    // not readable
+    if (!transport->type->methods.read)
+        return SET_ERROR(err, -1);
+
+    // proxy off to method handler
+    if (transport->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)
+{
+    // XXX: not writeable
+    if (!transport->type->methods.write)
+        return SET_ERROR(err, -1);
+
+    // proxy off to method handler
+    if (transport->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)
+{
+    error_t err;
+
+    // notify transport
+    if (transport->type->methods.events) {
+        if (transport->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)
+{
+    // destroy the transport-specific stuff
+    if (transport->type->methods.destroy)
+        transport->type->methods.destroy(transport);
+
+    // then the transport itself
+    free(transport);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/transport.h	Mon May 04 20:55:43 2009 +0300
@@ -0,0 +1,162 @@
+#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.
+ */
+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.
+ *
+ * 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/transport_fd.c	Mon May 04 20:55:43 2009 +0300
@@ -0,0 +1,365 @@
+#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_methods_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_methods_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_methods_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_destroy (transport_t *transport)
+{
+    struct transport_fd *fd = transport_check(transport, &transport_fd_type);
+
+    transport_fd_destroy(fd);
+}
+
+const struct transport_type transport_fd_type = {
+    .parent     = NULL,
+    .methods    = {
+        .read       = transport_fd_methods_read,
+        .write      = transport_fd_methods_write,
+        .events     = transport_fd_methods_events,
+        .destroy    = _transport_fd_destroy
+    }
+};
+
+/**
+ * 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_destroy (struct transport_fd *fd)
+{
+    err_t tmp;
+
+    // XXX: this might block
+    if ((tmp = transport_fd_close(fd)))
+        log_warn_err_code(tmp, "close");
+
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/transport_fd.h	Mon May 04 20:55:43 2009 +0300
@@ -0,0 +1,166 @@
+#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_methods_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_methods_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_methods_events (transport_t *transport, short mask, error_t *err);
+
+/**
+ * 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);
+
+/**
+ * Destroy the fd immediately.
+ *
+ * This logs a warning if the close() fails.
+ *
+ * XXX: this may actually block, I think? SO_LINGER?
+ */
+void transport_fd_destroy (struct transport_fd *fd);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/transport_internal.h	Mon May 04 20:55:43 2009 +0300
@@ -0,0 +1,124 @@
+#ifndef TRANSPORT_INTERNAL_H
+#define TRANSPORT_INTERNAL_H
+
+/**
+ * @file
+ *
+ * The internal interface for transport implementations.
+ */ 
+#include "transport.h"
+
+#include <stdbool.h>
+
+/**
+ * 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 (*destroy) (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);
+};
+
+/**
+ * The definition of a transport type
+ */
+struct transport_type {
+    /** Parent type */
+    const struct transport_type *parent;
+
+    /** Method table */
+    struct transport_methods methods;
+};
+
+/**
+ * The base transport type
+ */
+struct transport {
+    /** The type info, or NULL if not yet bound */
+    const struct transport_type *type;
+
+    /** 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.
+ *
+ * XXX: This sets the transport::connected flag, regardless of which callback it invokes.
+ *
+ * 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 */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/transport_test.c	Mon May 04 20:55:43 2009 +0300
@@ -0,0 +1,358 @@
+#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_destroy (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 = {
+    .methods                = {
+        .read               = transport_test_read,
+        .write              = transport_test_write,
+        .events             = transport_test_events,
+        .destroy            = _transport_test_destroy
+    },
+};
+
+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_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/transport_test.h	Mon May 04 20:55:43 2009 +0300
@@ -0,0 +1,74 @@
+#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);
+
+/**
+ * Destroy the transport buffer, releasing any buffers we allocated ourself
+ */
+void transport_test_destroy (struct transport_test *tp);
+
+#endif /* TRANSPORT_TEST_H */