From: Richard Worth <richard@theworths.org>
Date: Sat, 10 Dec 2005 13:43:07 +0000 (+0000)
Subject: 2005-12-10  Richard D. Worth  <richard@theworths.org>
X-Git-Url: https://git.cworth.org/git?a=commitdiff_plain;h=df6ad6c8658a9f3a537a070a89a067c5ae598fcc;p=ttt

2005-12-10  Richard D. Worth  <richard@theworths.org>

        * TODO: check off INVITE, RETRACT, DECLINE, Game Invitation
        notices, Connection setup errors, NO_INVITE, User information
        errors.

        * src/ttt-client.c: (_ttt_client_execute_helo),
        (_ttt_client_execute_statistics), (_ttt_client_execute_message),
        (_ttt_client_execute_help), (_ttt_client_execute_version),
        (_ttt_client_execute_quit), (_ttt_client_execute_invite),
        (_ttt_client_execute_accept), (_ttt_client_execute_retract),
        (_ttt_client_execute_decline), (_read_request),
        (_execute_request),
        (_handle_requests_thread): Whitespace changes. Use exported get
        methods (get_username, get_num_wins). Use strcasecmp in place of
        toupper. Finish RETRACT, DECLINE.

        * src/ttt.h:
        * src/ttt-server.h:
        * src/ttt-server.c: (ttt_server_init),
        (ttt_server_register_client), (ttt_server_unregister_client),
        (ttt_server_verify_username),
        (ttt_server_get_client_from_username), (ttt_server_add_invite),
        (ttt_server_remove_invite): Add invite struct. Make username case
        insensitive (missed in register_client). Implement add_invite,
        remove_invite. Moved statistics to ttt-client.c
---

diff --git a/ChangeLog b/ChangeLog
index dd3bc91..edb3e3b 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,30 @@
+2005-12-10  Richard D. Worth  <richard@theworths.org>
+
+	* TODO: check off INVITE, RETRACT, DECLINE, Game Invitation
+	notices, Connection setup errors, NO_INVITE, User information
+	errors.
+
+	* src/ttt-client.c: (_ttt_client_execute_helo),
+	(_ttt_client_execute_statistics), (_ttt_client_execute_message),
+	(_ttt_client_execute_help), (_ttt_client_execute_version),
+	(_ttt_client_execute_quit), (_ttt_client_execute_invite),
+	(_ttt_client_execute_accept), (_ttt_client_execute_retract),
+	(_ttt_client_execute_decline), (_read_request),
+	(_execute_request),
+	(_handle_requests_thread): Whitespace changes. Use exported get
+	methods (get_username, get_num_wins). Use strcasecmp in place of
+	toupper. Finish RETRACT, DECLINE.
+
+	* src/ttt.h:
+	* src/ttt-server.h:
+	* src/ttt-server.c: (ttt_server_init),
+	(ttt_server_register_client), (ttt_server_unregister_client),
+	(ttt_server_verify_username),
+	(ttt_server_get_client_from_username), (ttt_server_add_invite),
+	(ttt_server_remove_invite): Add invite struct. Make username case
+	insensitive (missed in register_client). Implement add_invite,
+	remove_invite. Moved statistics to ttt-client.c
+
 2005-12-09  Carl Worth  <cworth@cworth.org>
 
 	* src/ttt-server.c (ttt_server_unregister_client): Move client has
diff --git a/TODO b/TODO
index b6257c2..f54ef62 100644
--- a/TODO
+++ b/TODO
@@ -17,10 +17,10 @@ S C
 ✓   1.2.5. QUIT
 ✓   1.2.6. VERSION
     1.3. Game management commands
-    1.3.1. INVITE
+✓   1.3.1. INVITE
     1.3.2. ACCEPT
-    1.3.3. RETRACT
-    1.3.4. DECLINE
+✓   1.3.3. RETRACT
+✓   1.3.4. DECLINE
     1.4. In-game commands
     1.4.1. SHOW
     1.4.2. PART
