X-Git-Url: https://git.cworth.org/git?p=ttt;a=blobdiff_plain;f=src%2Fttt-client.c;h=c198d3a15cfdb6779bbeea9f40b2ea7ddcfe3dae;hp=b42db7a95064811b95fff78b4f0070236b4fe69c;hb=HEAD;hpb=80c3009d01077a141a0803267a1f0aff217ed61c diff --git a/src/ttt-client.c b/src/ttt-client.c index b42db7a..c198d3a 100644 --- a/src/ttt-client.c +++ b/src/ttt-client.c @@ -1,4 +1,4 @@ -/* ttt.c - client-server tic-tac-toe game +/* ttt-client.c - client handling code for tic-tac-toe game server * * Copyright © 2005 Carl Worth * @@ -19,17 +19,756 @@ * Author: Carl Worth */ -#include "ttt.h" +#include "ttt-client.h" +#include "ttt-error.h" +#include "ttt-server.h" +#include "ttt-socket.h" +#include "ttt-token.h" -int -main (int argc, char **argv) +#define YY_DECL int yylex (yyscan_t yyscanner, ttt_token_t *token) +#include "ttt-lex.h" +YY_DECL; + +struct _ttt_client { + pthread_mutex_t mutex; + pthread_t thread; + + ttt_server_t *server; + int socket; + yyscan_t scanner; + + char **request_strings; + int num_request_strings; + + char *username; + ttt_bool_t registered; + int num_wins; +}; + +typedef ttt_error_t (*ttt_command_func_t) (ttt_client_t *client, + char **args, + int num_args); + +static ttt_error_t +_ttt_client_execute_helo (ttt_client_t *client, + char **args, + int num_args); + +static ttt_error_t +_ttt_client_execute_who (ttt_client_t *client, + char **args, + int num_args); + +static ttt_error_t +_ttt_client_execute_statistics (ttt_client_t *client, + char **args, + int num_args); + +static ttt_error_t +_ttt_client_execute_message (ttt_client_t *client, + char **args, + int num_args); + +static ttt_error_t +_ttt_client_execute_help (ttt_client_t *client, + char **args, + int num_args); + +static ttt_error_t +_ttt_client_execute_version (ttt_client_t *client, + char **args, + int num_args); + +static ttt_error_t +_ttt_client_execute_quit (ttt_client_t *client, + char **args, + int num_args); + +static ttt_error_t +_ttt_client_execute_invite (ttt_client_t *client, + char **args, + int num_args); + +static ttt_error_t +_ttt_client_execute_accept (ttt_client_t *client, + char **args, + int num_args); + +static ttt_error_t +_ttt_client_execute_retract (ttt_client_t *client, + char **args, + int num_args); + +static ttt_error_t +_ttt_client_execute_decline (ttt_client_t *client, + char **args, + int num_args); + +typedef struct _ttt_command_description { + const char *command; + int args_min; + int args_max; + ttt_command_func_t execute; + const char *usage; + const char *description; +} ttt_command_description_t; + +ttt_command_description_t command_descriptions[] = { + {"HELO", 1, 1, _ttt_client_execute_helo, + "HELO ", "Register."}, + + {"HELP", 0, 1, _ttt_client_execute_help, + "HELP ", "Display help for a command."}, + + {"INVITE", 1, 1, _ttt_client_execute_invite, + "INVITE ", "Invite a player to play a game."}, + + {"ACCEPT", 1, 1, _ttt_client_execute_accept, + "ACCEPT ", "Accept a game invitation."}, + + {"RETRACT", 1, 1, _ttt_client_execute_retract, + "RETRACT ", "Retract a game invitation."}, + + {"DECLINE", 1, 1, _ttt_client_execute_decline, + "DECLINE ", "Decline a game invitation."}, + + {"MESSAGE", 1, 1, _ttt_client_execute_message, + "MESSAGE ", "Send a message to everyone."}, + + {"STATISTICS", 1, 1, _ttt_client_execute_statistics, + "STATISTICS ", "Lists the statistics for the specified user."}, + + {"QUIT", 0, 0, _ttt_client_execute_quit, + "QUIT ", "Quit session."}, + + {"VERSION", 1, 1, _ttt_client_execute_version, + "VERSION ", "Negotiate protocol version."}, + + {"WHO", 0, 0, _ttt_client_execute_who, + "WHO ", "List registered users."} +}; + +#define ARRAY_SIZE(arr) (sizeof(arr)/sizeof(arr[0])) + +static ttt_error_t +_ttt_client_execute_helo (ttt_client_t *client, + char **args, + int num_args) +{ + char *response; + char *notice; + ttt_error_t error; + + assert (num_args == 1); + + 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; + + xasprintf (¬ice, "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", + ttt_client_get_username (client), + ttt_server_get_host (client->server), + ttt_server_get_port (client->server)); + ttt_client_send (client, response); + + free (response); + + return TTT_ERROR_NONE; +} + +static ttt_error_t +_ttt_client_execute_who (ttt_client_t *client, + char **args, + int num_args) +{ + char *response; + + assert (num_args == 0); + + if (!client->registered) + return TTT_ERROR_NO_NAME_SET; + + response = xstrdup (ttt_server_who (client->server)); + ttt_client_send (client, response); + + free (response); + + return TTT_ERROR_NONE; +} + +static ttt_error_t +_ttt_client_execute_statistics (ttt_client_t *client, + char **args, + int num_args) +{ + const char *argusername; + ttt_client_t *argclient; + char *response; + ttt_error_t error; + + assert (num_args == 1); + + argusername = args[0]; + + if (!client->registered) + return TTT_ERROR_NO_NAME_SET; + + 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); + + return TTT_ERROR_NONE; +} + +static ttt_error_t +_ttt_client_execute_message (ttt_client_t *client, + char **args, + int num_args) +{ + const char *argmessage; + char *response; + char *notice; + + assert (num_args == 1); + + argmessage = args[0]; + + if (!client->registered) + return TTT_ERROR_NO_NAME_SET; + + xasprintf (&response, "MESSAGE\r\n"); + ttt_client_send (client, response); + + xasprintf (¬ice, "NOTICE MESSAGE %s \"%s\"\r\n", + ttt_client_get_username(client), + argmessage); + ttt_server_broadcast (client->server, notice); + + free (notice); + free (response); + + return TTT_ERROR_NONE; +} + +static ttt_error_t +_ttt_client_execute_help (ttt_client_t *client, + char **args, + int num_args) +{ + char *response; + char *argcommand; + ttt_command_description_t *desc; + ttt_bool_t is_command = FALSE; + int i; + + if (num_args == 0) { + xasprintf (&response, "HELP \"\r\n" + "Available Commands:\r\n"); + for (i = 0; i < ARRAY_SIZE(command_descriptions); i++) { + desc = &command_descriptions[i]; + xasprintf (&response, "%s\r\n %s - %s\r\n", + response, + desc->usage, + desc->description); + } + xasprintf (&response, "%s\"\r\n", response); + } else { + argcommand = args[0]; + for (i = 0; i < ARRAY_SIZE(command_descriptions); i++) { + desc = &command_descriptions[i]; + if (strcasecmp (desc->command, argcommand) == 0) { + is_command = TRUE; + xasprintf (&response, "HELP %s \"\r\n" + "%s\r\n" + "\r\n" + "Usage:\r\n" + " %s\r\n" + "\"\r\n", + desc->command, + desc->description, + desc->usage); + /* XXX: Add detailed help. */ + } + } + } + + if ((num_args == 1) && (!is_command)) + return TTT_ERROR_SYNTAX; + + ttt_client_send (client, response); + + free (response); + return TTT_ERROR_NONE; +} + +static ttt_error_t +_ttt_client_execute_version (ttt_client_t *client, + char **args, + int num_args) +{ + char *response; + char *argversion; + int version; + int i; + + assert (num_args == 1); + + argversion = args[0]; + + /* Verify that provided version arg is a positive integer */ + for (i = 0; i < strlen(argversion); i++) + if (!isdigit (argversion[i])) + return TTT_ERROR_SYNTAX; + + version = atoi (argversion); + + if (version < 1) + return TTT_ERROR_SYNTAX; + + if (version > TTT_SERVER_PROTOCOL_VERSION) + version = TTT_SERVER_PROTOCOL_VERSION; + + xasprintf (&response, "VERSION %d\r\n", + version); + ttt_client_send (client, response); + + free (response); + return TTT_ERROR_NONE; +} + +static ttt_error_t +_ttt_client_execute_quit (ttt_client_t *client, + char **args, + int num_args) { - ttt_args_t args; - int args_first; + char *notice; - ttt_args_parse (&args, argc, argv, &args_first); + assert (num_args == 0); - /* XXX: insert code here */ + if (!client->registered) + return TTT_ERROR_QUIT_REQUESTED; + + xasprintf (¬ice, "NOTICE QUIT %s\r\n", + ttt_client_get_username(client)); + ttt_server_broadcast (client->server, notice); + + free (notice); - return 0; + return TTT_ERROR_QUIT_REQUESTED; } + +static ttt_error_t +_ttt_client_execute_invite (ttt_client_t *client, + char **args, + int num_args) +{ + const char *invitee_username; + ttt_client_t *invitee; + char *response; + char *notice; + ttt_error_t error; + + assert (num_args == 1); + + invitee_username = args[0]; + + if (!client->registered) + return TTT_ERROR_NO_NAME_SET; + + 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 (¬ice, "NOTICE INVITE %s %s\r\n", + ttt_client_get_username(client), + ttt_client_get_username(invitee)); + ttt_server_broadcast (client->server, notice); + + free (notice); + free (response); + + return TTT_ERROR_NONE; +} + +static ttt_error_t +_ttt_client_execute_accept (ttt_client_t *client, + char **args, + int num_args) +{ + const char *actor_username; + ttt_client_t *actor; + char *response; + char *notice; + ttt_error_t error; + + assert (num_args == 1); + + actor_username = args[0]; + + if (!client->registered) + return TTT_ERROR_NO_NAME_SET; + + error = ttt_server_get_client_from_username (client->server, + actor_username, + &actor); + if (error) + return error; + + error = ttt_server_remove_invite (client->server, + actor, + client); + if (error) + return error; + + xasprintf (&response, "ACCEPT\r\n"); + ttt_client_send (client, response); + + xasprintf (¬ice, "NOTICE ACCEPT %s %s\r\n", + ttt_client_get_username(client), + ttt_client_get_username(actor)); + ttt_server_broadcast (client->server, notice); + + /* XXX: Start a new game */ + + free (notice); + free (response); + + return TTT_ERROR_NONE; +} + +static ttt_error_t +_ttt_client_execute_retract (ttt_client_t *client, + char **args, + int num_args) +{ + const char *invitee_username; + ttt_client_t *invitee; + char *response; + char *notice; + ttt_error_t error; + + assert (num_args == 1); + + invitee_username = args[0]; + + if (!client->registered) + return TTT_ERROR_NO_NAME_SET; + + error = ttt_server_get_client_from_username (client->server, + invitee_username, + &invitee); + if (error) + return error; + + error = ttt_server_remove_invite (client->server, + client, + invitee); + if (error) + return error; + + xasprintf (&response, "RETRACT\r\n"); + ttt_client_send (client, response); + + xasprintf (¬ice, "NOTICE RETRACT %s %s\r\n", + ttt_client_get_username(client), + ttt_client_get_username(invitee)); + ttt_server_broadcast (client->server, notice); + + free (notice); + free (response); + + return TTT_ERROR_NONE; +} + +static ttt_error_t +_ttt_client_execute_decline (ttt_client_t *client, + char **args, + int num_args) +{ + const char *actor_username; + ttt_client_t *actor; + char *response; + char *notice; + ttt_error_t error; + + assert (num_args == 1); + + actor_username = args[0]; + + if (!client->registered) + return TTT_ERROR_NO_NAME_SET; + + error = ttt_server_get_client_from_username (client->server, + actor_username, + &actor); + if (error) + return error; + + error = ttt_server_remove_invite (client->server, + actor, + client); + if (error) + return error; + + xasprintf (&response, "DECLINE\r\n"); + ttt_client_send (client, response); + + xasprintf (¬ice, "NOTICE DECLINE %s %s\r\n", + ttt_client_get_username(client), + ttt_client_get_username(actor)); + ttt_server_broadcast (client->server, notice); + + free (notice); + free (response); + + return TTT_ERROR_NONE; +} + +static void +_free_request (ttt_client_t *client); + +static void +_ttt_client_init (ttt_client_t *client, + ttt_server_t *server, + int socket) +{ + FILE *file; + + pthread_mutex_init (&client->mutex, NULL); + + client->server = server; + client->socket = socket; + + file = xfdopen (socket, "r"); + yylex_init (&client->scanner); + yyset_in (file, client->scanner); + + client->request_strings = NULL; + client->num_request_strings = 0; + + client->username = NULL; + client->registered = FALSE; + client->num_wins = 0; +} + +static void +_ttt_client_fini (ttt_client_t *client) +{ + pthread_mutex_lock (&client->mutex); + + if (client->registered) + ttt_server_unregister_client (client->server, client); + + free (client->username); + client->username = NULL; + + yylex_destroy (client->scanner); + shutdown (client->socket, SHUT_RDWR); + + _free_request (client); + + pthread_mutex_unlock (&client->mutex); + + pthread_mutex_destroy (&client->mutex); +} + +/* XXX: The memory management for the request strings is pretty cheesy. */ +static void +_append_to_request (ttt_client_t *client, + const char *string) +{ + client->num_request_strings++; + client->request_strings = + xrealloc (client->request_strings, + client->num_request_strings * sizeof (char *)); + + client->request_strings[client->num_request_strings - 1] = xstrdup (string); +} + +static void +_free_request (ttt_client_t *client) +{ + int i; + + for (i = 0; i < client->num_request_strings; i++) + free (client->request_strings[i]); + + free (client->request_strings); + + client->request_strings = NULL; + client->num_request_strings = 0; +} + +static ttt_status_t +_read_request (ttt_client_t *client) +{ + ttt_token_t token; + ttt_token_type_t token_type; + + _free_request (client); + + while (1) { + token_type = yylex (client->scanner, &token); + /* Yes, EOF in two different enums is pretty ugly. */ + if (token_type == TTT_TOKEN_TYPE_EOF) + return TTT_STATUS_EOF; + + if (token_type == TTT_TOKEN_TYPE_NEWLINE) { + if (client->num_request_strings) + return TTT_STATUS_SUCCESS; + else + continue; + } + + assert (token_type == TTT_TOKEN_TYPE_STRING); + + _append_to_request (client, token.u.string); + + free (token.u.string); + } +} + +static ttt_error_t +_execute_request (ttt_client_t *client) +{ + 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]); + + for (i = 0; i < ARRAY_SIZE(command_descriptions); i++) { + desc = &command_descriptions[i]; + if (strcmp (command, desc->command) == 0) { + if ((num_args < desc->args_min) || (num_args > desc->args_max)) + return TTT_ERROR_SYNTAX; + return (desc->execute) (client, + &client->request_strings[1], + num_args); + } + } + + return TTT_ERROR_COMMAND; +} + +static void * +_handle_requests_thread (void *closure) +{ + ttt_status_t status; + ttt_client_t *client = closure; + ttt_error_t error; + + while (1) { + status = _read_request (client); + if (status == TTT_STATUS_EOF) + break; + if (status) + ASSERT_NOT_REACHED; + + error = _execute_request (client); + if (error == TTT_ERROR_QUIT_REQUESTED) + break; + if (error) + ttt_client_send (client, ttt_error_string (error)); + } + + _ttt_client_fini (client); + free (client); + + return (void *) 0; +} + +/* Exported: See ttt-client.h for documentation. */ +void +ttt_client_new (void *closure, int client_socket) +{ + ttt_server_t *server = closure; + ttt_client_t *client; + int err; + + client = xmalloc (sizeof (ttt_client_t)); + + _ttt_client_init (client, server, client_socket); + + err = pthread_create (&client->thread, NULL, + _handle_requests_thread, client); + if (err != 0) { + fprintf (stderr, "Error: pthread_create failed: %s. Aborting.\r\n", + strerror (err)); + exit (1); + } +} + +/* Exported: See ttt-client.h for documentation. */ +void +ttt_client_send (ttt_client_t *client, const char *message) +{ + pthread_mutex_lock (&client->mutex); + + ttt_socket_write (client->socket, message, strlen (message)); + + pthread_mutex_unlock (&client->mutex); +} + +/* Exported: See ttt-client.h for documentation. */ +const char* +ttt_client_get_username (ttt_client_t *client) +{ + return client->username; +} + +/* Exported: See ttt-client.h for documentation. */ +void +ttt_client_set_username (ttt_client_t *client, const char *username) +{ + free (client->username); + client->username = xstrdup (username); +} + +/* Exported: See ttt-client.h for documentation. */ +int +ttt_client_get_num_wins (ttt_client_t *client) +{ + return client->num_wins; +} + +/* This is just to keep the compiler quiet about a function declared + * static but never defined. Just an annoying bug in flex's output. */ +static int yy_init_globals (yyscan_t yyscanner) {return 0;} +void use_yy_init_globals (void); +void use_yy_init_globals (void) {yyscan_t scan; yy_init_globals(scan);}