X-Git-Url: https://git.cworth.org/git?p=ttt;a=blobdiff_plain;f=src%2Fttt-client.c;h=1dd755f83651e3d7c6f80f176e0befa05f8af8dc;hp=9d44945e506664678854fab51c41fa3fd04e870d;hb=f2187ebd49f78b84bd2dca6172abc81e54dda199;hpb=907321c065fb1383e800f0794981df91d4327f57 diff --git a/src/ttt-client.c b/src/ttt-client.c index 9d44945..1dd755f 100644 --- a/src/ttt-client.c +++ b/src/ttt-client.c @@ -21,49 +21,294 @@ #include "ttt-client.h" +#define YY_DECL int yylex (yyscan_t yyscanner, ttt_token_t *token) + +#include "ttt-error.h" +#include "ttt-lex.h" #include "ttt-server.h" #include "ttt-socket.h" -#include "ttt-error.h" +#include "ttt-token.h" struct _ttt_client { pthread_mutex_t mutex; - pthread_t thread; + pthread_t thread; + + ttt_server_t *server; + int socket; + yyscan_t scanner; + + char **request_strings; + int num_request_strings; + + char *name; + 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_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); - ttt_server_t *server; - int socket; +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); + +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; - int id; +ttt_command_description_t command_descriptions[] = { + {"HELO", 1, 1, _ttt_client_execute_helo, + "HELO ", "Register."}, - char buf[TTT_CLIENT_BUF_SIZE]; - char *buf_head; - char *buf_tail; + {"HELP", 0, 1, _ttt_client_execute_help, + "HELP ", "Display help for a command."}, - char *request; - int request_size; - int request_len; + {"MESSAGE", 1, 1, _ttt_client_execute_message, + "MESSAGE ", "Send a message to everyone."}, + + {"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) +{ + ttt_error_t error; + char *response; + char *notice; + + assert (num_args == 1); + + ttt_client_set_name (client, args[0]); + + error = ttt_server_register_client (client->server, client); + if (error) + return error; + client->registered = TRUE; + + xasprintf (&response, "HELO %s %s %s\r\n", + client->name, + ttt_server_get_host (client->server), + ttt_server_get_port (client->server)); + ttt_client_send (client, response); + + xasprintf (¬ice, "NOTICE USER %s\r\n", + client->name); + ttt_server_broadcast (client->server, notice); + + free (notice); + 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_message (ttt_client_t *client, + char **args, + int num_args) +{ + char *response; + char *notice; + + assert (num_args == 1); + + 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", + client->name, + args[0]); + 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 *command; + ttt_command_description_t *desc; + int i; + ttt_bool_t is_command = FALSE; + + 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 { + command = args[0]; + 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 (desc->command, command) == 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; + + assert (num_args == 1); + + /* XXX: Argument is being ignored. + This is not completely implemented. */ + + xasprintf (&response, "VERSION 1\r\n"); + 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) +{ + char *notice; + + assert (num_args == 0); + + if (!client->registered) + return TTT_ERROR_QUIT_REQUESTED; + + xasprintf (¬ice, "NOTICE QUIT %s\r\n", + client->name); + ttt_server_broadcast (client->server, notice); + + free (notice); + + return TTT_ERROR_QUIT_REQUESTED; +} + +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; - client->buf_head = client->buf; - client->buf_tail = client->buf; + file = xfdopen (socket, "r"); + yylex_init (&client->scanner); + yyset_in (file, client->scanner); - client->request = NULL; - client->request_size = 0; - client->request_len = 0; + client->request_strings = NULL; + client->num_request_strings = 0; - /* XXX: Probably want to register only as the result of the HELO - command. Not only will that match the protocol correctly, but - it will also eliminate a race condition here. */ - client->id = ttt_server_register_client (server, client); + client->name = NULL; + client->registered = FALSE; + client->num_wins = 0; } static void @@ -71,101 +316,102 @@ _ttt_client_fini (ttt_client_t *client) { pthread_mutex_lock (&client->mutex); - ttt_server_unregister_client (client->server, client); + if (client->registered) + ttt_server_unregister_client (client->server, client); + + free (client->name); + client->name = NULL; + yylex_destroy (client->scanner); shutdown (client->socket, SHUT_RDWR); - free (client->request); + _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 *buf, - int size) + const char *string) { - int size_needed = client->request_len + size; - - if (size_needed > client->request_size) { - if (client->request_size == 0) { - client->request_size = size_needed; - } else { - while (size_needed > client->request_size) - client->request_size *= 2; - } - - client->request = xrealloc (client->request, client->request_size); - } - - memcpy (client->request + client->request_len, - buf, size); + client->num_request_strings++; + client->request_strings = + xrealloc (client->request_strings, + client->num_request_strings * sizeof (char *)); - client->request_len += size; + client->request_strings[client->num_request_strings - 1] = xstrdup (string); } -static ttt_status_t -_read_into_request_until (ttt_client_t *client, char delimeter) +static void +_free_request (ttt_client_t *client) { - ttt_bool_t found_delimeter = FALSE; - int bytes_read; - char *s; - - client->request_len = 0; - - while (1) { - - if (client->buf_tail >= client->buf_head) { - bytes_read = xread (client->socket, - client->buf, - TTT_CLIENT_BUF_SIZE); - if (bytes_read == 0) - return TTT_STATUS_EOF; - client->buf_head = client->buf; - client->buf_tail = client->buf_head + bytes_read; - } + int i; - for (s = client->buf_head; s < client->buf_tail; s++) { - if (*s == delimeter) { - found_delimeter = TRUE; - s++; - break; - } - } + for (i = 0; i < client->num_request_strings; i++) + free (client->request_strings[i]); - _append_to_request (client, - client->buf_head, - s - client->buf_head); - client->buf_head = s; + free (client->request_strings); - if (found_delimeter) - return TTT_STATUS_SUCCESS; - } + client->request_strings = NULL; + client->num_request_strings = 0; } static ttt_status_t _read_request (ttt_client_t *client) { - ttt_status_t status; - static const char null_terminator = '\0'; + ttt_token_t token; + ttt_token_type_t token_type; - status = _read_into_request_until (client, '\n'); - if (status) - return status; + _free_request (client); - _append_to_request (client, &null_terminator, 1); + 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; + } - return TTT_STATUS_SUCCESS; + 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) { - ttt_server_broadcast (client->server, client->request); + int i; + + char *command = client->request_strings[0]; + int num_args = client->num_request_strings-1; + ttt_command_description_t *desc; + + 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_NONE; + return TTT_ERROR_COMMAND; } static void * @@ -184,6 +430,8 @@ _handle_requests_thread (void *closure) ASSERT_NOT_REACHED; error = _execute_request (client); + if (error == TTT_ERROR_QUIT_REQUESTED) + break; if (error) ttt_client_send (client, ttt_error_string (error)); } @@ -209,7 +457,7 @@ ttt_client_new (void *closure, int client_socket) err = pthread_create (&client->thread, NULL, _handle_requests_thread, client); if (err != 0) { - fprintf (stderr, "Error: pthread_create failed: %s. Aborting.\n", + fprintf (stderr, "Error: pthread_create failed: %s. Aborting.\r\n", strerror (err)); exit (1); } @@ -226,9 +474,24 @@ ttt_client_send (ttt_client_t *client, const char *message) pthread_mutex_unlock (&client->mutex); } +/* Exported: See ttt-client.h for documentation. */ +const char* +ttt_client_get_name (ttt_client_t *client) +{ + return client->name; +} + +/* Exported: See ttt-client.h for documentation. */ +void +ttt_client_set_name (ttt_client_t *client, const char *name) +{ + free (client->name); + client->name = xstrdup (name); +} + /* Exported: See ttt-client.h for documentation. */ int -ttt_client_get_id (ttt_client_t *client) +ttt_client_get_num_wins (ttt_client_t *client) { - return client->id; + return client->num_wins; }