1 /* ttt-client.c - client handling code for tic-tac-toe game server
3 * Copyright © 2005 Carl Worth
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2, or (at your option)
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software Foundation,
17 * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19 * Author: Carl Worth <cworth@cworth.org>
22 #include "ttt-client.h"
24 #define YY_DECL int yylex (yyscan_t yyscanner, ttt_token_t *token)
26 #include "ttt-error.h"
28 #include "ttt-server.h"
29 #include "ttt-socket.h"
30 #include "ttt-token.h"
33 pthread_mutex_t mutex;
40 char **request_strings;
41 int num_request_strings;
44 ttt_bool_t registered;
48 typedef ttt_error_t (*ttt_command_func_t) (ttt_client_t *client,
53 _ttt_client_execute_helo (ttt_client_t *client,
58 _ttt_client_execute_who (ttt_client_t *client,
63 _ttt_client_execute_statistics (ttt_client_t *client,
68 _ttt_client_execute_message (ttt_client_t *client,
73 _ttt_client_execute_help (ttt_client_t *client,
78 _ttt_client_execute_version (ttt_client_t *client,
83 _ttt_client_execute_quit (ttt_client_t *client,
88 _ttt_client_execute_invite (ttt_client_t *client,
93 _ttt_client_execute_accept (ttt_client_t *client,
98 _ttt_client_execute_retract (ttt_client_t *client,
103 _ttt_client_execute_decline (ttt_client_t *client,
107 typedef struct _ttt_command_description {
111 ttt_command_func_t execute;
113 const char *description;
114 } ttt_command_description_t;
116 ttt_command_description_t command_descriptions[] = {
117 {"HELO", 1, 1, _ttt_client_execute_helo,
118 "HELO <username> ", "Register."},
120 {"HELP", 0, 1, _ttt_client_execute_help,
121 "HELP <command> ", "Display help for a command."},
123 {"INVITE", 1, 1, _ttt_client_execute_invite,
124 "INVITE <username> ", "Invite a player to play a game."},
126 {"ACCEPT", 1, 1, _ttt_client_execute_accept,
127 "ACCEPT <username> ", "Accept a game invitation."},
129 {"RETRACT", 1, 1, _ttt_client_execute_retract,
130 "RETRACT <username> ", "Retract a game invitation."},
132 {"DECLINE", 1, 1, _ttt_client_execute_decline,
133 "DECLINE <username> ", "Decline a game invitation."},
135 {"MESSAGE", 1, 1, _ttt_client_execute_message,
136 "MESSAGE <message> ", "Send a message to everyone."},
138 {"STATISTICS", 1, 1, _ttt_client_execute_statistics,
139 "STATISTICS <username> ", "Lists the statistics for the specified user."},
141 {"QUIT", 0, 0, _ttt_client_execute_quit,
142 "QUIT ", "Quit session."},
144 {"VERSION", 1, 1, _ttt_client_execute_version,
145 "VERSION <client-version>", "Negotiate protocol version."},
147 {"WHO", 0, 0, _ttt_client_execute_who,
148 "WHO ", "List registered users."}
151 #define ARRAY_SIZE(arr) (sizeof(arr)/sizeof(arr[0]))
154 _ttt_client_execute_helo (ttt_client_t *client,
162 assert (num_args == 1);
164 ttt_client_set_username (client, args[0]);
166 error = ttt_server_register_client (client->server, client);
169 client->registered = TRUE;
171 xasprintf (&response, "HELO %s %s %s\r\n",
173 ttt_server_get_host (client->server),
174 ttt_server_get_port (client->server));
175 ttt_client_send (client, response);
177 xasprintf (¬ice, "NOTICE USER %s\r\n",
179 ttt_server_broadcast (client->server, notice);
184 return TTT_ERROR_NONE;
188 _ttt_client_execute_who (ttt_client_t *client,
194 assert (num_args == 0);
196 if (!client->registered)
197 return TTT_ERROR_NO_NAME_SET;
199 response = xstrdup (ttt_server_who (client->server));
200 ttt_client_send (client, response);
204 return TTT_ERROR_NONE;
208 _ttt_client_execute_statistics (ttt_client_t *client,
216 assert (num_args == 1);
220 if (!client->registered)
221 return TTT_ERROR_NO_NAME_SET;
223 error = ttt_server_statistics (client->server, username, &response);
227 ttt_client_send (client, response);
231 return TTT_ERROR_NONE;
235 _ttt_client_execute_message (ttt_client_t *client,
242 assert (num_args == 1);
244 if (!client->registered)
245 return TTT_ERROR_NO_NAME_SET;
247 xasprintf (&response, "MESSAGE\r\n");
248 ttt_client_send (client, response);
250 xasprintf (¬ice, "NOTICE MESSAGE %s \"%s\"\r\n",
253 ttt_server_broadcast (client->server, notice);
258 return TTT_ERROR_NONE;
262 _ttt_client_execute_help (ttt_client_t *client,
268 ttt_command_description_t *desc;
270 ttt_bool_t is_command = FALSE;
273 xasprintf (&response, "HELP \"\r\n"
274 "Available Commands:\r\n");
275 for (i = 0; i < ARRAY_SIZE(command_descriptions); i++) {
276 desc = &command_descriptions[i];
277 xasprintf (&response, "%s\r\n %s - %s\r\n",
282 xasprintf (&response, "%s\"\r\n", response);
285 for (i = 0; i < strlen (command); i++)
286 command[i] = toupper (command[i]);
287 for (i = 0; i < ARRAY_SIZE(command_descriptions); i++) {
288 desc = &command_descriptions[i];
289 if (strcmp (desc->command, command) == 0) {
291 xasprintf (&response, "HELP %s \"\r\n"
300 /* XXX: Add detailed help. */
305 if ((num_args == 1) && (!is_command))
306 return TTT_ERROR_SYNTAX;
308 ttt_client_send (client, response);
311 return TTT_ERROR_NONE;
315 _ttt_client_execute_version (ttt_client_t *client,
324 assert (num_args == 1);
326 clientversion = args[0];
328 /* Verify that provided version arg is a positive integer */
329 for (i = 0; i < strlen(clientversion); i++)
330 if (!isdigit (clientversion[i]))
331 return TTT_ERROR_SYNTAX;
333 version = atoi (clientversion);
336 return TTT_ERROR_SYNTAX;
338 if (version > TTT_SERVER_PROTOCOL_VERSION)
339 version = TTT_SERVER_PROTOCOL_VERSION;
341 xasprintf (&response, "VERSION %d\r\n", version);
342 ttt_client_send (client, response);
345 return TTT_ERROR_NONE;
349 _ttt_client_execute_quit (ttt_client_t *client,
355 assert (num_args == 0);
357 if (!client->registered)
358 return TTT_ERROR_QUIT_REQUESTED;
360 xasprintf (¬ice, "NOTICE QUIT %s\r\n",
362 ttt_server_broadcast (client->server, notice);
366 return TTT_ERROR_QUIT_REQUESTED;
370 _ttt_client_execute_invite (ttt_client_t *client,
374 const char *username;
379 assert (num_args == 1);
383 if (!client->registered)
384 return TTT_ERROR_NO_NAME_SET;
386 error = ttt_server_verify_username (client->server, username);
390 xasprintf (&response, "INVITE\r\n");
391 ttt_client_send (client, response);
393 xasprintf (¬ice, "NOTICE INVITE %s %s\r\n",
396 ttt_server_broadcast (client->server, notice);
398 /* XXX: Store invitation in state */
403 return TTT_ERROR_NONE;
407 _ttt_client_execute_accept (ttt_client_t *client,
411 const char *username;
416 assert (num_args == 1);
420 if (!client->registered)
421 return TTT_ERROR_NO_NAME_SET;
423 error = ttt_server_verify_username (client->server, username);
427 /* XXX: Verify invitation, else return ERROR NO_INVITE */
429 xasprintf (&response, "ACCEPT\r\n");
430 ttt_client_send (client, response);
432 xasprintf (¬ice, "NOTICE ACCEPT %s %s\r\n",
435 ttt_server_broadcast (client->server, notice);
437 /* XXX: Start a new game */
442 return TTT_ERROR_NONE;
446 _ttt_client_execute_retract (ttt_client_t *client,
450 const char *username;
455 assert (num_args == 1);
459 if (!client->registered)
460 return TTT_ERROR_NO_NAME_SET;
462 error = ttt_server_verify_username (client->server, username);
466 /* XXX: Verify invitation, else return ERROR NO_INVITE */
468 xasprintf (&response, "RETRACT\r\n");
469 ttt_client_send (client, response);
471 xasprintf (¬ice, "NOTICE RETRACT %s %s\r\n",
474 ttt_server_broadcast (client->server, notice);
476 /* XXX: Remove invitiation from state */
481 return TTT_ERROR_NONE;
485 _ttt_client_execute_decline (ttt_client_t *client,
489 const char *username;
494 assert (num_args == 1);
498 if (!client->registered)
499 return TTT_ERROR_NO_NAME_SET;
501 error = ttt_server_verify_username (client->server, username);
505 /* XXX: Verify invitation, else return ERROR NO_INVITE */
507 xasprintf (&response, "DECLINE\r\n");
508 ttt_client_send (client, response);
510 xasprintf (¬ice, "NOTICE DECLINE %s %s\r\n",
513 ttt_server_broadcast (client->server, notice);
515 /* XXX: Remove invitation from state */
520 return TTT_ERROR_NONE;
524 _free_request (ttt_client_t *client);
527 _ttt_client_init (ttt_client_t *client,
528 ttt_server_t *server,
533 pthread_mutex_init (&client->mutex, NULL);
535 client->server = server;
536 client->socket = socket;
538 file = xfdopen (socket, "r");
539 yylex_init (&client->scanner);
540 yyset_in (file, client->scanner);
542 client->request_strings = NULL;
543 client->num_request_strings = 0;
545 client->username = NULL;
546 client->registered = FALSE;
547 client->num_wins = 0;
551 _ttt_client_fini (ttt_client_t *client)
553 pthread_mutex_lock (&client->mutex);
555 if (client->registered)
556 ttt_server_unregister_client (client->server, client);
558 free (client->username);
559 client->username = NULL;
561 yylex_destroy (client->scanner);
562 shutdown (client->socket, SHUT_RDWR);
564 _free_request (client);
566 pthread_mutex_unlock (&client->mutex);
568 pthread_mutex_destroy (&client->mutex);
571 /* XXX: The memory management for the request strings is pretty cheesy. */
573 _append_to_request (ttt_client_t *client,
576 client->num_request_strings++;
577 client->request_strings =
578 xrealloc (client->request_strings,
579 client->num_request_strings * sizeof (char *));
581 client->request_strings[client->num_request_strings - 1] = xstrdup (string);
585 _free_request (ttt_client_t *client)
589 for (i = 0; i < client->num_request_strings; i++)
590 free (client->request_strings[i]);
592 free (client->request_strings);
594 client->request_strings = NULL;
595 client->num_request_strings = 0;
599 _read_request (ttt_client_t *client)
602 ttt_token_type_t token_type;
604 _free_request (client);
607 token_type = yylex (client->scanner, &token);
608 /* Yes, EOF in two different enums is pretty ugly. */
609 if (token_type == TTT_TOKEN_TYPE_EOF)
610 return TTT_STATUS_EOF;
612 if (token_type == TTT_TOKEN_TYPE_NEWLINE) {
613 if (client->num_request_strings)
614 return TTT_STATUS_SUCCESS;
619 assert (token_type == TTT_TOKEN_TYPE_STRING);
621 _append_to_request (client, token.u.string);
623 free (token.u.string);
628 _execute_request (ttt_client_t *client)
632 char *command = client->request_strings[0];
633 int num_args = client->num_request_strings-1;
634 ttt_command_description_t *desc;
636 for (i = 0; i < strlen (command); i++)
637 command[i] = toupper (command[i]);
639 for (i = 0; i < ARRAY_SIZE(command_descriptions); i++) {
640 desc = &command_descriptions[i];
641 if (strcmp (command, desc->command) == 0) {
642 if ((num_args < desc->args_min) || (num_args > desc->args_max))
643 return TTT_ERROR_SYNTAX;
644 return (desc->execute) (client,
645 &client->request_strings[1],
650 return TTT_ERROR_COMMAND;
654 _handle_requests_thread (void *closure)
658 ttt_client_t *client = closure;
662 status = _read_request (client);
663 if (status == TTT_STATUS_EOF)
668 error = _execute_request (client);
669 if (error == TTT_ERROR_QUIT_REQUESTED)
672 ttt_client_send (client, ttt_error_string (error));
675 _ttt_client_fini (client);
681 /* Exported: See ttt-client.h for documentation. */
683 ttt_client_new (void *closure, int client_socket)
685 ttt_server_t *server = closure;
686 ttt_client_t *client;
689 client = xmalloc (sizeof (ttt_client_t));
691 _ttt_client_init (client, server, client_socket);
693 err = pthread_create (&client->thread, NULL,
694 _handle_requests_thread, client);
696 fprintf (stderr, "Error: pthread_create failed: %s. Aborting.\r\n",
702 /* Exported: See ttt-client.h for documentation. */
704 ttt_client_send (ttt_client_t *client, const char *message)
706 pthread_mutex_lock (&client->mutex);
708 ttt_socket_write (client->socket, message, strlen (message));
710 pthread_mutex_unlock (&client->mutex);
713 /* Exported: See ttt-client.h for documentation. */
715 ttt_client_get_username (ttt_client_t *client)
717 return client->username;
720 /* Exported: See ttt-client.h for documentation. */
722 ttt_client_set_username (ttt_client_t *client, const char *username)
724 free (client->username);
725 client->username = xstrdup (username);
728 /* Exported: See ttt-client.h for documentation. */
730 ttt_client_get_num_wins (ttt_client_t *client)
732 return client->num_wins;