--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/TODO Thu Mar 26 21:46:10 2009 +0200
@@ -0,0 +1,17 @@
+sock:
+ * async connect + resolve
+ * async SSL handshake
+ * sock_openssl, or improve sock_gnutls
+ * tests for all of the above...
+
+irc_net:
+ * tracking of channel users list
+
+modules:
+ * proper unload support, there needs to be some kind of completion notification thing that can then destroy the module
+ once it's completely unloaded - maybe some kind of module-resource-reference-counting thing, which will eventually
+ be needed anyways?
+
+irc_log:
+ * logging of NICK/QUIT
+ * handling of SQL errors?
--- a/src/CMakeLists.txt Mon Mar 16 23:55:30 2009 +0200
+++ b/src/CMakeLists.txt Thu Mar 26 21:46:10 2009 +0200
@@ -10,7 +10,7 @@
# define our source code modules
set (CORE_SOURCES error.c log.c)
set (SOCK_SOURCES sock.c sock_tcp.c sock_gnutls.c sock_test.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)
+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)
set (NEXUS_SOURCES nexus.c ${CORE_SOURCES} ${SOCK_SOURCES} ${IRC_SOURCES} signals.c module.c)
set (TEST_SOURCES test.c ${CORE_SOURCES} ${SOCK_SOURCES} ${IRC_SOURCES})
--- a/src/irc_chan.c Mon Mar 16 23:55:30 2009 +0200
+++ b/src/irc_chan.c Thu Mar 26 21:46:10 2009 +0200
@@ -3,6 +3,7 @@
#include "log.h"
#include <stdlib.h>
+#include <string.h>
#include <assert.h>
const char* irc_chan_name (struct irc_chan *chan)
@@ -11,11 +12,48 @@
}
/**
+ * Add or update a nickname to the irc_chan.users list.
+ *
+ * If the given nickname already exists in our users list, this does nothing. Otherwise, an irc_user is aquired using
+ * irc_net_get_user, and a new irc_chan_user struct added to our users list.
+ */
+static err_t irc_chan_add_user (struct irc_chan *chan, const char *nickname)
+{
+ struct irc_user *user;
+ struct irc_chan_user *chan_user;
+ err_t err;
+
+ // skip if already listed
+ if (irc_chan_get_user(chan, nickname))
+ return SUCCESS;
+
+ // lookup/create the irc_user state
+ if ((err = irc_net_get_user(chan->net, &user, nickname)))
+ return err;
+
+ // alloc the new irc_chan_user
+ if ((chan_user = calloc(1, sizeof(*chan_user))) == NULL) {
+ // XXX: release irc_user
+ return ERR_CALLOC;
+ }
+
+ // store
+ chan_user->user = user;
+
+ // add to users list
+ LIST_INSERT_HEAD(&chan->users, chan_user, chan_users);
+
+ // ok
+ return SUCCESS;
+}
+
+/**
* :nm JOIN <channel>
*/
static void irc_chan_on_JOIN (const struct irc_line *line, void *arg)
{
struct irc_chan *chan = arg;
+ err_t err;
// us?
if (irc_prefix_cmp_nick(line->prefix, chan->net->conn->nickname) == 0) {
@@ -29,8 +67,19 @@
IRC_CHAN_INVOKE_CALLBACK(chan, on_self_join);
} else {
- // XXX: who cares
+ char prefix_buf[IRC_PREFIX_MAX];
+ struct irc_nm nm;
+ // parse the nickname
+ if ((err = irc_nm_parse(&nm, prefix_buf, line->prefix)))
+ return log_warn("invalid prefix: %s", line->prefix);
+
+ // add them
+ if ((err = irc_chan_add_user(chan, nm.nickname)))
+ return log_warn("irc_chan_add_user(%s, %s): %s", irc_chan_name(chan), nm.nickname, error_name(err));
+
+ // invoke callback (source)
+ IRC_CHAN_INVOKE_CALLBACK(chan, on_join, &nm);
}
}
@@ -50,22 +99,69 @@
if ((err = irc_nm_parse(&nm, prefix_buf, line->prefix))) {
log_warn("invalid prefix: %s", line->prefix);
- // invoke callback with NULL prefix
+ // invoke callback with NULL source
IRC_CHAN_INVOKE_CALLBACK(chan, on_msg, NULL, msg);
} else {
- // invoke callback (prefix, message)
+ // invoke callback (source, message)
IRC_CHAN_INVOKE_CALLBACK(chan, on_msg, &nm, msg);
}
}
/**
+ * Add/update nicknames to users list using irc_chan_add_user
+ *
+ * @see IRC_RPL_NAMREPLY
+ */
+static void irc_chan_on_RPL_NAMREPLY (const struct irc_line *line, void *arg)
+{
+ struct irc_chan *chan = arg;
+ char chanflags[IRC_CHANFLAGS_MAX];
+ const char *nickname;
+ err_t err;
+
+ // the args
+ const char *arg_names = line->args[3];
+
+ // copy the nicklist to a mutable buffer
+ char names_buf[strlen(arg_names) + 1], *names = names_buf;
+ strcpy(names, arg_names);
+
+ // iterate over each name
+ // XXX: nickflags
+ while ((nickname = strsep(&names, " "))) {
+ // parse off the channel flags
+ nickname = irc_nick_chanflags(nickname, chanflags);
+
+ // add/update
+ // XXX: do something with chanflags
+ if ((err = irc_chan_add_user(chan, nickname)))
+ log_warn("irc_chan_add_user(%s, %s): %s", irc_chan_name(chan), nickname, error_name(err));
+ }
+}
+
+/**
+ * Channel join sequence complete
+ *
+ * @see IRC_RPL_ENDOFNAMES
+ */
+static void irc_chan_on_RPL_ENDOFNAMES (const struct irc_line *line, void *arg)
+{
+ struct irc_chan *chan = arg;
+
+ // XXX: update state
+ log_info("channel join sync complete");
+}
+
+/**
* Core command handlers
*/
struct irc_cmd_handler _cmd_handlers[] = {
- { "JOIN", &irc_chan_on_JOIN },
- { "PRIVMSG", &irc_chan_on_PRIVMSG },
- { NULL, NULL }
+ { "JOIN", &irc_chan_on_JOIN },
+ { "PRIVMSG", &irc_chan_on_PRIVMSG },
+ { IRC_RPL_NAMREPLY, &irc_chan_on_RPL_NAMREPLY },
+ { IRC_RPL_ENDOFNAMES, &irc_chan_on_RPL_ENDOFNAMES },
+ { NULL, NULL }
};
err_t irc_chan_create (struct irc_chan **chan_ptr, struct irc_net *net, const struct irc_chan_info *info, struct error_info *err)
@@ -81,6 +177,7 @@
chan->info = *info;
// init
+ LIST_INIT(&chan->users);
irc_cmd_init(&chan->handlers);
CHAIN_INIT(&chan->callbacks);
@@ -102,6 +199,8 @@
void irc_chan_destroy (struct irc_chan *chan)
{
+ // XXX: free chan_users list
+
// free command handlers
irc_cmd_free(&chan->handlers);
@@ -122,6 +221,22 @@
chain_remove(&chan->callbacks, callbacks, arg);
}
+struct irc_chan_user* irc_chan_get_user (struct irc_chan *chan, const char *nickname)
+{
+ struct irc_chan_user *chan_user = NULL;
+
+ // look for it...
+ LIST_FOREACH(chan_user, &chan->users, chan_users) {
+ if (irc_cmp_nick(nickname, chan_user->user->nickname) == 0) {
+ // found
+ return chan_user;
+ }
+ }
+
+ // not found
+ return NULL;
+}
+
err_t irc_chan_join (struct irc_chan *chan)
{
err_t err;
--- a/src/irc_chan.h Mon Mar 16 23:55:30 2009 +0200
+++ b/src/irc_chan.h Thu Mar 26 21:46:10 2009 +0200
@@ -10,6 +10,7 @@
struct irc_chan;
#include "irc_net.h"
+#include "irc_user.h"
#include "irc_cmd.h"
#include "irc_proto.h"
#include "error.h"
@@ -33,6 +34,9 @@
/** Joined the channel */
void (*on_self_join) (struct irc_chan *chan, void *arg);
+ /** Someone joined the channel */
+ void (*on_join) (struct irc_chan *chan, const struct irc_nm *source, void *arg);
+
/** Someone sent a message to the channel */
void (*on_msg) (struct irc_chan *chan, const struct irc_nm *source, const char *msg, void *arg);
};
@@ -53,6 +57,17 @@
} while (0);
/**
+ * Per-channel-user state
+ */
+struct irc_chan_user {
+ /** The per-network user info */
+ struct irc_user *user;
+
+ /** The irc_chan list */
+ LIST_ENTRY(irc_chan_user) chan_users;
+};
+
+/**
* IRC channel state
*/
struct irc_chan {
@@ -65,15 +80,22 @@
/** Our identifying info */
struct irc_chan_info info;
- /** State flags @{ */
- /** JOIN request sent, waiting for reply */
- bool joining;
+ /**
+ * @group State flags
+ * @{
+ */
- /** Currently joined to channel */
- bool joined;
+ /** JOIN request sent, waiting for reply */
+ bool joining;
+
+ /** Currently joined to channel */
+ bool joined;
// @}
+ /** List of users on channel */
+ LIST_HEAD(irc_chan_users_list, irc_chan_user) users;
+
/** General command handlers */
irc_cmd_handlers_t handlers;
@@ -114,6 +136,15 @@
void irc_chan_remove_callbacks (struct irc_chan *chan, const struct irc_chan_callbacks *callbacks, void *arg);
/**
+ * Look up an irc_chan_user struct by nickname for this channel, returning NULL if no such chan_user exists.
+ *
+ * @param chan the channel context
+ * @param nickname the user's current nickname
+ * @param return the irc_chan_user state, or NULL if nickname not found
+ */
+struct irc_chan_user* irc_chan_get_user (struct irc_chan *chan, const char *nickname);
+
+/**
* Send the initial JOIN message.
*
* The channel must be in the IRC_CHAN_INIT state, and will transition to the IRC_CHAN_JOINING state.
--- a/src/irc_net.c Mon Mar 16 23:55:30 2009 +0200
+++ b/src/irc_net.c Thu Mar 26 21:46:10 2009 +0200
@@ -4,6 +4,11 @@
#include <stdlib.h>
#include <string.h>
+const char* irc_net_name (struct irc_net *net)
+{
+ return net->info.network;
+}
+
/**
* Something happaned which caused our irc_conn to fail. Destroy it, and recover. XXX: somehow
*/
@@ -108,6 +113,26 @@
}
/**
+ * Propagate line to irc_chan based on args[1]
+ */
+static void irc_net_on_chan1 (const struct irc_line *line, void *arg)
+{
+ struct irc_net *net = arg;
+
+ irc_net_propagate_chan(net, line, line->args[1]);
+}
+
+/**
+ * Propagate line to irc_chan based on args[2]
+ */
+static void irc_net_on_chan2 (const struct irc_line *line, void *arg)
+{
+ struct irc_net *net = arg;
+
+ irc_net_propagate_chan(net, line, line->args[2]);
+}
+
+/**
* :nm (PRIVMSG|NOTICE) <target> <message>
*
* Either propagate to channel if found, otherwise handle as a privmsg
@@ -131,18 +156,22 @@
* Our irc_cmd handler list
*/
static struct irc_cmd_handler _cmd_handlers[] = {
- { "JOIN", &irc_net_on_chan0 },
- { "PART", &irc_net_on_chan0 },
- { "MODE", &irc_net_on_chan0 },
- { "TOPIC", &irc_net_on_chan0 },
- { "KICK", &irc_net_on_chan0 },
+ { "JOIN", &irc_net_on_chan0 },
+ { "PART", &irc_net_on_chan0 },
+ { "MODE", &irc_net_on_chan0 },
+ { "TOPIC", &irc_net_on_chan0 },
+ { "KICK", &irc_net_on_chan0 },
- { "PRIVMSG", &irc_net_on_msg },
- { "NOTICE", &irc_net_on_msg },
+ { "PRIVMSG", &irc_net_on_msg },
+ { "NOTICE", &irc_net_on_msg },
// XXX: NICK/QUIT
- { NULL, NULL }
+ // numerics
+ { IRC_RPL_NAMREPLY, &irc_net_on_chan2 },
+ { IRC_RPL_ENDOFNAMES, &irc_net_on_chan1 },
+
+ { NULL, NULL }
};
err_t irc_net_create (struct irc_net **net_ptr, const struct irc_net_info *info, struct error_info *err)
@@ -157,6 +186,7 @@
// initialize
net->info = *info;
TAILQ_INIT(&net->channels);
+ LIST_INIT(&net->users);
if (info->raw_sock) {
log_info("connected using raw socket: %p", info->raw_sock);
@@ -225,6 +255,8 @@
irc_chan_destroy(chan);
}
+ // XXX: our users
+
// ourselves
free(net);
}
@@ -269,6 +301,34 @@
return NULL;
}
+err_t irc_net_get_user (struct irc_net *net, struct irc_user **user_ptr, const char *nickname)
+{
+ struct irc_user *user = NULL;
+ err_t err;
+
+ // look for it...
+ LIST_FOREACH(user, &net->users, net_users) {
+ if (irc_cmp_nick(nickname, user->nickname) == 0) {
+ // found existing
+ *user_ptr = user;
+
+ return SUCCESS;
+ }
+ }
+
+ // create a new user struct
+ if ((err = irc_user_create(&user, net, nickname)))
+ return err;
+
+ // add to our list
+ LIST_INSERT_HEAD(&net->users, user, net_users);
+
+ // ok
+ *user_ptr = user;
+
+ return SUCCESS;
+}
+
err_t irc_net_quit (struct irc_net *net, const char *message)
{
if (!net->conn)
--- a/src/irc_net.h Mon Mar 16 23:55:30 2009 +0200
+++ b/src/irc_net.h Thu Mar 26 21:46:10 2009 +0200
@@ -7,6 +7,9 @@
* Support for IRC networks. This is similar to an IRC connection, but we keep track of channel state, and handle
* reconnects.
*/
+
+struct irc_net;
+
#include "error.h"
#include "irc_conn.h"
#include "irc_chan.h"
@@ -48,11 +51,19 @@
/** The list of IRC channel states */
TAILQ_HEAD(irc_net_chan_list, irc_chan) channels;
+ /** Our set of valid irc_user items for use with irc_chan */
+ LIST_HEAD(irc_net_users_list, irc_user) users;
+
/** The irc_client list */
TAILQ_ENTRY(irc_net) client_networks;
};
/**
+ * Return the networks's name
+ */
+const char* irc_net_name (struct irc_net *net);
+
+/**
* Create a new IRC network state, using the given network info to connect/register.
*
* Errors are returned via *err, also returning the error code.
@@ -84,12 +95,21 @@
/**
* Look up an existing irc_chan by name, returning NULL if not found.
*
- * @param net the network state
+ * @param net the network context
* @param channel the channel name
*/
struct irc_chan* irc_net_get_chan (struct irc_net *net, const char *channel);
/**
+ * Look up an existing irc_user by nickname, or create a new one if not found.
+ *
+ * @param user_ptr set to the new irc_user state
+ * @param net the network context
+ * @param nickname the user's current nickname
+ */
+err_t irc_net_get_user (struct irc_net *net, struct irc_user **user_ptr, const char *nickname);
+
+/**
* Quit from the IRC network, this sends a QUIT message to the server, and waits for the connection to be closed cleanly.
*/
err_t irc_net_quit (struct irc_net *net, const char *message);
--- a/src/irc_proto.c Mon Mar 16 23:55:30 2009 +0200
+++ b/src/irc_proto.c Thu Mar 26 21:46:10 2009 +0200
@@ -81,3 +81,19 @@
// doesn't match
return 1;
}
+
+const char* irc_nick_chanflags (const char *nick, char chanflags[IRC_CHANFLAGS_MAX])
+{
+ char *cf = chanflags;
+
+ // consume the chanflags, using strchr to look for the char in the set of chanflags...
+ while (strchr(IRC_CHANFLAGS, *nick) && (cf < chanflags + IRC_CHANFLAGS_MAX - 1))
+ *cf++ = *nick++;
+
+ // NUL-terminate chanflags
+ *cf = '\0';
+
+ // then return the pointer to the first nickname char
+ return nick;
+}
+
--- a/src/irc_proto.h Mon Mar 16 23:55:30 2009 +0200
+++ b/src/irc_proto.h Thu Mar 26 21:46:10 2009 +0200
@@ -12,7 +12,7 @@
/**
* Maximum length of an IRC nickname including terminating NUL.
*/
-#define IRC_NICK_MAX 31
+#define IRC_NICK_MAX (30 + 1)
/**
* Maximum length of an IRC prefix/nickmask string, including any termianting NULs.
@@ -22,6 +22,16 @@
#define IRC_PREFIX_MAX 510
/**
+ * Maximum length of an IRC nickname channel flags prefix, including any terminating NUL
+ */
+#define IRC_CHANFLAGS_MAX (8 + 1)
+
+/**
+ * The set of characters that are considered nickname channel flag prefixes (i.e. op, voice, etc).
+ */
+#define IRC_CHANFLAGS "@+"
+
+/**
* Parsed nickmask
*/
struct irc_nm {
@@ -66,15 +76,47 @@
int irc_prefix_cmp_nick (const char *prefix, const char *nick);
/**
+ * Copy the prefixed flags from the given nickname into the given flags buffer (of at least IRC_CHANFLAGS_MAX bytes) and
+ * NUL-terminate it. Returns a pointer to the actual nickname itself.
+ *
+ * @param nick the nickname, including prefixed chanflags
+ * @param chanflags the buffer to store the parsed chanflags into
+ * @returns a pointer to the first char of the actual nickname
+ */
+const char* irc_nick_chanflags (const char *nick, char chanflags[IRC_CHANFLAGS_MAX]);
+
+/**
* @group IRC command numerics
* @{
*/
/**
- * 001 <nick> :Welcome to the Internet Relay Network <nick>!<user>@<host>
+ * 001 <nick> "Welcome to the Internet Relay Network <nick>!<user>@<host>"
+ *
+ * The first "official" reply sent by the server after the NICK/USER registration was accepted.
*/
#define IRC_RPL_WELCOME "001"
+/**
+ * 353 <nick> ( "=" | "*" | "@" ) <channel> ( [ "@" | "+" ] <nick> [ " " ... ] )
+ *
+ * Sent by the server after a JOIN/NAMES command to give the full list of users currently on a channel. The list may
+ * be split into multiple messages RPL_NAMREPLY messages, which are then terminated by a RPL_ENDOFNAMES reply.
+ *
+ * The first argument char denotes the "channel type", and is, apparently, one of the following, for those who care:
+ * @ secret
+ * * private
+ * = others (public)
+ */
+#define IRC_RPL_NAMREPLY "353"
+
+/**
+ * 366 <nick> <channel> "End of NAMES list"
+ *
+ * Sent by the server to terminate a sequence of zero or more RPL_NAMREPLY messages from a JOIN/NAMES command.
+ */
+#define IRC_RPL_ENDOFNAMES "366"
+
// @}
#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/irc_user.c Thu Mar 26 21:46:10 2009 +0200
@@ -0,0 +1,29 @@
+#include "irc_user.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+err_t irc_user_create (struct irc_user **user_ptr, struct irc_net *net, const char *nickname)
+{
+ struct irc_user *user;
+
+ // allocate
+ if ((user = calloc(1, sizeof(*user))) == NULL)
+ return ERR_CALLOC;
+
+ // copy nickname
+ if ((user->nickname = strdup(nickname)) == NULL)
+ return ERR_STRDUP;
+
+ // ok
+ *user_ptr = user;
+
+ return SUCCESS;
+}
+
+void irc_user_destroy (struct irc_user *user)
+{
+ free(user->nickname);
+ free(user);
+}
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/irc_user.h Thu Mar 26 21:46:10 2009 +0200
@@ -0,0 +1,37 @@
+#ifndef IRC_USER_H
+#define IRC_USER_H
+
+/**
+ * Minimal state for tracking per-user state for an irc_net. Mainly, the purpose of this module is to support the
+ * irc_chan.users implementation, such that when a user's nickname is changed, one does not need to modify all
+ * irc_chan lists, but rather, just replace the nickname field here.
+ *
+ * XXX: should this tie in to irc_net to register its own NICK/QUIT handlers?
+ */
+#include "irc_net.h"
+#include "error.h"
+
+/**
+ * The basic per-network user info. The lifetime of an irc_user struct is strictly for the duration between the user
+ * JOIN'ing onto the first irc_chan we are on (or us JOIN'ing onto it while they are on it), and them PART'ing the
+ * channel or QUIT'ing.
+ */
+struct irc_user {
+ /** The user's nickname */
+ char *nickname;
+
+ /** Our entry in the irc_net list */
+ LIST_ENTRY(irc_user) net_users;
+};
+
+/**
+ * Create a new irc_user with the given nickname. The nickname is copied for storage.
+ */
+err_t irc_user_create (struct irc_user **user_ptr, struct irc_net *net, const char *nickname);
+
+/**
+ * Destroy an irc_user, releasing memory
+ */
+void irc_user_destroy (struct irc_user *user);
+
+#endif
--- a/src/test.c Mon Mar 16 23:55:30 2009 +0200
+++ b/src/test.c Thu Mar 26 21:46:10 2009 +0200
@@ -220,6 +220,18 @@
return sock_test_add_recv_str(sock, str);
}
+/**
+ * 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 test_dump_str (void)
{
log_info("dumping example strings on stdout:");
@@ -498,15 +510,17 @@
irc_conn_destroy(conn);
}
-struct _test_net_ctx {
+struct test_chan_ctx {
+ /** The channel we're supposed to be testing */
struct irc_chan *chan;
-
+
+ /** on_self_join callback called */
bool on_chan_self_join;
};
void _on_chan_self_join (struct irc_chan *chan, void *arg)
{
- struct _test_net_ctx *ctx = arg;
+ struct test_chan_ctx *ctx = arg;
assert(chan == ctx->chan);
@@ -519,60 +533,195 @@
.on_self_join = &_on_chan_self_join,
};
-void test_irc_net (void)
+/**
+ * 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 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);
+}
+
+/**
+ * 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, struct test_chan_ctx *ctx)
+{
struct irc_chan *chan;
struct irc_chan_info chan_info = {
.channel = "#test",
};
struct error_info err;
- struct _test_net_ctx ctx = { NULL, false };
-
- // create the test socket
- assert((sock = sock_test_create()));
- // create the irc_net
- net_info.raw_sock = SOCK_TEST_BASE(sock);
- assert_success(irc_net_create(&net, &net_info, &err));
+ // initialize the given ctx
+ memset(ctx, 0, sizeof(*ctx));
// add a channel
- log_info("test offline irc_net_add_chan");
- assert((chan = irc_net_add_chan(net, &chan_info)));
- assert(!chan->joining && !chan->joined);
- assert_success(irc_chan_add_callbacks(chan, &_chan_callbacks, &ctx));
- ctx.chan = chan;
-
- // test register output
- assert_sock_data(sock, "NICK nick\r\nUSER user 0 * realname\r\n");
+ 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;
- // registration reply
- log_info("test irc_conn_on_RPL_WELCOME");
- test_sock_push(sock, "001 mynick :Blaa blaa blaa\r\n");
- assert(net->conn->registered);
- assert_strcmp(net->conn->nickname, "mynick");
-
+ // 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
- log_info("test irc_net_conn_registered -> irc_chan_join");
- assert(chan->joining);
+ assert(ctx->chan->joining);
assert_sock_data(sock, "JOIN #test\r\n");
// JOIN reply
- log_info("test irc_chan_on_JOIN");
test_sock_push(sock, ":mynick!user@host JOIN #test\r\n");
- assert(!chan->joining && chan->joined);
- assert(ctx.on_chan_self_join);
+ assert(!ctx->chan->joining && ctx->chan->joined);
+ assert(ctx->on_chan_self_join);
+}
- // test errors by setting EOF
- log_info("test irc_net_error");
- sock_test_set_recv_eof(sock);
- assert(net->conn == NULL);
+/**
+ * Creates an irc_chan on the given irc_net, and checks up to the JOIN reply
+ */
+struct irc_chan* setup_irc_chan (struct sock_test *sock, struct irc_net *net, struct test_chan_ctx *ctx)
+{
+ setup_irc_chan_raw(net, ctx);
+ do_irc_chan_join(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, &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);
+}
+
+/**
+ * Ensure that an irc_chan_user exists for the given channel/nickname, and return it
+ */
+struct irc_chan_user* check_chan_user (struct irc_chan *chan, const char *nickname)
+{
+ struct irc_chan_user *chan_user;
+
+ if ((chan_user = irc_chan_get_user(chan, nickname)) == NULL)
+ FATAL("user %s not found in channel %s", dump_str(nickname), dump_str(irc_chan_name(chan)));
+
+ log_debug("%s -> %p: user=%p, nickname=%s", nickname, chan_user, chan_user->user, chan_user->user->nickname);
+
+ assert_strcmp(chan_user->user->nickname, nickname);
+
+ return chan_user;
+}
+
+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);
+ struct irc_chan *chan = setup_irc_chan(sock, net, &ctx);
+
+ // RPL_NAMREPLY
+ log_info("test irc_chan_on_RPL_NAMREPLY");
+ test_sock_push(sock, "353 mynick = #test :mynick userA +userB @userC\r\n");
+
+ check_chan_user(chan, "mynick");
+ check_chan_user(chan, "userA");
+ check_chan_user(chan, "userB");
+ check_chan_user(chan, "userC");
// cleanup
irc_net_destroy(net);
@@ -589,12 +738,14 @@
void (*func) (void);
} _tests[] = {
- { "dump_str", &test_dump_str },
- { "sock_test", &test_sock_test },
- { "line_proto", &test_line_proto },
- { "irc_conn", &test_irc_conn },
- { "irc_net", &test_irc_net },
- { NULL, NULL }
+ { "dump_str", &test_dump_str },
+ { "sock_test", &test_sock_test },
+ { "line_proto", &test_line_proto },
+ { "irc_conn", &test_irc_conn },
+ { "irc_net", &test_irc_net },
+ { "irc_chan_add_offline", &test_irc_chan_add_offline },
+ { "irc_chan_namreply", &test_irc_chan_namreply },
+ { NULL, NULL }
};
int main (int argc, char **argv)