src/irc_conn.h
author Tero Marttila <terom@fixme.fi>
Thu, 28 May 2009 01:17:36 +0300
branchnew-lib-errors
changeset 219 cefec18b8268
parent 217 7728d6ec3abf
permissions -rw-r--r--
some of the lib/transport stuff compiles
#ifndef IRC_CONN_H
#define IRC_CONN_H

/**
 * @file
 *
 * Support for connections to IRC servers, a rather fundamental thing. This holds the line_proto to handle the network
 * communications, and then takes care of sending/receiving commands, as well as some core functionality like
 * responding to PING requests, and tracking our current nickname.
 *
 * XXX: split into irc_proto and irc_client
 */

struct irc_conn;

#include "error.h"
#include <lib/transport.h>
#include <lib/line_proto.h>
#include "irc_queue.h"
#include "irc_line.h"
#include "irc_cmd.h"
#include <stdbool.h>

/**
 * The info required to register using irc_conn_register()
 */
struct irc_conn_register_info {
    /** Nickname to use on that server */
    const char *nickname;

    /** Username to supply */
    const char *username;

    /** Realname to supply */
    const char *realname;
};

/**
 * Connection state callbacks
 */
struct irc_conn_callbacks {
    /**
     * Recieved RPL_WELCOME, so irc_conn_register has completed, and we are now registered.
     */
    void (*on_registered) (struct irc_conn *conn, void *arg);

    /**
     * The connection has failed in some way, and can not be considered useable anymore. Sending messages won't work,
     * and no more messages will be received. The connection should be destroyed, probably directly from this callback.
     *
     * NOTE: Implementing this callback is mandatory
     */
    void (*on_error) (struct irc_conn *conn, error_t *err, void *arg);

    /**
     * The connection was closed cleanly after irc_conn_QUIT.
     *
     * You probably want to destroy the irc_conn now to clean up.
     */
    void (*on_quit) (struct irc_conn *conn, void *arg);
};

/**
 * A single sock_stream connection to a single IRC server, providing a nice interface to the low-level IRC protocol.
 *
 * Create a irc_conn from a connected sock_stream using irc_conn_create(), providing the high-level callbacks. Then,
 * register handlers for the IRC protocol messages recieved from the server using irc_conn_add_cmd_handlers() or
 * irc_cmd_add and irc_conn::handlers/irc_conn::ctcp_handlers.
 *
 * You can then perform the registration step using irc_conn_register(), providing a irc_conn_register_info struct with
 * the relevant info.
 *
 * Once you have succesfully registered (RPL_WELCOME recieved, irc_conn_callbacks::on_registered called,
 * irc_conn::registered set), you can then send messages to the server in the form of irc_line's using the set of
 * irc_conn_send functions.
 */
struct irc_conn {
    /** We are a line-based protocol, this wraps the given sock_stream */
    struct line_proto *lp;

    /** Outgoing line queue */
    struct irc_queue *out_queue;

    /** High-level callbacks */
    struct irc_conn_callbacks callbacks;

    /** Opaque argument for callbacks */
    void *cb_arg;

    /** @defgroup irc_conn_status Status flags
     * @{ 
     */
    /** Registration request sent */
    bool registering;

    /** Registered (as in, we have a working nickname) */
    bool registered;

    /** Quit message sent, waiting for server to close connection */
    bool quitting;

    /** Quit message sent, connection closed, we're done */
    bool quit;
    
    // @}
    
    /** 
     * Our current, actual nickname. This is set to NULL while we are still registering, but is then initialized when we
     * get the first RPL_WELCOME message, and then kept up-to-date by handling relevant NICK messages.
     */
    char *nickname;

    /**
     * General IRC command handlers. These handlers are invoked directly for all irc_line's recieved from the IRC
     * server, everything including core PING/NICK/RPL_WELCOME handling is implemented using these. You should add
     * your own handlers using irc_conn_add_cmd_handlers() to implement whatever functionality it is that you want;
     * see irc_cmd.h for more info about how these work. The "command" field is the literal IRC command as received
     * from the server itself.
     *
     * So for example, the following line:
     * \verbatim :nick!user@host PRIVMSG #foo :Hello everyone! \endverbatim
     *
     * results in the following irc_line:
     * \code
     *  { { "nick", "user", "host" }, "PRIVMSG", { "#foo", "Hello everyone!", NULL } }
     * \endcode
     *
     * As with all rules, there are exceptions - CTCP messages are handled differently. PRIVMSG's which contain
     * CTCP extended messages are trapped before these handlers are invoked, and are instead processed using
     * irc_conn::ctcp_handlers. Some built-in CTCP message handlers are provided that then submit these messages back
     * to this main irc_conn::handlers list, as pseudo-irc_line's with a command field like "CTCP ACTION". These
     * include:
     *
     *  CTCP ACTION     - { { "nick", "user", "host" }, "CTCP ACTION", "#foo", "sends an action message" }
     *
     * @see irc_cmd.h
     * @see irc_line
     * @see irc_conn::ctcp_handlers
     */
    struct irc_cmd_handlers handlers;

