]> git.cworth.org Git - ttt/blob - src/ttt-client.c
9d88380640bd096bc950c0965a2f78f467753086
[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 static ttt_error_t
88 _ttt_client_execute_invite (ttt_client_t *client,
89                             char         **args,
90                             int          num_args);
91
92 static ttt_error_t
93 _ttt_client_execute_accept (ttt_client_t *client,
94                             char         **args,
95                             int          num_args);
96
97 typedef struct _ttt_command_description {
98     const char         *command;
99     int                args_min;
100     int                args_max;
101     ttt_command_func_t execute;
102     const char         *usage;
103     const char         *description;
104 } ttt_command_description_t;
105
106 ttt_command_description_t command_descriptions[] = {
107     {"HELO",    1, 1, _ttt_client_execute_helo,
108      "HELO <username>         ", "Register."},
109
110     {"HELP",    0, 1, _ttt_client_execute_help,
111      "HELP <command>          ", "Display help for a command."},
112
113     {"INVITE",  1, 1, _ttt_client_execute_invite,
114      "INVITE <username>       ", "Invite a player to play a game."},
115
116     {"ACCEPT",  1, 1, _ttt_client_execute_accept,
117      "ACCEPT <username>       ", "Accept a game invitation."},
118
119     {"MESSAGE", 1, 1, _ttt_client_execute_message,
120      "MESSAGE <message>       ", "Send a message to everyone."},
121
122     {"STATISTICS", 1, 1, _ttt_client_execute_statistics,
123      "STATISTICS <username>   ", "Lists the statistics for the specified user."},
124
125     {"QUIT",    0, 0, _ttt_client_execute_quit,
126      "QUIT                    ", "Quit session."},
127
128     {"VERSION", 1, 1, _ttt_client_execute_version,
129      "VERSION <client-version>", "Negotiate protocol version."},
130
131     {"WHO",     0, 0, _ttt_client_execute_who,
132      "WHO                     ", "List registered users."}
133 };
134
135 #define ARRAY_SIZE(arr) (sizeof(arr)/sizeof(arr[0]))
136
137 static ttt_error_t
138 _ttt_client_execute_helo (ttt_client_t *client,
139                           char         **args,
140                           int          num_args)
141 {
142     ttt_error_t error;
143     char *response;
144     char *notice;
145
146     assert (num_args == 1);
147
148     ttt_client_set_username (client, args[0]);
149
150     error = ttt_server_register_client (client->server, client);
151     if (error)
152         return error;
153     client->registered = TRUE;
154
155     xasprintf (&response, "HELO %s %s %s\r\n",
156                client->username,
157                ttt_server_get_host (client->server),
158                ttt_server_get_port (client->server));
159     ttt_client_send (client, response);
160
161     xasprintf (&notice, "NOTICE USER %s\r\n",
162                client->username);
163     ttt_server_broadcast (client->server, notice);
164
165     free (notice);
166     free (response);
167
168     return TTT_ERROR_NONE;
169 }
170
171 static ttt_error_t
172 _ttt_client_execute_who (ttt_client_t *client,
173                          char         **args,
174                          int          num_args)
175 {
176     char *response;
177
178     assert (num_args == 0);
179
180     if (!client->registered)
181         return TTT_ERROR_NO_NAME_SET;
182
183     response = xstrdup (ttt_server_who (client->server));
184     ttt_client_send (client, response);
185
186     free (response);
187
188     return TTT_ERROR_NONE;
189 }
190
191 static ttt_error_t
192 _ttt_client_execute_statistics (ttt_client_t *client,
193                                 char         **args,
194                                 int          num_args)
195 {
196     char *response;
197     char *username;
198     ttt_error_t error;
199
200     assert (num_args == 1);
201
202     username = args[0];
203
204     if (!client->registered)
205         return TTT_ERROR_NO_NAME_SET;
206
207     error = ttt_server_statistics (client->server, username, &response);
208     if (error)
209         return error;
210
211     ttt_client_send (client, response);
212
213     free (response);
214
215     return TTT_ERROR_NONE;
216 }
217
218 static ttt_error_t
219 _ttt_client_execute_message (ttt_client_t  *client,
220                              char          **args,
221                              int           num_args)
222 {
223     char *response;
224     char *notice;
225
226     assert (num_args == 1);
227
228     if (!client->registered)
229         return TTT_ERROR_NO_NAME_SET;
230
231     xasprintf (&response, "MESSAGE\r\n");
232     ttt_client_send (client, response);
233
234     xasprintf (&notice, "NOTICE MESSAGE %s \"%s\"\r\n",
235               client->username,
236               args[0]);
237     ttt_server_broadcast (client->server, notice);
238
239     free (notice);
240     free (response);
241
242     return TTT_ERROR_NONE;
243 }
244
245 static ttt_error_t
246 _ttt_client_execute_help (ttt_client_t  *client,
247                           char          **args,
248                           int           num_args)
249 {
250     char *response;
251     char *command;
252     ttt_command_description_t *desc;
253     int i;
254     ttt_bool_t is_command = FALSE;
255     
256     if (num_args == 0) {
257         xasprintf (&response, "HELP \"\r\n"
258                    "Available Commands:\r\n");
259         for (i = 0; i < ARRAY_SIZE(command_descriptions); i++) {
260             desc = &command_descriptions[i];
261             xasprintf (&response, "%s\r\n  %s - %s\r\n",
262                        response,
263                        desc->usage,
264                        desc->description);
265         }
266         xasprintf (&response, "%s\"\r\n", response);
267     } else {
268         command = args[0];
269         for (i = 0; i < strlen (command); i++)
270             command[i] = toupper (command[i]);
271         for (i = 0; i < ARRAY_SIZE(command_descriptions); i++) {
272             desc = &command_descriptions[i];
273             if (strcmp (desc->command, command) == 0) {
274                 is_command = TRUE;
275                 xasprintf (&response, "HELP %s \"\r\n"
276                            "%s\r\n"
277                            "\r\n"
278                            "Usage:\r\n"
279                            "  %s\r\n"
280                            "\"\r\n",
281                            desc->command,
282                            desc->description,
283                            desc->usage);
284                 /* XXX: Add detailed help. */
285             }
286         }
287     }
288     
289     if ((num_args == 1) && (!is_command))
290         return TTT_ERROR_SYNTAX;
291
292     ttt_client_send (client, response);
293
294     free (response);
295     return TTT_ERROR_NONE;
296 }
297
298 static ttt_error_t
299 _ttt_client_execute_version (ttt_client_t  *client,
300                              char          **args,
301                              int           num_args)
302 {
303     char *response;
304     char *clientversion;
305     int version;
306     int i;
307
308     assert (num_args == 1);
309
310     clientversion = args[0];
311
312     /* Verify that provided version arg is a positive integer */
313     for (i = 0; i < strlen(clientversion); i++)
314         if (!isdigit (clientversion[i]))
315             return TTT_ERROR_SYNTAX;
316
317     version = atoi (clientversion);
318
319     if (version < 1)
320         return TTT_ERROR_SYNTAX;
321
322     if (version > TTT_SERVER_PROTOCOL_VERSION)
323         version = TTT_SERVER_PROTOCOL_VERSION;
324
325     xasprintf (&response, "VERSION %d\r\n", version);
326     ttt_client_send (client, response);
327
328     free (response);
329     return TTT_ERROR_NONE;
330 }
331
332 static ttt_error_t
333 _ttt_client_execute_quit (ttt_client_t *client,
334                           char         **args,
335                           int          num_args)
336 {
337     char *notice;
338
339     assert (num_args == 0);
340
341     if (!client->registered)
342         return TTT_ERROR_QUIT_REQUESTED;
343     
344     xasprintf (&notice, "NOTICE QUIT %s\r\n",
345                client->username);
346     ttt_server_broadcast (client->server, notice);
347     
348     free (notice);
349
350     return TTT_ERROR_QUIT_REQUESTED;
351 }
352
353 static ttt_error_t
354 _ttt_client_execute_invite (ttt_client_t *client,
355                             char         **args,
356                             int          num_args)
357 {
358     const char *username;
359     char *response;
360     char *notice;
361     ttt_error_t error;
362
363     assert (num_args == 1);
364
365     username = args[0];
366
367     if (!client->registered)
368         return TTT_ERROR_NO_NAME_SET;
369
370     error = ttt_server_verify_username (client->server, username);
371     if (error)
372         return error;
373
374     xasprintf (&response, "INVITE\r\n");
375     ttt_client_send (client, response);
376
377     xasprintf (&notice, "NOTICE INVITE %s %s\r\n",
378                client->username,
379                username);
380     ttt_server_broadcast (client->server, notice);
381
382     free (notice);
383     free (response);
384
385     return TTT_ERROR_NONE;
386 }
387
388 static ttt_error_t
389 _ttt_client_execute_accept (ttt_client_t *client,
390                             char         **args,
391                             int          num_args)
392 {
393     const char *username;
394     char *response;
395     char *notice;
396     ttt_error_t error;
397
398     assert (num_args == 1);
399
400     username = args[0];
401
402     if (!client->registered)
403         return TTT_ERROR_NO_NAME_SET;
404
405     error = ttt_server_verify_username (client->server, username);
406     if (error)
407         return error;
408
409     xasprintf (&response, "ACCEPT\r\n");
410     ttt_client_send (client, response);
411
412     xasprintf (&notice, "NOTICE ACCEPT %s %s\r\n",
413                client->username,
414                username);
415     ttt_server_broadcast (client->server, notice);
416
417     /* XXX: Start a new game */
418
419     free (notice);
420     free (response);
421
422     return TTT_ERROR_NONE;
423 }
424
425 static void
426 _free_request (ttt_client_t *client);
427
428 static void
429 _ttt_client_init (ttt_client_t  *client,
430                   ttt_server_t  *server,
431                   int            socket)
432 {
433     FILE *file;
434
435     pthread_mutex_init (&client->mutex, NULL);
436
437     client->server = server;
438     client->socket = socket;
439
440     file = xfdopen (socket, "r");
441     yylex_init (&client->scanner);
442     yyset_in (file, client->scanner);
443
444     client->request_strings = NULL;
445     client->num_request_strings = 0;
446
447     client->username = NULL;
448     client->registered = FALSE;
449     client->num_wins = 0;
450 }
451
452 static void
453 _ttt_client_fini (ttt_client_t *client)
454 {
455     pthread_mutex_lock (&client->mutex);
456
457     if (client->registered)
458         ttt_server_unregister_client (client->server, client);
459
460     free (client->username);
461     client->username = NULL;
462
463     yylex_destroy (client->scanner);
464     shutdown (client->socket, SHUT_RDWR);
465
466     _free_request (client);
467
468     pthread_mutex_unlock (&client->mutex);
469
470     pthread_mutex_destroy (&client->mutex);
471 }
472
473 /* XXX: The memory management for the request strings is pretty cheesy. */
474 static void
475 _append_to_request (ttt_client_t        *client,
476                     const char          *string)
477 {
478     client->num_request_strings++;
479     client->request_strings =
480         xrealloc (client->request_strings,
481                   client->num_request_strings * sizeof (char *));
482
483     client->request_strings[client->num_request_strings - 1] = xstrdup (string);
484 }
485
486 static void
487 _free_request (ttt_client_t *client)
488 {
489     int i;
490
491     for (i = 0; i < client->num_request_strings; i++)
492         free (client->request_strings[i]);
493
494     free (client->request_strings);
495
496     client->request_strings = NULL;
497     client->num_request_strings = 0;
498 }
499
500 static ttt_status_t
501 _read_request (ttt_client_t *client)
502 {
503     ttt_token_t token;
504     ttt_token_type_t token_type;
505
506     _free_request (client);
507
508     while (1) {
509         token_type = yylex (client->scanner, &token);
510         /* Yes, EOF in two different enums is pretty ugly. */
511         if (token_type == TTT_TOKEN_TYPE_EOF)
512             return TTT_STATUS_EOF;
513
514         if (token_type == TTT_TOKEN_TYPE_NEWLINE) {
515             if (client->num_request_strings)
516                 return TTT_STATUS_SUCCESS;
517             else
518                 continue;
519         }
520
521         assert (token_type == TTT_TOKEN_TYPE_STRING);
522          
523         _append_to_request (client, token.u.string);
524         
525         free (token.u.string);
526     }
527 }
528
529 static ttt_error_t
530 _execute_request (ttt_client_t *client)
531 {
532     int i;
533
534     char *command = client->request_strings[0];
535     int num_args = client->num_request_strings-1;
536     ttt_command_description_t *desc;
537
538     for (i = 0; i < strlen (command); i++)
539         command[i] = toupper (command[i]);
540
541     for (i = 0; i < ARRAY_SIZE(command_descriptions); i++) {
542         desc = &command_descriptions[i];
543         if (strcmp (command, desc->command) == 0) {
544             if ((num_args < desc->args_min) || (num_args > desc->args_max))
545                 return TTT_ERROR_SYNTAX;
546             return (desc->execute) (client,
547                                     &client->request_strings[1],
548                                     num_args);
549         }
550     }
551
552     return TTT_ERROR_COMMAND;
553 }
554
555 static void *
556 _handle_requests_thread (void *closure)
557 {
558     ttt_status_t status;
559     ttt_error_t error;
560     ttt_client_t *client = closure;
561
562     while (1) {
563
564         status = _read_request (client);
565         if (status == TTT_STATUS_EOF)
566             break;
567         if (status)
568             ASSERT_NOT_REACHED;
569
570         error = _execute_request (client);
571         if (error == TTT_ERROR_QUIT_REQUESTED)
572             break;
573         if (error)
574             ttt_client_send (client, ttt_error_string (error));
575     }
576
577     _ttt_client_fini (client);
578     free (client);
579
580     return (void *) 0;
581 }
582
583 /* Exported: See ttt-client.h for documentation. */
584 void
585 ttt_client_new (void *closure, int client_socket)
586 {
587     ttt_server_t *server = closure;
588     ttt_client_t *client;
589     int err;
590
591     client = xmalloc (sizeof (ttt_client_t));
592     
593     _ttt_client_init (client, server, client_socket);
594
595     err = pthread_create (&client->thread, NULL,
596                           _handle_requests_thread, client);
597     if (err != 0) {
598         fprintf (stderr, "Error: pthread_create failed: %s. Aborting.\r\n",
599                  strerror (err));
600         exit (1);
601     }
602 }
603
604 /* Exported: See ttt-client.h for documentation. */
605 void
606 ttt_client_send (ttt_client_t *client, const char *message)
607 {
608     pthread_mutex_lock (&client->mutex);
609
610     ttt_socket_write (client->socket, message, strlen (message));
611
612     pthread_mutex_unlock (&client->mutex);
613 }
614
615 /* Exported: See ttt-client.h for documentation. */
616 const char*
617 ttt_client_get_username (ttt_client_t *client)
618 {
619     return client->username;
620 }
621
622 /* Exported: See ttt-client.h for documentation. */
623 void
624 ttt_client_set_username (ttt_client_t *client, const char *username)
625 {
626     free (client->username);
627     client->username = xstrdup (username);
628 }
629
630 /* Exported: See ttt-client.h for documentation. */
631 int
632 ttt_client_get_num_wins (ttt_client_t *client)
633 {
634     return client->num_wins;
635 }