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