    /** 
     * CTCP command handlers. These handlers are invoked for all PRIVMSG's recieved from the IRC server which begin
     * with the CTCP X-DELIM char (in other words, a simplified CTCP spec is implemented). These PRIVMSG's will not be
     * seen using irc_conn::handlers. These CTCP messages are then parsed, and a pseudo-irc_line is created, with the
     * same source as the real message, the command set to the CTCP extended message tag, args[0] as the PRIVMSG's
     * destination, and args[1] as the rest of the CTCP extended message payload.
     *
     * So for example, the following line:
     * \verbatim :nick!user@host PRIVMSG #foo :\001ACTION does  something lame\001 \endverbatim
     *
     * results in the following irc_line:
     * \code
     *  { { "nick", "user", "host" }, "ACTION", "#foo", "does  something lame" }
     * \endcode
     *
     * Internally, some of these CTCP messages are then redirected back to handlers for convenience; currently only
     * "ACTION".
     *
     * @see irc_cmd.h
     * @see irc_line
     * @see irc_conn::handlers
     */
    struct irc_cmd_handlers ctcp_handlers;
};

/**
 * Create a new irc_conn using the given sock_stream, which should be connected to an IRC server (i.e. ready for
 * read/write).
 *
 * This does not yet send any requests to the server, it only sets up the core state. Use irc_conn_register() to 
 * actually register with the server.
 *
 * On success, the resulting irc_conn is returned via *conn_ptr with SUCCESS. Otherwise, ERR_* with error info returned
 * via *err.
 *
 * @param conn_ptr returned new irc_conn structure
 * @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, 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.
 *
 * This does not end the session cleanly, and is intended mainly to be used after clean exit, or to clean up after errors.
 */
void irc_conn_destroy (struct irc_conn *conn);

/**
 * Add a new chain of command handler callbacks to be used to handle irc_lines from the server. The given arg will be
 * passed to the callbacks as the context argument. The chain will be appended to the end of the current list of chains.
 *
 * @param conn the connection to use
 * @param handlers the array of irc_cmd_handler structs, terminated with a NULL entry
 * @param arg the context argument to use for the callbacks
 */
err_t irc_conn_add_cmd_handlers (struct irc_conn *conn, struct irc_cmd_handler *handlers, void *arg);

/**
 * Register with the IRC server using the given registration info (initial nickname etc.)
 *
 * This sends the NICK/USER command sequence.
 *
 * @param conn the connection to use
 * @param info the information required to register, including nickname/username/etc
 */
err_t irc_conn_register (struct irc_conn *conn, const struct irc_conn_register_info *info);

/**
 * Compare the given nickname against our current nickname, returning true if it represents ourselves.
 *
 * As a convenience function, this is NULL-safe - it can safely be called with a NULL conn, NULL nickname, or a
 * conn that doesn't yet have a nickname, and it will simply return false.
 */
bool irc_conn_self (struct irc_conn *conn, const char *nickname);

/**
 * Send a generic IRC message by formatting the given irc_line and sending it as a line.
 *
 * Use the irc_conn_COMMAND functions defined below for a more convenient interface.
 *
 * An error is returned if the line contains invalid data or writing the line to the sock_stream fails. Possible socket
 * failures will also be reported using irc_conn_callbacks::on_error, so ignoring return values from this is usually
 * OK...
 *
 * @param conn the IRC protocol connection
 * @param line the irc_line protocol line to send
 */
err_t irc_conn_send (struct irc_conn *conn, const struct irc_line *line);

/**
 * @defgroup irc_conn_COMMAND Simple request functions
 *
 * These functions all simply build the corresponding irc_line struct, and then pass it on to irc_conn_send().
 *
 * @{
 */

