]> git.cworth.org Git - ttt/blob - src/ttt-client.c
2005-12-03 Richard D. Worth <richard@theworths.org>
[ttt] / src / ttt-client.c
1 /* ttt-client.c - client handling code for tic-tac-toe game server
2  *
3  * Copyright © 2005 Carl Worth
4  *
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)
8  * any later version.
9  *
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.
14  *
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.
18  *
19  * Author: Carl Worth <cworth@cworth.org>
20  */
21
22 #include "ttt-client.h"
23
24 #define YY_DECL int yylex (yyscan_t yyscanner, ttt_token_t *token)
25
26 #include "ttt-error.h"
27 #include "ttt-lex.h"
28 #include "ttt-server.h"
29 #include "ttt-socket.h"
30 #include "ttt-token.h"
31
32 struct _ttt_client {
33     pthread_mutex_t mutex;
34     pthread_t       thread;
35     
36     ttt_server_t    *server;
37     int             socket;
38     yyscan_t        scanner;
39     
40     char            **request_strings;
41     int             num_request_strings;
42     
43     char            *username;
44     ttt_bool_t      registered;
45     int             num_wins;
46 };
47
48 typedef ttt_error_t (*ttt_command_func_t) (ttt_client_t *client,
49                                            char **args,
50                                            int num_args);
51
52 static ttt_error_t
53 _ttt_client_execute_helo (ttt_client_t *client,
54                           char         **args,
55                           int          num_args);
56
57 static ttt_error_t
58 _ttt_client_execute_who (ttt_client_t *client,
59                          char         **args,
60                          int          num_args);
61
62 static ttt_error_t
63 _ttt_client_execute_statistics (ttt_client_t *client,
64                                 char         **args,
65                                 int          num_args);
66
67 static ttt_error_t
68 _ttt_client_execute_message (ttt_client_t *client,
69                              char         **args,
70                              int          num_args);
71
72 static ttt_error_t
73 _ttt_client_execute_help (ttt_client_t  *client,
74                           char     **args,
75                           int      num_args);
76
77 static ttt_error_t
78 _ttt_client_execute_version (ttt_client_t  *client,
79                              char          **args,
80                              int           num_args);
81
82 static ttt_error_t
83 _ttt_client_execute_quit (ttt_client_t *client,
84                           char         **args,
85                           int          num_args);
86
87 typedef struct _ttt_command_description {
88     const char         *command;
89     int                args_min;
90     int                args_max;
91     ttt_command_func_t execute;
92     const char         *usage;
93     const char         *description;
94 } ttt_command_description_t;
95
96 ttt_command_description_t command_descriptions[] = {
97     {"HELO",    1, 1, _ttt_client_execute_helo,
98      "HELO <username>         ", "Register."},
99
100     {"HELP",    0, 1, _ttt_client_execute_help,
101      "HELP <command>          ", "Display help for a command."},
102
103     {"MESSAGE", 1, 1, _ttt_client_execute_message,
104      "MESSAGE <message>       ", "Send a message to everyone."},
105
106     {"STATISTICS", 1, 1, _ttt_client_execute_statistics,
107      "STATISTICS <username>   ", "Lists the statistics for the specified user."},
108
109     {"QUIT",    0, 0, _ttt_client_execute_quit,
110      "QUIT                    ", "Quit session."},
111
112     {"VERSION", 1, 1, _ttt_client_execute_version,
113      "VERSION <client-version>", "Negotiate protocol version."},
114
115     {"WHO",     0, 0, _ttt_client_execute_who,
116      "WHO                     ", "List registered users."}
117 };
118
119 #define ARRAY_SIZE(arr) (sizeof(arr)/sizeof(arr[0]))
120
121 static ttt_error_t
122 _ttt_client_execute_helo (ttt_client_t *client,
123                           char         **args,
124                           int          num_args)
125 {
126     ttt_error_t error;
127     char *response;
128     char *notice;
129
130     assert (num_args == 1);
131
132     ttt_client_set_username (client, args[0]);
133
134     error = ttt_server_register_client (client->server, client);
135     if (error)
136         return error;
137     client->registered = TRUE;
138
139     xasprintf (&response, "HELO %s %s %s\r\n",
140                client->username,
141                ttt_server_get_host (client->server),
142                ttt_server_get_port (client->server));
143     ttt_client_send (client, response);
144
145     xasprintf (&notice, "NOTICE USER %s\r\n",
146                client->username);
147     ttt_server_broadcast (client->server, notice);
148
149     free (notice);
150     free (response);
151
152     return TTT_ERROR_NONE;
153 }
154
155 static ttt_error_t
156 _ttt_client_execute_who (ttt_client_t *client,
157                          char         **args,
158                          int          num_args)
159 {
160     char *response;
161
162     assert (num_args == 0);
163
164     if (!client->registered)
165         return TTT_ERROR_NO_NAME_SET;
166
167     response = xstrdup (ttt_server_who (client->server));
168     ttt_client_send (client, response);
169
170     free (response);
171
172     return TTT_ERROR_NONE;
173 }
174
175 static ttt_error_t
176 _ttt_client_execute_statistics (ttt_client_t *client,
177                                 char         **args,
178                                 int          num_args)
179 {
180     char *response;
181     char *username;
182     ttt_error_t error;
183
184     assert (num_args == 1);
185
186     username = args[0];
187
188     if (!client->registered)
189         return TTT_ERROR_NO_NAME_SET;
190
191     error = ttt_server_statistics (client->server, username, &response);
192     if (error)
193         return error;
194
195     ttt_client_send (client, response);
196
197     free (response);
198
199     return TTT_ERROR_NONE;
200 }
201
202 static ttt_error_t
203 _ttt_client_execute_message (ttt_client_t  *client,
204                              char          **args,
205                              int           num_args)
206 {
207     char *response;
208     char *notice;
209
210     assert (num_args == 1);
211
212     if (!client->registered)
213         return TTT_ERROR_NO_NAME_SET;
214
215     xasprintf (&response, "MESSAGE\r\n");
216     ttt_client_send (client, response);
217
218     xasprintf (&notice, "NOTICE MESSAGE %s \"%s\"\r\n",
219               client->username,
220               args[0]);
221     ttt_server_broadcast (client->server, notice);
222
223     free (notice);
224     free (response);
225
226     return TTT_ERROR_NONE;
227 }
228
229 static ttt_error_t
230 _ttt_client_execute_help (ttt_client_t  *client,
231                           char     **args,
232                           int      num_args)
233 {
234     char *response;
235     char *command;
236     ttt_command_description_t *desc;
237     int i;
238     ttt_bool_t is_command = FALSE;
239     
240     if (num_args == 0) {
241         xasprintf (&response, "HELP \"\r\n"
242                    "Available Commands:\r\n");
243         for (i = 0; i < ARRAY_SIZE(command_descriptions); i++) {
244             desc = &command_descriptions[i];
245             xasprintf (&response, "%s\r\n  %s - %s\r\n",
246                        response,
247                        desc->usage,
248                        desc->description);
249         }
250         xasprintf (&response, "%s\"\r\n", response);
251     } else {
252         command = args[0];
253         for (i = 0; i < strlen (command); i++)
254             command[i] = toupper (command[i]);
255         for (i = 0; i < ARRAY_SIZE(command_descriptions); i++) {
256             desc = &command_descriptions[i];
257             if (strcmp (desc->command, command) == 0) {
258                 is_command = TRUE;
259                 xasprintf (&response, "HELP %s \"\r\n"
260                            "%s\r\n"
261                            "\r\n"
262                            "Usage:\r\n"
263                            "  %s\r\n"
264                            "\"\r\n",
265                            desc->command,
266                            desc->description,
267                            desc->usage);
268                 /* XXX: Add detailed help. */
269             }
270         }
271     }
272     
273     if ((num_args == 1) && (!is_command))
274         return TTT_ERROR_SYNTAX;
275
276     ttt_client_send (client, response);
277
278     free (response);
279     return TTT_ERROR_NONE;
280 }
281
282 static ttt_error_t
283 _ttt_client_execute_version (ttt_client_t  *client,
284                              char          **args,
285                              int           num_args)
286 {
287     char *response;
288     char *clientversion;
289     int version;
290     int i;
291
292     assert (num_args == 1);
293
294     clientversion = args[0];
295
296     /* Verify that provided version arg is a positive integer */
297     for (i = 0; i < strlen(clientversion); i++)
298         if (!isdigit (clientversion[i]))
299             return TTT_ERROR_SYNTAX;
300
301     version = atoi (clientversion);
302
303     if (version < 1)
304         return TTT_ERROR_SYNTAX;
305
306     if (version > TTT_SERVER_PROTOCOL_VERSION)
307         version = TTT_SERVER_PROTOCOL_VERSION;
308
309     xasprintf (&response, "VERSION %d\r\n", version);
310     ttt_client_send (client, response);
311
312     free (response);
313     return TTT_ERROR_NONE;
314 }
315
316 static ttt_error_t
317 _ttt_client_execute_quit (ttt_client_t *client,
318                           char         **args,
319                           int          num_args)
320 {
321     char *notice;
322
323     assert (num_args == 0);
324
325     if (!client->registered)
326         return TTT_ERROR_QUIT_REQUESTED;
327     
328     xasprintf (&notice, "NOTICE QUIT %s\r\n",
329                client->username);
330     ttt_server_broadcast (client->server, notice);
331     
332     free (notice);
333
334     return TTT_ERROR_QUIT_REQUESTED;
335 }
336
337 static void
338 _free_request (ttt_client_t *client);
339
340 static void
341 _ttt_client_init (ttt_client_t  *client,
342                   ttt_server_t  *server,
343                   int            socket)
344 {
345     FILE *file;
346
347     pthread_mutex_init (&client->mutex, NULL);
348
349     client->server = server;
350     client->socket = socket;
351
352     file = xfdopen (socket, "r");
353     yylex_init (&client->scanner);
354     yyset_in (file, client->scanner);
355
356     client->request_strings = NULL;
357     client->num_request_strings = 0;
358
359     client->username = NULL;
360     client->registered = FALSE;
361     client->num_wins = 0;
362 }
363
364 static void
365 _ttt_client_fini (ttt_client_t *client)
366 {
367     pthread_mutex_lock (&client->mutex);
368
369     if (client->registered)
370         ttt_server_unregister_client (client->server, client);
371
372     free (client->username);
373     client->username = NULL;
374
375     yylex_destroy (client->scanner);
376     shutdown (client->socket, SHUT_RDWR);
377
378     _free_request (client);
379
380     pthread_mutex_unlock (&client->mutex);
381
382     pthread_mutex_destroy (&client->mutex);
383 }
384
385 /* XXX: The memory management for the request strings is pretty cheesy. */
386 static void
387 _append_to_request (ttt_client_t        *client,
388                     const char          *string)
389 {
390     client->num_request_strings++;
391     client->request_strings =
392         xrealloc (client->request_strings,
393                   client->num_request_strings * sizeof (char *));
394
395     client->request_strings[client->num_request_strings - 1] = xstrdup (string);
396 }
397
398 static void
399 _free_request (ttt_client_t *client)
400 {
401     int i;
402
403     for (i = 0; i < client->num_request_strings; i++)
404         free (client->request_strings[i]);
405
406     free (client->request_strings);
407
408     client->request_strings = NULL;
409     client->num_request_strings = 0;
410 }
411
412 static ttt_status_t
413 _read_request (ttt_client_t *client)
414 {
415     ttt_token_t token;
416     ttt_token_type_t token_type;
417
418     _free_request (client);
419
420     while (1) {
421         token_type = yylex (client->scanner, &token);
422         /* Yes, EOF in two different enums is pretty ugly. */
423         if (token_type == TTT_TOKEN_TYPE_EOF)
424             return TTT_STATUS_EOF;
425
426         if (token_type == TTT_TOKEN_TYPE_NEWLINE) {
427             if (client->num_request_strings)
428                 return TTT_STATUS_SUCCESS;
429             else
430                 continue;
431         }
432
433         assert (token_type == TTT_TOKEN_TYPE_STRING);
434          
435         _append_to_request (client, token.u.string);
436         
437         free (token.u.string);
438     }
439 }
440
441 static ttt_error_t
442 _execute_request (ttt_client_t *client)
443 {
444     int i;
445
446     char *command = client->request_strings[0];
447     int num_args = client->num_request_strings-1;
448     ttt_command_description_t *desc;
449
450     for (i = 0; i < strlen (command); i++)
451         command[i] = toupper (command[i]);
452
453     for (i = 0; i < ARRAY_SIZE(command_descriptions); i++) {
454         desc = &command_descriptions[i];
455         if (strcmp (command, desc->command) == 0) {
456             if ((num_args < desc->args_min) || (num_args > desc->args_max))
457                 return TTT_ERROR_SYNTAX;
458             return (desc->execute) (client,
459                                     &client->request_strings[1],
460                                     num_args);
461         }
462     }
463
464     return TTT_ERROR_COMMAND;
465 }
466
467 static void *
468 _handle_requests_thread (void *closure)
469 {
470     ttt_status_t status;
471     ttt_error_t error;
472     ttt_client_t *client = closure;
473
474     while (1) {
475
476         status = _read_request (client);
477         if (status == TTT_STATUS_EOF)
478             break;
479         if (status)
480             ASSERT_NOT_REACHED;
481
482         error = _execute_request (client);
483         if (error == TTT_ERROR_QUIT_REQUESTED)
484             break;
485         if (error)
486             ttt_client_send (client, ttt_error_string (error));
487     }
488
489     _ttt_client_fini (client);
490     free (client);
491
492     return (void *) 0;
493 }
494
495 /* Exported: See ttt-client.h for documentation. */
496 void
497 ttt_client_new (void *closure, int client_socket)
498 {
499     ttt_server_t *server = closure;
500     ttt_client_t *client;
501     int err;
502
503     client = xmalloc (sizeof (ttt_client_t));
504     
505     _ttt_client_init (client, server, client_socket);
506
507     err = pthread_create (&client->thread, NULL,
508                           _handle_requests_thread, client);
509     if (err != 0) {
510         fprintf (stderr, "Error: pthread_create failed: %s. Aborting.\r\n",
511                  strerror (err));
512         exit (1);
513     }
514 }
515
516 /* Exported: See ttt-client.h for documentation. */
517 void
518 ttt_client_send (ttt_client_t *client, const char *message)
519 {
520     pthread_mutex_lock (&client->mutex);
521
522     ttt_socket_write (client->socket, message, strlen (message));
523
524     pthread_mutex_unlock (&client->mutex);
525 }
526
527 /* Exported: See ttt-client.h for documentation. */
528 const char*
529 ttt_client_get_username (ttt_client_t *client)
530 {
531     return client->username;
532 }
533
534 /* Exported: See ttt-client.h for documentation. */
535 void
536 ttt_client_set_username (ttt_client_t *client, const char *username)
537 {
538     free (client->username);
539     client->username = xstrdup (username);
540 }
541
542 /* Exported: See ttt-client.h for documentation. */
543 int
544 ttt_client_get_num_wins (ttt_client_t *client)
545 {
546     return client->num_wins;
547 }