implement irc_queue for irc_conn, and add missing irc_queue_destroy, fix irc_conn_destroy
#include "irc_conn.h"
#include "irc_cmd.h"
#include "irc_proto.h"
#include "log.h"
#include <stdlib.h>
#include <string.h>
#include <assert.h>
/**
* Handle an async error on this IRC connection that we could not recover from any other way, the protocol is now dead,
* and should be considered as destroyed after this returns.
*
* For conveniance, this returns the ERROR_CODE
*/
static err_t irc_conn_set_error (struct irc_conn *conn, struct error_info *err)
{
// notify user callback
conn->callbacks.on_error(conn, err, conn->cb_arg);
return ERROR_CODE(err);
}
/**
* Update irc_conn.nickname
*/
static err_t irc_conn_set_nickname (struct irc_conn *conn, const char *nickname)
{
struct error_info err;
// strdup
if ((conn->nickname = strdup(nickname)) == NULL) {
SET_ERROR(&err, ERR_STRDUP);
// notify
return irc_conn_set_error(conn, &err);
}
// ok
return SUCCESS;
}
/**
* 001 <nick> :Welcome to the Internet Relay Network <nick>!<user>@<host>
*/
static void on_RPL_WELCOME (const struct irc_line *line, void *arg)
{
struct irc_conn *conn = arg;
// update state
conn->registering = false;
conn->registered = true;
// set our real nickname from the message target
if (irc_conn_set_nickname(conn, line->args[0]))
return;
// trigger callback
if (conn->callbacks.on_registered)
conn->callbacks.on_registered(conn, conn->cb_arg);
}
/**
* PING <server1> [ <server2> ]
*
* Send a 'PONG <server1>` reply right away.
*/
static void on_PING (const struct irc_line *line, void *arg)
{
struct irc_conn *conn = arg;
// just reply
irc_conn_PONG(conn, line->args[0]);
}
/**
* NICK <nickname>
*
* If the prefix is us, then update our nickname
*/
static void on_NICK (const struct irc_line *line, void *arg)
{
struct irc_conn *conn = arg;
// ignore if it's not us
if (!line->source || irc_cmp_nick(line->source->nickname, conn->nickname))
return;
// update our nickname
irc_conn_set_nickname(conn, line->args[0]);
}
/**
* CTCP ACTION handler
*/
static void irc_conn_on_CTCP_ACTION (const struct irc_line *line, void *arg)
{
struct irc_conn *conn = arg;
// build the pseudo-line and invoke
struct irc_line action_line = {
.source = line->source,
.command = "CTCP ACTION",
.args = {
line->args[0],
line->args[1],
NULL
}
};
// invoke the general command handlers
irc_cmd_invoke(&conn->handlers, &action_line);
}
/**
* Our command handlers
*/
static struct irc_cmd_handler irc_conn_handlers[] = {
{ IRC_RPL_WELCOME, &on_RPL_WELCOME },
{ "PING", &on_PING },
{ "NICK", &on_NICK },
{ NULL, NULL, },
}, irc_conn_ctcp_handlers[] = {
{ "ACTION", &irc_conn_on_CTCP_ACTION },
{ NULL, NULL }
};
/**
* Incoming CTCP message handler
*
* We only handle "simple" CTCP messages, i.e. those that begin and end with X-DELIM and contain a single tagged
* extended message. The full range of CTCP quoting etc specified in the "offical" spec referenced below is not
* implemented, as it is rarely used, or even implemented.
*
* http://www.irchelp.org/irchelp/rfc/ctcpspec.html
*/
static void irc_conn_on_CTCP (struct irc_conn *conn, const struct irc_line *line)
{
// copy the message data into a mutable buffer
char data_buf[strlen(line->args[1])], *data = data_buf;
strcpy(data_buf, line->args[1]);
// should only be called when this is true...
assert(*data++ == '\001');
// tokenize the extended message
// XXX: do something with the trailing data?
char *msg = strsep(&data, "\001");
// parse the "command" tag
char *tag = strsep(&msg, " ");
// invalid if missing
if (tag == NULL)
return log_warn("CTCP message with no tag: '%s'", data);
// parse the CTCP "line"
struct irc_line ctcp_line = {
// the sender of the message
.source = line->source,
// the CTCP extended message tag
.command = tag,
.args = {
// the destination of the message
line->args[0],
// the rest of the CTCP extended message payload
msg,
NULL
}
};
// invoke the CTCP command handlers
irc_cmd_invoke(&conn->ctcp_handlers, &ctcp_line);
}
/**
* Incoming line handler
*/
static void irc_conn_on_line (char *line_buf, void *arg)
{
struct irc_conn *conn = arg;
struct irc_line line;
struct irc_nm nm;
int err;
// log
log_debug("%s", line_buf);
// parse
if ((err = irc_line_parse(&line, &nm, line_buf))) {
log_warn("invalid line: %s: %s\n", line_buf, error_name(err));
return;
}
// trap CTCP messages
if (strcasecmp(line.command, "PRIVMSG") == 0 && line.args[1][0] == '\001')
// parse and invoke the CTCP command handlers
irc_conn_on_CTCP(conn, &line);
else
// invoke command handlers
irc_cmd_invoke(&conn->handlers, &line);
}
/**
* Transport failed
*/
static void irc_conn_on_error (struct error_info *err, void *arg)
{
struct irc_conn *conn = arg;
// EOF after quit?
if (ERROR_CODE(err) == ERR_READ_EOF && conn->quitting) {
// udpate states
conn->registered = false;
conn->quitting = false;
conn->quit = true;
// callback
if (conn->callbacks.on_quit)
conn->callbacks.on_quit(conn, conn->cb_arg);
} else {
// log
log_err_info(err, "line_proto error");
// trash ourselves
irc_conn_set_error(conn, err);
}
}
static struct line_proto_callbacks irc_conn_lp_callbacks = {
.on_line = &irc_conn_on_line,
.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)
{
struct irc_conn *conn;
// alloc new state struct
if ((conn = calloc(1, sizeof(struct irc_conn))) == NULL)
return SET_ERROR(err, ERR_CALLOC);
// init state
conn->callbacks = *callbacks;
conn->cb_arg = cb_arg;
// initialize command handlers
irc_cmd_init(&conn->handlers);
irc_cmd_init(&conn->ctcp_handlers);
// add the core handlers
if (
(ERROR_CODE(err) = irc_cmd_add(&conn->handlers, irc_conn_handlers, conn))
|| (ERROR_CODE(err) = irc_cmd_add(&conn->ctcp_handlers, irc_conn_ctcp_handlers, conn))
)
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))
goto error;
// create the outgoing line queue
if (irc_queue_create(&conn->out_queue, conn->lp, err))
goto error;
// ok
*conn_ptr = conn;
return SUCCESS;
error:
// release
irc_conn_destroy(conn);
return ERROR_CODE(err);
}
void irc_conn_destroy (struct irc_conn *conn)
{
// the line_proto
if (conn->lp)
line_proto_release(conn->lp);
// the queue
if (conn->out_queue)
irc_queue_destroy(conn->out_queue);
// the command handlers
irc_cmd_free(&conn->handlers);
irc_cmd_free(&conn->ctcp_handlers);
// additional data
free(conn->nickname);
// the irc_conn itself
free(conn);
}
err_t irc_conn_add_cmd_handlers (struct irc_conn *conn, struct irc_cmd_handler *handlers, void *arg)
{
// use the irc_cmd stuff
return irc_cmd_add(&conn->handlers, handlers, arg);
}
err_t irc_conn_register (struct irc_conn *conn, const struct irc_conn_register_info *info)
{
err_t err;
if (conn->registering || conn->registered)
return ERR_IRC_CONN_REGISTER_STATE;
// send the initial messages
if (
(err = irc_conn_NICK(conn, info->nickname))
|| (err = irc_conn_USER(conn, info->username, info->realname))
)
return err;
// set state
conn->registering = true;
// ok
return SUCCESS;
}
err_t irc_conn_send (struct irc_conn *conn, const struct irc_line *line)
{
// just proxy off to irc_quuee
return irc_queue_process(conn->out_queue, line);
}
err_t irc_conn_NICK (struct irc_conn *conn, const char *nickname)
{
// NICK <nickname>
struct irc_line line = {
NULL, "NICK", { nickname, NULL }
};
return irc_conn_send(conn, &line);
}
err_t irc_conn_USER (struct irc_conn *conn, const char *username, const char *realname)
{
// USER <user> <mode> <unused> <realname>
struct irc_line line = {
NULL, "USER", { username, "0", "*", realname, NULL }
};
return irc_conn_send(conn, &line);
}
err_t irc_conn_PONG (struct irc_conn *conn, const char *target)
{
// PONG <server> [ <server2> ]
// params are actually the wrong way around now, but nobody cares
struct irc_line line = {
NULL, "PONG", { target, NULL }
};
return irc_conn_send(conn, &line);
}
err_t irc_conn_JOIN (struct irc_conn *conn, const char *channel)
{
// JOIN ( <channel> *( "," <channel> ) [ <key> *( "," <key> ) ] ) / "0"
struct irc_line line = {
NULL, "JOIN", { channel, NULL }
};
return irc_conn_send(conn, &line);
}
err_t irc_conn_QUIT (struct irc_conn *conn, const char *message)
{
err_t err;
struct irc_line line = {
NULL, "QUIT", { message, NULL }
};
// state check
if (conn->quitting || conn->quit)
return ERR_IRC_CONN_QUIT_STATE;
// try and send
if ((err = irc_conn_send(conn, &line)))
return err;
// mark as quitting
conn->quitting = true;
// ok
return SUCCESS;
}