@@ -29,11 +29,11 @@ S C
     2.1. Global notices
 ✓   2.1.1. NOTICE USER <username>
 ✓   2.1.2. NOTICE QUIT <username>
-    2.1.3. Game Invitation
+✓   2.1.3. Game Invitation
 ✓   2.1.3.1. NOTICE INVITE <username1> <username2>
 ✓   2.1.3.2. NOTICE ACCEPT <username2> <username1>
-    2.1.3.3. NOTICE RETRACT <username1> <username2>
-    2.1.3.4. NOTICE DECLINE <username2> <username1>
+✓   2.1.3.3. NOTICE RETRACT <username1> <username2>
+✓   2.1.3.4. NOTICE DECLINE <username2> <username1>
     2.1.4. NOTICE NEWGAME <game> <username> <username>
     2.1.5. NOTICE DISPOSE <game>
 ✓   2.1.6. NOTICE MESSAGE <username> <text>
@@ -43,7 +43,7 @@ S C
     2.2.2. Move notices
     2.2.2.1. NOTICE MOVE <game> <username> <number>
     3. Errors
-    3.1. Connection setup errors
+✓   3.1. Connection setup errors
 ✓   3.1.1. ERROR NO_NAME_SET
 ✓   3.1.2. ERROR INVALID_NAME
     3.2. Command format errors
@@ -53,9 +53,9 @@ S C
     3.2.4. ERROR NOT_GRID
     3.3. Global command errors.
     3.4. Game management errors.
-    3.4.1. ERROR NO_INVITE
+✓   3.4.1. ERROR NO_INVITE
     3.4.2. ERROR NO_GAME
-    3.5. User information errors
+✓   3.5. User information errors
 ✓   3.5.1. ERROR NO_USER
     3.6. In-game errors
     3.6.1. Global game errors
diff --git a/src/ttt-client.c b/src/ttt-client.c
index e68f70c..c198d3a 100644
--- a/src/ttt-client.c
+++ b/src/ttt-client.c
@@ -127,10 +127,10 @@ ttt_command_description_t command_descriptions[] = {
      "ACCEPT <username>       ", "Accept a game invitation."},
 
     {"RETRACT", 1, 1, _ttt_client_execute_retract,
-     "RETRACT <username>       ", "Retract a game invitation."},
+     "RETRACT <username>      ", "Retract a game invitation."},
 
     {"DECLINE", 1, 1, _ttt_client_execute_decline,
-     "DECLINE <username>       ", "Decline a game invitation."},
+     "DECLINE <username>      ", "Decline a game invitation."},
 
     {"MESSAGE", 1, 1, _ttt_client_execute_message,
      "MESSAGE <message>       ", "Send a message to everyone."},
