--- 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, ®ister_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, ®ister_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 */