kike: implement the ping-pong and QUIT
This commit is contained in:
		@@ -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);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user