kike: implement the ping-pong and QUIT
This commit is contained in:
parent
9720e30c8b
commit
2fe3c7ed45
@ -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)
|
||||
|
145
src/kike.c
145
src/kike.c
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user