# HG changeset patch # User Tero Marttila # Date 1241459743 -10800 # Node ID e6a1ce44aeccad825ed790c0842a5eed8b3b93f1 # Parent d35e7cb3a48903a10184aa382a08ff9f7dd7f2e8# Parent a58ad50911fc05437ac749ec8e22c9f22dddd766 merge new-transport@a58ad50911fc diff -r d35e7cb3a489 -r e6a1ce44aecc TODO --- 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 diff -r d35e7cb3a489 -r e6a1ce44aecc src/CMakeLists.txt --- 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" diff -r d35e7cb3a489 -r e6a1ce44aecc src/config.c --- 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; } diff -r d35e7cb3a489 -r e6a1ce44aecc src/config.h --- 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 diff -r d35e7cb3a489 -r e6a1ce44aecc src/error.c --- 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]"; diff -r d35e7cb3a489 -r e6a1ce44aecc src/error.h --- 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); diff -r d35e7cb3a489 -r e6a1ce44aecc src/fifo.c --- /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 +#include +#include +#include +#include + +/** + * Our transport_type + */ +extern const struct transport_type fifo_type; + +/** + * The fifo state + */ +struct fifo { + /** The FD-based state */ + struct transport_fd base_fd; + + /** Path to the fifo */ + char *path; +}; + +/** + * 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); +} diff -r d35e7cb3a489 -r e6a1ce44aecc src/fifo.h --- /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 + +/** + * A read-only transport based on a FIFO, this provides nonblocking read operations by re-opening the FIFO on EOF. + * + * The transport will be ready for use right away, transport_callbacks::on_connect will never be called. + * + * @param transport_info the setup info required to create the transport + * @param transport_ptr returned transport + * @param path the path to the filesystem fifo object + * @param err returned error info + */ +err_t fifo_open_read (struct transport_info *transport_info, transport_t **transport_ptr, struct event_base *ev_base, + const char *path, error_t *err); + + +#endif diff -r d35e7cb3a489 -r e6a1ce44aecc src/irc_client.c --- 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"); diff -r d35e7cb3a489 -r e6a1ce44aecc src/irc_conn.c --- 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) diff -r d35e7cb3a489 -r e6a1ce44aecc src/irc_conn.h --- 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. diff -r d35e7cb3a489 -r e6a1ce44aecc src/irc_net.c --- 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 } diff -r d35e7cb3a489 -r e6a1ce44aecc src/irc_net.h --- 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; }; /** diff -r d35e7cb3a489 -r e6a1ce44aecc src/irc_net_connect.c --- 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 #include -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 diff -r d35e7cb3a489 -r e6a1ce44aecc src/irc_net_internal.h --- 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 diff -r d35e7cb3a489 -r e6a1ce44aecc src/irc_queue.c --- 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 #include #include @@ -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 diff -r d35e7cb3a489 -r e6a1ce44aecc src/irc_queue.h --- 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. diff -r d35e7cb3a489 -r e6a1ce44aecc src/line_proto.c --- 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); diff -r d35e7cb3a489 -r e6a1ce44aecc src/line_proto.h --- 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 */ diff -r d35e7cb3a489 -r e6a1ce44aecc src/log.c --- 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; diff -r d35e7cb3a489 -r e6a1ce44aecc src/log.h --- 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 */ diff -r d35e7cb3a489 -r e6a1ce44aecc src/modules/logwatch.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 #include "../irc_chan.h" -#include "../sock.h" #include "../line_proto.h" +#include "../nexus.h" #include #include diff -r d35e7cb3a489 -r e6a1ce44aecc src/modules/logwatch_source.c --- 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 @@ -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); diff -r d35e7cb3a489 -r e6a1ce44aecc src/signals.c --- 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 #include #include @@ -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"); diff -r d35e7cb3a489 -r e6a1ce44aecc src/sock.c --- 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); -} - diff -r d35e7cb3a489 -r e6a1ce44aecc src/sock.h --- 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 #include /** - * 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 diff -r d35e7cb3a489 -r e6a1ce44aecc src/sock_fd.c --- 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 -#include -#include - -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); -} - diff -r d35e7cb3a489 -r e6a1ce44aecc src/sock_fd.h --- 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 -#include - -/** - * 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 diff -r d35e7cb3a489 -r e6a1ce44aecc src/sock_fifo.c --- 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 -#include -#include -#include -#include - -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); -} diff -r d35e7cb3a489 -r e6a1ce44aecc src/sock_gnutls.c --- 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 /** - * 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); } diff -r d35e7cb3a489 -r e6a1ce44aecc src/sock_gnutls.h --- 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 @@ -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 diff -r d35e7cb3a489 -r e6a1ce44aecc src/sock_internal.h --- 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 - -/** - * 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 */ diff -r d35e7cb3a489 -r e6a1ce44aecc src/sock_ssl.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 ); diff -r d35e7cb3a489 -r e6a1ce44aecc src/sock_tcp.c --- 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 @@ -7,166 +8,211 @@ #include #include -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); -} - diff -r d35e7cb3a489 -r e6a1ce44aecc src/sock_tcp.h --- 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 - -/** - * 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 diff -r d35e7cb3a489 -r e6a1ce44aecc src/sock_tcp_internal.h --- /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 + +/** + * 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 */ diff -r d35e7cb3a489 -r e6a1ce44aecc src/sock_test.c --- 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 -#include -#include - -/** - * 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; -} - diff -r d35e7cb3a489 -r e6a1ce44aecc src/sock_test.h --- 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 - -/** - * 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 diff -r d35e7cb3a489 -r e6a1ce44aecc src/test.c --- 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 -#include -#include -#include -#include - -#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 -#include -#include -#include -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); -} diff -r d35e7cb3a489 -r e6a1ce44aecc src/test/assert.c --- /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 + +#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)); +} + + diff -r d35e7cb3a489 -r e6a1ce44aecc src/test/assert.h --- /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 +#include + +/** + * 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 diff -r d35e7cb3a489 -r e6a1ce44aecc src/test/fifo.c --- /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 +#include +#include +#include + +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); +} + diff -r d35e7cb3a489 -r e6a1ce44aecc src/test/irc_chan.c --- /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); +} + diff -r d35e7cb3a489 -r e6a1ce44aecc src/test/irc_chan.h --- /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 */ diff -r d35e7cb3a489 -r e6a1ce44aecc src/test/irc_conn.c --- /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); +} + diff -r d35e7cb3a489 -r e6a1ce44aecc src/test/irc_conn.h --- /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 */ diff -r d35e7cb3a489 -r e6a1ce44aecc src/test/irc_net.c --- /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); +} + diff -r d35e7cb3a489 -r e6a1ce44aecc src/test/irc_net.h --- /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 */ diff -r d35e7cb3a489 -r e6a1ce44aecc src/test/irc_queue.c --- /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); +} + diff -r d35e7cb3a489 -r e6a1ce44aecc src/test/line_proto.c --- /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); +} + diff -r d35e7cb3a489 -r e6a1ce44aecc src/test/str.c --- /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")); +} + diff -r d35e7cb3a489 -r e6a1ce44aecc src/test/test.c --- /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 + +/** + * 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; +} + diff -r d35e7cb3a489 -r e6a1ce44aecc src/test/test.h --- /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 +#include + +/** + * 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 diff -r d35e7cb3a489 -r e6a1ce44aecc src/test/test_list.c --- /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" +}; + diff -r d35e7cb3a489 -r e6a1ce44aecc src/test/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 diff -r d35e7cb3a489 -r e6a1ce44aecc src/test/transport.c --- /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); +} + diff -r d35e7cb3a489 -r e6a1ce44aecc src/test/transport.h --- /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 diff -r d35e7cb3a489 -r e6a1ce44aecc src/test/util.c --- /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); +} + diff -r d35e7cb3a489 -r e6a1ce44aecc src/test/util.h --- /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 + +#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 diff -r d35e7cb3a489 -r e6a1ce44aecc src/transport.c --- /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 + +/* + * 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); +} + diff -r d35e7cb3a489 -r e6a1ce44aecc src/transport.h --- /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 diff -r d35e7cb3a489 -r e6a1ce44aecc src/transport_fd.c --- /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 +#include +#include + +/** + * Our libevent callback + */ +static void transport_fd_on_event (evutil_socket_t _fd, short ev_what, void *arg) +{ + struct transport_fd *fd = arg; + + (void) _fd; + + short what = 0; + + // build flags + if (ev_what & EV_READ) + what |= TRANSPORT_READ; + + if (ev_what & EV_WRITE) + what |= TRANSPORT_WRITE; + + // invoke user callback + fd->cb_func(fd, what, fd->cb_arg); +} + +/** + * Our transport_methods implementations + */ +err_t transport_fd_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"); + +} + diff -r d35e7cb3a489 -r e6a1ce44aecc src/transport_fd.h --- /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 +#include + +// forward-declare +struct transport_fd; + +/** + * Our transport_type + */ +extern const struct transport_type transport_fd_type; + +/** + * Low-level callback + */ +typedef void (*transport_fd_callback_func) (struct transport_fd *fd, short what, void *arg); + +/** + * The fd-based transport implementation + */ +struct transport_fd { + /** Base transport state */ + struct transport base; + + /** Libevent base to use */ + struct event_base *ev_base; + + /** OS file descriptor */ + evutil_socket_t fd; + + /** IO events */ + struct event *ev_read, *ev_write; + + /** Low-level callback */ + transport_fd_callback_func cb_func; + + /** Callback context argument */ + void *cb_arg; + +}; + +/** + * Get a transport_t pointer from a transport_fd + */ +#define TRANSPORT_FD_BASE(tp_ptr) (&(tp_ptr)->base) + +/** + * Invalid OS FD + */ +#define TRANSPORT_FD_INVALID ((evutil_socket_t) -1) + +/** + * Implementation of transport_methods::read + */ +err_t transport_fd_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 diff -r d35e7cb3a489 -r e6a1ce44aecc src/transport_internal.h --- /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 + +/** + * 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 */ diff -r d35e7cb3a489 -r e6a1ce44aecc src/transport_test.c --- /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 +#include +#include +#include +#include + +/** + * Simple IO vector + */ +struct io_vec { + /** The buffer */ + char *buf; + + /** Buffer size */ + size_t len; +}; + +/** + * Simple vectored IO-buffer + */ +struct io_buf { + /** The array of buffer-vectors, {NULL}-terminated */ + struct io_vec *vecs; + + /** The number of io_vecs */ + size_t count; + + /** Current read/write vector */ + struct io_vec *read_vec, *write_vec; + + /** Offset into current vector */ + size_t off; +}; + +/** + * Forward-declare our transport_type + */ +extern const struct transport_type transport_test_type; + + +/** + * A dummy sock_stream implementation intended for testing purposes. + */ +struct transport_test { + /** The base transport stuff */ + struct transport base; + + /** The send/recieve buffers */ + struct io_buf send_buf, recv_buf; + + /** No more data is going to be added, return EOF once all the rest is consumed */ + bool eof; +}; + +/** + * Get a transport pointer from a transport_test pointer + */ +#define TRANSPORT_TEST_BASE(tp_ptr) (&(tp_ptr)->base) + +/** + * Grow buf->vecs if needed to ensure that buf->write_vec points to a valid io_vec + */ +static err_t io_buf_grow (struct io_buf *buf) +{ + size_t read_vec_offset = buf->read_vec ? (buf->read_vec - buf->vecs) : 0; + size_t write_vec_offset = buf->write_vec ? (buf->write_vec - buf->vecs) : 0; + struct io_vec *v; + struct io_vec *vecs_tmp = buf->vecs; + + // don't grow if not full + if (buf->vecs && buf->write_vec < buf->vecs + buf->count) + return SUCCESS; + + // new size + buf->count = buf->count * 2 + 1; + + // grow + if ((buf->vecs = realloc(buf->vecs, buf->count * sizeof(struct io_vec))) == NULL) { + // restore old value + buf->vecs = vecs_tmp; + + return ERR_CALLOC; + } + + // restore vec positions + buf->write_vec = buf->vecs + write_vec_offset; + buf->read_vec = buf->vecs + read_vec_offset; + + // zero new vecs + for (v = buf->write_vec; v < buf->vecs + buf->count; v++) + memset(v, 0, sizeof(*v)); + + // ok + return SUCCESS; +} + +/** + * Write some data to an io_buf, copying it. + */ +static err_t io_buf_write (struct io_buf *buf, const char *data, size_t len) +{ + error_t err; + + // ensure there's room + if ((ERROR_CODE(&err) = io_buf_grow(buf))) + goto error; + + // the vector to use + struct io_vec *vec = buf->write_vec; + + // allocate + if ((vec->buf = malloc(len)) == NULL) + JUMP_SET_ERROR(&err, ERR_MEM); + + // store + vec->len = len; + memcpy(vec->buf, data, len); + + // vec consumed + buf->write_vec++; + + // ok + return SUCCESS; + +error: + return ERROR_CODE(&err); +} + +/** + * Destroy the io_buf, freeing all resources. + * + * The io_buf must not be used anymore. + */ +static void io_buf_destroy (struct io_buf *buf) +{ + size_t i; + + // free the io_vec buffers + for (i = 0; i < buf->count; i++) { + free(buf->vecs[i].buf); + } + + // free the vector list + free(buf->vecs); +} + +/** + * transport_methods::read implementation. + */ +static err_t transport_test_read (transport_t *transport, void *buf_ptr, size_t *len, error_t *err) +{ + struct transport_test *tp = transport_check(transport, &transport_test_type); + struct io_buf *buf = &tp->recv_buf; + struct io_vec *vec = buf->read_vec; + + // EOF/nonblock if we're past the end of the last vector + if (!vec || vec == buf->vecs + buf->count || buf->off >= vec->len) { + if (!tp->eof) { + // wait for more to be fed in + *len = 0; + return SUCCESS; + + } else { + // EOF! + return SET_ERROR(err, ERR_EOF); + } + } + + // amount of data available in this iovec + size_t available = vec->len - buf->off; + + // amount to read + size_t to_read = *len; + + // trim down? + if (to_read > available) + to_read = available; + + // copy + memcpy(buf_ptr, vec->buf + buf->off, to_read); + + + if (to_read < available) { + // bytes still left in the vector + buf->off += to_read; + + } else { + // consumed the whole vector + // XXX: release data? + buf->read_vec++; + buf->off = 0; + } + + // update len + *len = to_read; + + // ok + return SUCCESS; +} + +/** + * transport_methods::write implementation. + */ +static err_t transport_test_write (transport_t *transport, const void *data, size_t *len, error_t *err) +{ + struct transport_test *tp = transport_check(transport, &transport_test_type); + + // write it out + // XXX: partial writes? + if ((ERROR_CODE(err) = io_buf_write(&tp->send_buf, data, *len))) + goto error; + + // ok + return SUCCESS; + +error: + return ERROR_CODE(err); +} + +static err_t transport_test_events (transport_t *transport, short mask, error_t *err) +{ + struct transport_test *tp = transport_check(transport, &transport_test_type); + + (void) tp; + (void) mask; + (void) err; + + // XXX: don't re-trigger anything + + return SUCCESS; +} + +static void _transport_test_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); +} + diff -r d35e7cb3a489 -r e6a1ce44aecc src/transport_test.h --- /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 */