@@ -155,30 +155,34 @@ _ttt_client_execute_helo (ttt_client_t *client,
 			  char         **args,
 			  int          num_args)
 {
+    char        *response;
+    char        *notice;
     ttt_error_t error;
-    char *response;
-    char *notice;
 
     assert (num_args == 1);
 
-    ttt_client_set_username (client, args[0]);
+    if (!(client->registered))
+    {
+	ttt_client_set_username (client, args[0]);
 
-    error = ttt_server_register_client (client->server, client);
-    if (error)
-	return error;
-    client->registered = TRUE;
+	error = ttt_server_register_client (client->server, client);
+	if (error)
+	    return error;
+	client->registered = TRUE;
+
+	xasprintf (&notice, "NOTICE USER %s\r\n",
+		   ttt_client_get_username (client));
+	ttt_server_broadcast (client->server, notice);
+
+	free (notice);
+    }
 
     xasprintf (&response, "HELO %s %s %s\r\n",
-	       client->username,
+	       ttt_client_get_username (client),
 	       ttt_server_get_host (client->server),
 	       ttt_server_get_port (client->server));
     ttt_client_send (client, response);
 
-    xasprintf (&notice, "NOTICE USER %s\r\n",
-	       client->username);
-    ttt_server_broadcast (client->server, notice);
-
-    free (notice);
     free (response);
 
     return TTT_ERROR_NONE;
@@ -209,21 +213,30 @@ _ttt_client_execute_statistics (ttt_client_t *client,
 				char         **args,
 				int          num_args)
 {
-    char *response;
-    char *username;
-    ttt_error_t error;
+    const char   *argusername;
+    ttt_client_t *argclient;
+    char         *response;
+    ttt_error_t  error;
 
     assert (num_args == 1);
 
-    username = args[0];
+    argusername = args[0];
 
     if (!client->registered)
 	return TTT_ERROR_NO_NAME_SET;
 
-    error = ttt_server_statistics (client->server, username, &response);
+    error = ttt_server_get_client_from_username (client->server,
+						 argusername,
+						 &argclient);
     if (error)
 	return error;
 
+    xasprintf (&response, "STATISTICS %s \"\r\n"
+	       "TICTACTOE WINS %d\r\n"
+	       "\"\r\n",
+	       ttt_client_get_username(argclient),
+	       ttt_client_get_num_wins(argclient));
+
     ttt_client_send (client, response);
 
     free (response);
@@ -236,11 +249,14 @@ _ttt_client_execute_message (ttt_client_t  *client,
 			     char          **args,
 			     int           num_args)
 {
-    char *response;
-    char *notice;
+    const char *argmessage;
+    char       *response;
+    char       *notice;
 
     assert (num_args == 1);
 
+    argmessage = args[0];
+
     if (!client->registered)
 	return TTT_ERROR_NO_NAME_SET;
 
@@ -248,8 +264,8 @@ _ttt_client_execute_message (ttt_client_t  *client,
     ttt_client_send (client, response);
 
     xasprintf (&notice, "NOTICE MESSAGE %s \"%s\"\r\n",
-	      client->username,
-	      args[0]);
+	       ttt_client_get_username(client),
+	       argmessage);
     ttt_server_broadcast (client->server, notice);
 
     free (notice);
@@ -263,11 +279,11 @@ _ttt_client_execute_help (ttt_client_t  *client,
 			  char          **args,
 			  int           num_args)
 {
-    char *response;
-    char *command;
+    char                      *response;
+    char                      *argcommand;
     ttt_command_description_t *desc;
+    ttt_bool_t                is_command = FALSE;
     int i;
-    ttt_bool_t is_command = FALSE;
     
     if (num_args == 0) {
 	xasprintf (&response, "HELP \"\r\n"
@@ -281,12 +297,10 @@ _ttt_client_execute_help (ttt_client_t  *client,
 	}
 	xasprintf (&response, "%s\"\r\n", response);
     } else {
-	command = args[0];
-	for (i = 0; i < strlen (command); i++)
-	    command[i] = toupper (command[i]);
+	argcommand = args[0];
 	for (i = 0; i < ARRAY_SIZE(command_descriptions); i++) {
 	    desc = &command_descriptions[i];
-	    if (strcmp (desc->command, command) == 0) {
+	    if (strcasecmp (desc->command, argcommand) == 0) {
 		is_command = TRUE;
 		xasprintf (&response, "HELP %s \"\r\n"
 			   "%s\r\n"
@@ -317,20 +331,20 @@ _ttt_client_execute_version (ttt_client_t  *client,
 			     int           num_args)
 {
     char *response;
-    char *clientversion;
-    int version;
+    char *argversion;
+    int  version;
     int i;
 
     assert (num_args == 1);
 
-    clientversion = args[0];
+    argversion = args[0];
 
     /* Verify that provided version arg is a positive integer */
-    for (i = 0; i < strlen(clientversion); i++)
-	if (!isdigit (clientversion[i]))
+    for (i = 0; i < strlen(argversion); i++)
+	if (!isdigit (argversion[i]))
 	    return TTT_ERROR_SYNTAX;
 
-    version = atoi (clientversion);
+    version = atoi (argversion);
 
     if (version < 1)
 	return TTT_ERROR_SYNTAX;
@@ -338,7 +352,8 @@ _ttt_client_execute_version (ttt_client_t  *client,
     if (version > TTT_SERVER_PROTOCOL_VERSION)
 	version = TTT_SERVER_PROTOCOL_VERSION;
 
-    xasprintf (&response, "VERSION %d\r\n", version);
+    xasprintf (&response, "VERSION %d\r\n",
+	       version);
     ttt_client_send (client, response);
 
     free (response);
@@ -358,7 +373,7 @@ _ttt_client_execute_quit (ttt_client_t *client,
 	return TTT_ERROR_QUIT_REQUESTED;
     
     xasprintf (&notice, "NOTICE QUIT %s\r\n",
-	       client->username);
+	       ttt_client_get_username(client));
     ttt_server_broadcast (client->server, notice);
     
     free (notice);
@@ -371,32 +386,37 @@ _ttt_client_execute_invite (ttt_client_t *client,
 			    char         **args,
 			    int          num_args)
 {
-    const char *username;
-    char *response;
-    char *notice;
-    ttt_error_t error;
+    const char   *invitee_username;
+    ttt_client_t *invitee;
+    char         *response;
+    char         *notice;
+    ttt_error_t  error;
 
     assert (num_args == 1);
 
-    username = args[0];
+    invitee_username = args[0];
 
     if (!client->registered)
         return TTT_ERROR_NO_NAME_SET;
 
-    error = ttt_server_verify_username (client->server,	username);
+    error = ttt_server_get_client_from_username (client->server,
+						 invitee_username,
+						 &invitee);
     if (error)
 	return error;
 
+    ttt_server_add_invite (client->server,
+			   client,
+			   invitee);
+
     xasprintf (&response, "INVITE\r\n");
     ttt_client_send (client, response);
 
     xasprintf (&notice, "NOTICE INVITE %s %s\r\n",
-	       client->username,
-	       username);
+	       ttt_client_get_username(client),
+	       ttt_client_get_username(invitee));
     ttt_server_broadcast (client->server, notice);
 
-    /* XXX: Store invitation in state */
-
     free (notice);
     free (response);
 
@@ -408,30 +428,37 @@ _ttt_client_execute_accept (ttt_client_t *client,
 			    char         **args,
 			    int          num_args)
 {
-    const char *username;
-    char *response;
-    char *notice;
-    ttt_error_t error;
+    const char   *actor_username;
+    ttt_client_t *actor;
+    char         *response;
+    char         *notice;
+    ttt_error_t  error;
 
     assert (num_args == 1);
 
-    username = args[0];
+    actor_username = args[0];
 
     if (!client->registered)
         return TTT_ERROR_NO_NAME_SET;
 
-    error = ttt_server_verify_username (client->server,	username);
+    error = ttt_server_get_client_from_username (client->server,
+						 actor_username,
+						 &actor);
     if (error)
 	return error;
 
-    /* XXX: Verify invitation, else return ERROR NO_INVITE */
+    error = ttt_server_remove_invite (client->server,
+				      actor,
+				      client);
+    if (error)
+	return error;
 
     xasprintf (&response, "ACCEPT\r\n");
     ttt_client_send (client, response);
 
     xasprintf (&notice, "NOTICE ACCEPT %s %s\r\n",
-	       client->username,
-	       username);
+	       ttt_client_get_username(client),
+	       ttt_client_get_username(actor));
     ttt_server_broadcast (client->server, notice);
 
     /* XXX: Start a new game */
@@ -447,34 +474,39 @@ _ttt_client_execute_retract (ttt_client_t *client,
 			     char         **args,
 			     int          num_args)
 {
-    const char *username;
-    char *response;
-    char *notice;
-    ttt_error_t error;
+    const char   *invitee_username;
+    ttt_client_t *invitee;
+    char         *response;
+    char         *notice;
+    ttt_error_t  error;
 
     assert (num_args == 1);
 
-    username = args[0];
+    invitee_username = args[0];
 
     if (!client->registered)
         return TTT_ERROR_NO_NAME_SET;
 
-    error = ttt_server_verify_username (client->server,	username);
+    error = ttt_server_get_client_from_username (client->server,
+						 invitee_username,
+						 &invitee);
     if (error)
 	return error;
 
-    /* XXX: Verify invitation, else return ERROR NO_INVITE */
-
+    error = ttt_server_remove_invite (client->server,
+				      client,
+				      invitee);
+    if (error)
+	return error;
+    
     xasprintf (&response, "RETRACT\r\n");
     ttt_client_send (client, response);
 
     xasprintf (&notice, "NOTICE RETRACT %s %s\r\n",
-	       client->username,
-	       username);
+	       ttt_client_get_username(client),
+	       ttt_client_get_username(invitee));
     ttt_server_broadcast (client->server, notice);
 
-    /* XXX: Remove invitiation from state */
-
     free (notice);
     free (response);
 
@@ -486,34 +518,39 @@ _ttt_client_execute_decline (ttt_client_t *client,
 			     char         **args,
 			     int          num_args)
 {
-    const char *username;
-    char *response;
-    char *notice;
-    ttt_error_t error;
+    const char   *actor_username;
+    ttt_client_t *actor;
+    char         *response;
+    char         *notice;
+    ttt_error_t  error;
 
     assert (num_args == 1);
 
-    username = args[0];
+    actor_username = args[0];
 
     if (!client->registered)
         return TTT_ERROR_NO_NAME_SET;
 
-    error = ttt_server_verify_username (client->server,	username);
+    error = ttt_server_get_client_from_username (client->server,
+						 actor_username,
+						 &actor);
     if (error)
 	return error;
 
-    /* XXX: Verify invitation, else return ERROR NO_INVITE */
+    error = ttt_server_remove_invite (client->server,
+				      actor,
+				      client);
+    if (error)
+	return error;
 
     xasprintf (&response, "DECLINE\r\n");
     ttt_client_send (client, response);
 
     xasprintf (&notice, "NOTICE DECLINE %s %s\r\n",
-	       client->username,
-	       username);
+	       ttt_client_get_username(client),
+	       ttt_client_get_username(actor));
     ttt_server_broadcast (client->server, notice);
 
-    /* XXX: Remove invitation from state */
-
     free (notice);
     free (response);
 
@@ -598,7 +635,7 @@ _free_request (ttt_client_t *client)
 static ttt_status_t
 _read_request (ttt_client_t *client)
 {
-    ttt_token_t token;
+    ttt_token_t      token;
     ttt_token_type_t token_type;
 
     _free_request (client);
@@ -627,11 +664,10 @@ _read_request (ttt_client_t *client)
 static ttt_error_t
 _execute_request (ttt_client_t *client)
 {
-    int i;
-
-    char *command = client->request_strings[0];
-    int num_args = client->num_request_strings-1;
+    char                      *command = client->request_strings[0];
+    int                       num_args = client->num_request_strings - 1;
     ttt_command_description_t *desc;
+    int i;
 
     for (i = 0; i < strlen (command); i++)
 	command[i] = toupper (command[i]);
@@ -654,11 +690,10 @@ static void *
 _handle_requests_thread (void *closure)
 {
     ttt_status_t status;
-    ttt_error_t error;
     ttt_client_t *client = closure;
+    ttt_error_t  error;
 
     while (1) {
-
 	status = _read_request (client);
 	if (status == TTT_STATUS_EOF)
 	    break;
diff --git a/src/ttt-server.c b/src/ttt-server.c
index cb28741..4b29cf2 100644
--- a/src/ttt-server.c
+++ b/src/ttt-server.c
@@ -26,15 +26,24 @@
 #include "ttt-error.h"
 #include "ttt-socket.h"
 
+struct _ttt_invite {
+    ttt_client_t    *actor;
+    ttt_client_t    *invitee;
+};
+
 struct _ttt_server {
     pthread_mutex_t mutex;
 
-    const char *host;
-    const char *port;
+    const char      *host;
+    const char      *port;
 
-    ttt_client_t **clients;
-    int clients_size;
-    int num_clients;
+    ttt_client_t    **clients;
+    int             clients_size;
+    int             num_clients;
+
+    ttt_invite_t    **invites;
+    int             invites_size;
+    int             num_invites;
 };
 
 static void
@@ -48,6 +57,10 @@ ttt_server_init (ttt_server_t *server, const char *host, const char *port)
     server->clients = NULL;
     server->clients_size = 0;
     server->num_clients = 0;
+
+    server->invites = NULL;
+    server->invites_size = 0;
+    server->num_invites = 0;
 }
 
 /* Exported: See ttt-server.h for documentation. */
@@ -68,7 +81,7 @@ ttt_server_register_client (ttt_server_t *server, ttt_client_t *client)
 	return TTT_ERROR_INVALID_NAME;
 
     for (i = 0; i < server->num_clients; i++) {
-	if (strcmp (ttt_client_get_username (server->clients[i]), username) == 0) {
+	if (strcasecmp (ttt_client_get_username (server->clients[i]), username) == 0) {
 	    error = TTT_ERROR_INVALID_NAME;
 	    goto CLEANUP_LOCK;
 	}
@@ -88,7 +101,7 @@ ttt_server_register_client (ttt_server_t *server, ttt_client_t *client)
 				    server->clients_size * sizeof (ttt_client_t *));
     }
 
-    server->clients [server->num_clients - 1] = client;
+    server->clients[server->num_clients - 1] = client;
 
  CLEANUP_LOCK:
     pthread_mutex_unlock (&server->mutex);
@@ -100,10 +113,39 @@ ttt_server_register_client (ttt_server_t *server, ttt_client_t *client)
 void
 ttt_server_unregister_client (ttt_server_t *server, ttt_client_t *client)
 {
+    ttt_invite_t *invite;
+    char         *notice;
+    ttt_bool_t   send_notice = FALSE;
     int i;
 
     pthread_mutex_lock (&server->mutex);
 
+    /* Auto-retract and decline pending notices */
+    /* Notices are sent after mutex unlock */
+    for (i = 0; i < server->num_invites; i++)
+    {
+	invite = server->invites[i];
+	if ((invite->actor == client) || (invite->invitee == client))
+	{
+	    send_notice = TRUE;
+	    if (invite->actor == client)
+		xasprintf (&notice, "%s" "NOTICE RETRACT %s %s\r\n",
+			   notice,
+			   ttt_client_get_username(invite->actor),
+			   ttt_client_get_username(invite->invitee));
+	    else
+		xasprintf (&notice, "%s" "NOTICE DECLINE %s %s\r\n",
+			   notice,
+			   ttt_client_get_username(invite->invitee),
+			   ttt_client_get_username(invite->actor));
+
+	    memmove (&server->invites[i], &server->invites[i+1],
+		     (server->num_invites - i - 1) * sizeof (ttt_invite_t *));
+	    server->num_invites--;
+	    i--;
+	}
+    }
+
     for (i = 0; i < server->num_clients; i++)
 	if (server->clients[i] == client)
 	    break;
@@ -118,6 +160,12 @@ ttt_server_unregister_client (ttt_server_t *server, ttt_client_t *client)
     server->num_clients--;
 
     pthread_mutex_unlock (&server->mutex);
+
+    if (send_notice)
+    {
+	ttt_server_broadcast(server, notice);
+	free (notice);
+    }
 }
 
 /* Exported: See ttt-server.h for documentation. */
@@ -159,24 +207,34 @@ ttt_server_who (ttt_server_t *server)
 
 /* Exported: See ttt-server.h for documentation. */
 ttt_error_t
-ttt_server_statistics (ttt_server_t *server, const char *username, char **response)
+ttt_server_verify_username (ttt_server_t *server,
+			    const char   *username)
+{
+    ttt_client_t *client;
+
+    return ttt_server_get_client_from_username (server,
+						username,
+						&client);
+}
+
+/* Exported: See ttt-server.h for documentation. */
+ttt_error_t
+ttt_server_get_client_from_username (ttt_server_t *server,
+				     const char   *username,
+				     ttt_client_t **client)
 {
     ttt_bool_t usernamefound = FALSE;
     const char *client_username;
-    int client_num_wins;
     int i;
 
     pthread_mutex_lock (&server->mutex);
 
     for (i = 0; i < server->num_clients; i++) {
 	client_username = ttt_client_get_username (server->clients[i]);
-	if (strcasecmp (username, client_username) == 0) {
+	if (strcasecmp (username, client_username) == 0)
+	{
 	    usernamefound = TRUE;
-	    client_num_wins = ttt_client_get_num_wins (server->clients[i]);
-	    xasprintf (response, "STATISTICS %s \"\r\n"
-		       "TICTACTOE WINS %d\r\n\"\r\n",
-		       client_username,
-		       client_num_wins);
+	    *client = server->clients[i];
 	}
     }
 
@@ -190,28 +248,77 @@ ttt_server_statistics (ttt_server_t *server, const char *username, char **respon
 
 /* Exported: See ttt-server.h for documentation. */
 ttt_error_t
-ttt_server_verify_username (ttt_server_t *server, const char *username)
+ttt_server_add_invite (ttt_server_t *server,
+		       ttt_client_t *actor,
+		       ttt_client_t *invitee)
 {
-    ttt_bool_t usernamefound = FALSE;
-    const char *client_username;
-    int i;
+    ttt_invite_t *invite;
 
     pthread_mutex_lock (&server->mutex);
 
-    for (i = 0; i < server->num_clients; i++) {
-	client_username = ttt_client_get_username (server->clients[i]);
-	if (strcasecmp (username, client_username) == 0)
-	    usernamefound = TRUE;
+    invite = xmalloc (sizeof (ttt_invite_t));
+
+    invite->actor = actor;
+    invite->invitee = invitee;
+
+    server->num_invites++;
+
+    if (server->num_invites > server->invites_size) {
+	if (server->invites_size == 0)
+	    server->invites_size = 1;
+	else
+	    server->invites_size *= 2;
+
+	server->invites = xrealloc (server->invites,
+				    server->invites_size * sizeof (ttt_invite_t *));
     }
 
-    pthread_mutex_unlock (&server->mutex);
+    server->invites[server->num_invites - 1] = invite;
 
-    if (!usernamefound)
-	return TTT_ERROR_NO_USER;
+    pthread_mutex_unlock (&server->mutex);
 
     return TTT_ERROR_NONE;
 }
 
+/* Exported: See ttt-server.h for documentation. */
+ttt_error_t
+ttt_server_remove_invite (ttt_server_t *server,
+			  ttt_client_t *actor,
+			  ttt_client_t *invitee)
+{
+    ttt_invite_t *invite;
+    ttt_error_t  error;
+    int i;
+
+    pthread_mutex_lock (&server->mutex);
+
+    error = TTT_ERROR_NO_INVITE;
+    for (i = 0; i < server->num_invites; i++)
+    {
+	invite = server->invites[i];
+	if ((invite->actor == actor) && (invite->invitee == invitee))
+	{
+	    error = TTT_ERROR_NONE;
+	    break;
+	}
+    }
+
+    if (error)
+	goto CLEANUP_LOCK;
+
+    assert (i < server->num_invites);
+
+    memmove (&server->invites[i], &server->invites[i+1],
+	     (server->num_invites - i - 1) * sizeof (ttt_invite_t *));
+
+    server->num_invites--;
+
+ CLEANUP_LOCK:
+    pthread_mutex_unlock (&server->mutex);
+
+    return error;
+}
+
 /* Exported: See ttt-server.h for documentation. */
 const char*
 ttt_server_get_host (ttt_server_t *server)
@@ -237,7 +344,7 @@ static const char *WELCOME_MESSAGE =
 "	telnet %s %s\r\n"
 "\r\n"
 "The TTTP (tic-tac-toe protocol) has been partially implemented.\r\n"
-"The following commands should work: HELO, HELP, MESSAGE, VERSION, QUIT, WHO.\r\n"
+"The following commands should work: HELO, HELP, INVITE, ACCEPT, RETRACT, DECLINE, MESSAGE, STATISTICS, QUIT, VERSION, WHO.\r\n"
 "\r\n";
 
 static void
diff --git a/src/ttt-server.h b/src/ttt-server.h
index 8d0f9c4..80f22bf 100644
--- a/src/ttt-server.h
+++ b/src/ttt-server.h
@@ -73,10 +73,38 @@ ttt_server_broadcast (ttt_server_t *server, const char *message);
 const char*
 ttt_server_who (ttt_server_t *server);
 
-/* Generates the statistics for the user. If the function does not
- * return an error, the response will be allocated in this function
- * and will need to be free'd by the caller.
+/* Checks to see whether a username exists.
  *
+ * Returns: ttt_server_get_client_from_username(server, username)
+ *
+ * Locking: See ttt_server_get_client_from_username
+ *
+ * Errors: See ttt_server_get_client_from_username
+ */
+ttt_error_t
+ttt_server_verify_username (ttt_server_t *server,
+			    const char *username);
+
+/* Points *client to the client with the supplied username, else
+ * leaves *client unhanged.
+ * 
+ * Returns: TTT_ERROR_NONE, else TTT_ERROR_NO_USER if username not
+ * found. 
+ *
+ * Locking: The server mutex will be acquired and held throughout the
+ * execution of this function. Each client mutex may also be acquired
+ * and held by functions called during the execution of this function.
+ *
+ * Errors: If an error such as an IO error occurs, this function will
+ * not return.
+ */
+ttt_error_t
+ttt_server_get_client_from_username (ttt_server_t *server,
+				     const char   *username,
+				     ttt_client_t **client);
+
+/* Adds an invitation
+ * 
  * Locking: The server mutex will be acquired and held throughout the
  * execution of this function. Each client mutex may also be acquired
  * and held by functions called during the execution of this function.
@@ -85,13 +113,15 @@ ttt_server_who (ttt_server_t *server);
  * not return.
  */
 ttt_error_t
-ttt_server_statistics (ttt_server_t *server,
-		       const char *username,
-		       char **response);
+ttt_server_add_invite (ttt_server_t *server,
+		       ttt_client_t *actor,
+		       ttt_client_t *invitee);
 
-/* Checks to see if the username is registered. If the username exists
- * will return TTT_ERROR_NONE, else TTT_ERROR_NO_USER.
+/* Removes an invitation
  *
+ * Returns: TTT_ERROR_NONE, else TTT_ERROR_NO_INVITE if no invite
+ * found.
+ * 
  * Locking: The server mutex will be acquired and held throughout the
  * execution of this function. Each client mutex may also be acquired
  * and held by functions called during the execution of this function.
@@ -100,8 +130,9 @@ ttt_server_statistics (ttt_server_t *server,
  * not return.
  */
 ttt_error_t
-ttt_server_verify_username (ttt_server_t *server,
-			    const char *username);
+ttt_server_remove_invite (ttt_server_t *server,
+			  ttt_client_t *actor,
+			  ttt_client_t *invitee);
 
 /* Gets the server hostname.
  *
diff --git a/src/ttt.h b/src/ttt.h
index 5c14169..5b4a807 100644
--- a/src/ttt.h
+++ b/src/ttt.h
@@ -73,6 +73,7 @@ typedef enum {
 } ttt_status_t;
 
 typedef struct _ttt_server ttt_server_t;
+typedef struct _ttt_invite ttt_invite_t;
 typedef struct _ttt_client ttt_client_t;
 
 #include "x.h"