/**
 * Send a NICK message. Usually either called indirectly via irc_conn_register, or used to change to a new nickname.
 *
 * If succesfull, this will result in a NICK message for our current nickname; irc_conn will handle this to update 
 * irc_conn::nickname.
 *
 * @param conn the IRC protocol connection
 * @param nickname the new nickname to use
 *
 * Possible errors (from RFC2812):
 *  ERR_NONICKNAMEGIVEN
 *  ERR_NICKNAMEINUSE
 *  ERR_UNAVAILRESOURCE
 *  ERR_ERRONEUSNICKNAME
 *  ERR_NICKCOLLISION
 *  ERR_RESTRICTED
 */
err_t irc_conn_NICK (struct irc_conn *conn, const char *nickname);

/**
 * Send a USER message. Usually called indirectly via irc_conn_register.
 *
 * @param conn the IRC protocol connection
 * @param username the username to register with, may be replaced with username from ident reply
 * @param realname the full-name to register with
 *
 * Possible errors (from RFC2812):
 *  ERR_NEEDMOREPARAMS
 *  ERR_ALREADYREGISTRED
 */
err_t irc_conn_USER (struct irc_conn *conn, const char *username, const char *realname);

/**
 * Send a PONG message to the given target.
 *
 * Note that you do not need to handle the normal PING/PONG cycle, irc_conn does this for you.
 *
 * @param conn the IRC protocol connection
 * @param target the PING source, aka. the target to send the PONG reply to
 *
 * Possible errors (from RFC2812):
 *  ERR_NOORIGIN
 *  ERR_NOSUCHSERVER
 */
err_t irc_conn_PONG (struct irc_conn *conn, const char *target);

/**
 * Send a simple JOIN message for the given channel. Note that this does not implement all possible arguments.
 *
 * If succesfull, this will result in a JOIN message for us on the given channel, plus a series of
 * RPL_NAMREPLY/RPL_ENDOFNAMES/RPL_TOPIC/etc messages.
 *
 * @param conn the IRC protocol connection
 * @param channel the full channel name to join
 *
 * Possible errors (from RFC2812):
 *  ERR_NEEDMOREPARAMS              ERR_BANNEDFROMCHAN
 *  ERR_INVITEONLYCHAN              ERR_BADCHANNELKEY
 *  ERR_CHANNELISFULL               ERR_BADCHANMASK
 *  ERR_NOSUCHCHANNEL               ERR_TOOMANYCHANNELS
 *  ERR_TOOMANYTARGETS              ERR_UNAVAILRESOURCE
 *
 */
err_t irc_conn_JOIN (struct irc_conn *conn, const char *channel);

/**
 * Send a PRIVMSG message to some target, usually a channel or another user (nickname).
 *
 * Note that the protocol limits messages to 512 bytes in length (total, including other protocol stuff), and messages
 * can't contain newlines or NULs - this will return an ERR_IRC_LINE_* in both cases.
 *
 * If succesfull, this won't result in any reply. If the target is a nickname, the server supports AWAY, and the target
 * has marked themselves as away, this may result in an RPL_AWAY reply.
 *
 * @param conn the IRC protocol connection
 * @param target the message target, usually either a channel name or a nickname
 * @param message the message to send
 *
 * Possible errors (from RFC2812):
 *  ERR_NORECIPIENT                 ERR_NOTEXTTOSEND
 *  ERR_CANNOTSENDTOCHAN            ERR_NOTOPLEVEL
 *  ERR_WILDTOPLEVEL                ERR_TOOMANYTARGETS
 *  ERR_NOSUCHNICK
 *
 */
err_t irc_conn_PRIVMSG (struct irc_conn *conn, const char *target, const char *message);

/**
 * Send a NOTICE messaeg to some target, usually a channel or another user (nickname).
 *
 * The same limitations on message length and data apply as for irc_conn_PRIVMSG.
 *
 * The only difference between PRIVMSG and NOTICE is that NOTICE messages should never generate an automated reply,
 * hence, they are used for e.g. CTCP replies, and ideally, for IRC bot output.
 *
 * @param conn the IRC protocol connection
 * @param target the message target, usually either a channel name or a nickname
 * @param message the message to send
 *
 * Possible errors (from RFC2812):
 *  None. Servers do not ever reply to NOTICE messages.
 */
err_t irc_conn_NOTICE (struct irc_conn *conn, const char *target, const char *message);

/**
 * Send a QUIT message to the server. The server will reply with an ERROR message and close the connection.
 *
 * This updates our state as disconnecting, and once EOF is recieved, the irc_conn_callbacks::on_quit callback is
 * called.
 *
 * @param conn the IRC protocol connection
 * @param message the quit message, which may be displayed to other clients
 */
err_t irc_conn_QUIT (struct irc_conn *conn, const char *message);

// @}

#endif /* IRC_CONN_H */