kike: implement the ping-pong and QUIT

This commit is contained in:
Přemysl Eric Janouch 2014-08-02 00:09:23 +02:00
parent 9720e30c8b
commit 2fe3c7ed45
2 changed files with 144 additions and 10 deletions

View File

@ -961,6 +961,15 @@ poller_timers_find (struct poller_timers *self,
return -1;
}
static ssize_t
poller_timers_find_by_data (struct poller_timers *self, void *data)
{
for (size_t i = 0; i < self->len; i++)
if (self->info[i].user_data == data)
return i;
return -1;
}
static void
poller_timers_add (struct poller_timers *self,
poller_timer_func dispatcher, void *data, int timeout_ms)

View File

@ -225,6 +225,7 @@ struct client
bool initialized; ///< Has any data been received yet?
bool registered; ///< The user has registered
bool closing_link; ///< Closing link
bool ssl_rx_want_tx; ///< SSL_read() wants to write
bool ssl_tx_want_rx; ///< SSL_write() wants to read
@ -439,19 +440,38 @@ server_context_free (struct server_context *self)
// --- Main program ------------------------------------------------------------
static void client_cancel_timers (struct client *);
static void client_set_kill_timer (struct client *);
static void
client_unregister (struct client *c, const char *reason)
{
if (!c->registered)
return;
// TODO: multicast a `:prefix QUIT :reason' to all people present on all
// channels we were on.
// TODO: remove ourselves from the channels, ...
str_map_set (&c->ctx->users, c->nickname, NULL);
free (c->nickname);
c->nickname = NULL;
c->registered = false;
}
static void
client_kill (struct client *c, const char *reason)
{
// TODO: multicast a QUIT message with `reason' || "Client exited"
(void) reason;
// TODO: do further cleanup if the client has successfully registered etc.
client_unregister (c, reason ? reason : "Client exited");
struct server_context *ctx = c->ctx;
ssize_t i = poller_find_by_fd (&ctx->poller, c->socket_fd);
if (i != -1)
poller_remove_at_index (&ctx->poller, i);
client_cancel_timers (c);
if (c->ssl)
(void) SSL_shutdown (c->ssl);
xclose (c->socket_fd);
c->socket_fd = -1;
client_free (c);
@ -497,6 +517,83 @@ irc_get_text (struct server_context *ctx, int id, const char *def)
return catgets (ctx->catalog, 1, id, def);
}
static void
irc_close_link (struct client *c, const char *reason)
{
if (!soft_assert (!c->closing_link))
return;
// We push an `ERROR' message to the write buffer and let the poller send
// it, with some arbitrary timeout. The `closing_link' state makes sure
// that a/ we ignore any successive messages, and b/ that the connection
// is killed after the write buffer is transferred and emptied.
irc_send (c, "ERROR :Closing Link: %s[%s] (%s)", c->nickname,
c->hostname /* TODO host IP? */, reason);
c->closing_link = true;
client_unregister (c, reason);
client_set_kill_timer (c);
}
// --- Timers ------------------------------------------------------------------
static void
client_cancel_timers (struct client *c)
{
ssize_t i;
struct poller_timers *timers = &c->ctx->poller.timers;
while ((i = poller_timers_find_by_data (timers, c)) != -1)
poller_timers_remove_at_index (timers, i);
}
static void
client_set_timer (struct client *c, poller_timer_func fn, unsigned interval)
{
client_cancel_timers (c);
poller_timers_add (&c->ctx->poller.timers, fn, c, interval * 1000);
}
static void
on_irc_client_kill_timer (void *user_data)
{
struct client *c = user_data;
hard_assert (!c->initialized || c->closing_link);
client_kill (c, NULL);
}
static void
client_set_kill_timer (struct client *c)
{
client_set_timer (c, on_irc_client_kill_timer, c->ctx->ping_interval);
}
static void
on_irc_client_timeout_timer (void *user_data)
{
struct client *c = user_data;
struct str reason;
str_init (&reason);
str_append_printf (&reason, "Ping timeout: >%u seconds",
c->ctx->ping_interval);
irc_close_link (c, reason.str);
str_free (&reason);
}
static void
on_irc_client_ping_timer (void *user_data)
{
struct client *c = user_data;
hard_assert (!c->closing_link);
irc_send (c, "PING :%s", c->ctx->server_name);
client_set_timer (c, on_irc_client_timeout_timer, c->ctx->ping_interval);
}
static void
client_set_ping_timer (struct client *c)
{
client_set_timer (c, on_irc_client_ping_timer, c->ctx->ping_interval);
}
// --- IRC command handling ----------------------------------------------------
enum
@ -774,6 +871,28 @@ irc_handle_ping (const struct irc_message *msg, struct client *c)
c->ctx->server_name, msg->params.vector[0]);
}
static void
irc_handle_pong (const struct irc_message *msg, struct client *c)
{
// We are the only server, so we don't have to care too much
if (msg->params.len < 1)
irc_send_reply (c, IRC_ERR_NOORIGIN);
else
// Set a new timer to send another PING
client_set_ping_timer (c);
}
static void
irc_handle_quit (const struct irc_message *msg, struct client *c)
{
struct str reason;
str_init (&reason);
str_append_printf (&reason, "Quit: %s",
msg->params.len > 0 ? msg->params.vector[0] : c->nickname);
irc_close_link (c, reason.str);
str_free (&reason);
}
static void
irc_handle_time (const struct irc_message *msg, struct client *c)
{
@ -812,6 +931,7 @@ static void
irc_register_handlers (struct server_context *ctx)
{
// TODO: add an index for IRC_ERR_NOSUCHSERVER validation?
// TODO: more commands, see RFC 2812 :!
static const struct irc_command message_handlers[] =
{
{ "PASS", false, irc_handle_pass },
@ -821,6 +941,8 @@ irc_register_handlers (struct server_context *ctx)
{ "LUSERS", true, irc_handle_lusers },
{ "MOTD", true, irc_handle_motd },
{ "PING", true, irc_handle_ping },
{ "PONG", false, irc_handle_pong },
{ "QUIT", false, irc_handle_quit },
{ "TIME", true, irc_handle_time },
{ "VERSION", true, irc_handle_version },
};
@ -838,11 +960,10 @@ irc_process_message (const struct irc_message *msg,
{
(void) raw;
// XXX: we may want to discard everything following a QUIT etc.
// We can set a flag within the client object.
// TODO: see RFC 2812 :!
struct client *c = user_data;
if (c->closing_link)
return;
struct irc_command *cmd = str_map_find (&c->ctx->handlers, msg->command);
if (!cmd)
irc_send_reply (c, IRC_ERR_UNKNOWNCOMMAND, msg->command);
@ -1073,6 +1194,7 @@ on_irc_client_ready (const struct pollfd *pfd, void *user_data)
return;
}
c->initialized = true;
client_set_ping_timer (c);
}
int new_events = 0;
@ -1105,6 +1227,10 @@ on_irc_client_ready (const struct pollfd *pfd, void *user_data)
if (pfd->events != new_events)
poller_set (&c->ctx->poller, c->socket_fd, new_events,
(poller_dispatcher_func) on_irc_client_ready, c);
// The purpose of the `closing_link' state is to transfer the `ERROR'
if (c->closing_link && !c->write_buffer.len)
client_kill (c, NULL);
}
static void
@ -1151,11 +1277,10 @@ on_irc_client_available (const struct pollfd *pfd, void *user_data)
c->hostname = xstrdup (host);
LIST_PREPEND (ctx->clients, c);
// TODO: set a timeout on the socket, something like 3 minutes, then we
// should terminate the connection.
set_blocking (fd, false);
poller_set (&ctx->poller, fd, POLLIN,
(poller_dispatcher_func) on_irc_client_ready, c);
client_set_kill_timer (c);
}
}