#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.
*/
struct irc_conn;
#include "error.h"
#include "transport.h"
#include "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, struct error_info *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 */