1dd755f83651e3d7c6f80f176e0befa05f8af8dc
[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            *name;
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_message (ttt_client_t *client,
64                              char         **args,
65                              int          num_args);
66
67 static ttt_error_t
68 _ttt_client_execute_help (ttt_client_t  *client,
69                           char     **args,
70                           int      num_args);
71
72 static ttt_error_t
73 _ttt_client_execute_version (ttt_client_t  *client,
74                              char          **args,
75                              int           num_args);
76
77 static ttt_error_t
78 _ttt_client_execute_quit (ttt_client_t *client,
79                           char         **args,
80                           int          num_args);
81
82 typedef struct _ttt_command_description {
83     const char         *command;
84     int                args_min;
85     int                args_max;
86     ttt_command_func_t execute;
87     const char         *usage;
88     const char         *description;
89 } ttt_command_description_t;
90
91 ttt_command_description_t command_descriptions[] = {
92     {"HELO",    1, 1, _ttt_client_execute_helo,
93      "HELO <username>         ", "Register."},
94
95     {"HELP",    0, 1, _ttt_client_execute_help,
96      "HELP <command>          ", "Display help for a command."},
97
98     {"MESSAGE", 1, 1, _ttt_client_execute_message,
99      "MESSAGE <message>       ", "Send a message to everyone."},
100
101     {"QUIT",    0, 0, _ttt_client_execute_quit,
102      "QUIT                    ", "Quit session."},
103
104     {"VERSION", 1, 1, _ttt_client_execute_version,
105      "VERSION <client-version>", "Negotiate protocol version."},
106
107     {"WHO",     0, 0, _ttt_client_execute_who,
108      "WHO                     ", "List registered users."}
109 };
110
111 #define ARRAY_SIZE(arr) (sizeof(arr)/sizeof(arr[0]))
112
113 static ttt_error_t
114 _ttt_client_execute_helo (ttt_client_t *client,
115                           char         **args,
116                           int          num_args)
117 {
118     ttt_error_t error;
119     char *response;
120     char *notice;
121
122     assert (num_args == 1);
123
124     ttt_client_set_name (client, args[0]);
125
126     error = ttt_server_register_client (client->server, client);
127     if (error)
128         return error;
129     client->registered = TRUE;
130
131     xasprintf (&response, "HELO %s %s %s\r\n",
132                client->name,
133                ttt_server_get_host (client->server),
134                ttt_server_get_port (client->server));
135     ttt_client_send (client, response);
136
137     xasprintf (&notice, "NOTICE USER %s\r\n",
138                client->name);
139     ttt_server_broadcast (client->server, notice);
140
141     free (notice);
142     free (response);
143
144     return TTT_ERROR_NONE;
145 }
146
147 static ttt_error_t
148 _ttt_client_execute_who (ttt_client_t *client,
149                          char         **args,
150                          int          num_args)
151 {
152     char *response;
153
154     assert (num_args == 0);
155
156     if (!client->registered)
157         return TTT_ERROR_NO_NAME_SET;
158
159     response = xstrdup (ttt_server_who (client->server));
160     ttt_client_send (client, response);
161
162     free (response);
163
164     return TTT_ERROR_NONE;
165 }
166
167 static ttt_error_t
168 _ttt_client_execute_message (ttt_client_t  *client,
169                              char          **args,
170                              int           num_args)
171 {
172     char *response;
173     char *notice;
174
175     assert (num_args == 1);
176
177     if (!client->registered)
178         return TTT_ERROR_NO_NAME_SET;
179
180     xasprintf (&response, "MESSAGE\r\n");
181     ttt_client_send (client, response);
182
183     xasprintf (&notice, "NOTICE MESSAGE %s \"%s\"\r\n",
184               client->name,
185               args[0]);
186     ttt_server_broadcast (client->server, notice);
187
188     free (notice);
189     free (response);
190
191     return TTT_ERROR_NONE;
192 }
193
194 static ttt_error_t
195 _ttt_client_execute_help (ttt_client_t  *client,
196                           char     **args,
197                           int      num_args)
198 {
199     char *response;
200     char *command;
201     ttt_command_description_t *desc;
202     int i;
203     ttt_bool_t is_command = FALSE;
204     
205     if (num_args == 0) {
206         xasprintf (&response, "HELP \"\r\n"
207                    "Available Commands:\r\n");
208         for (i = 0; i < ARRAY_SIZE(command_descriptions); i++) {
209             desc = &command_descriptions[i];
210             xasprintf (&response, "%s\r\n  %s - %s\r\n",
211                        response,
212                        desc->usage,
213                        desc->description);
214         }
215         xasprintf (&response, "%s\"\r\n", response);
216     } else {
217         command = args[0];
218         for (i = 0; i < strlen (command); i++)
219             command[i] = toupper (command[i]);
220         for (i = 0; i < ARRAY_SIZE(command_descriptions); i++) {
221             desc = &command_descriptions[i];
222             if (strcmp (desc->command, command) == 0) {
223                 is_command = TRUE;
224                 xasprintf (&response, "HELP %s \"\r\n"
225                            "%s\r\n"
226                            "\r\n"
227                            "Usage:\r\n"
228                            "  %s\r\n"
229                            "\"\r\n",
230                            desc->command,
231                            desc->description,
232                            desc->usage);
233                 /* XXX: Add detailed help. */
234             }
235         }
236     }
237     
238     if ((num_args == 1) && (!is_command))
239         return TTT_ERROR_SYNTAX;
240
241     ttt_client_send (client, response);
242
243     free (response);
244     return TTT_ERROR_NONE;
245 }
246
247 static ttt_error_t
248 _ttt_client_execute_version (ttt_client_t  *client,
249                              char          **args,
250                              int           num_args)
251 {
252     char *response;
253
254     assert (num_args == 1);
255
256     /* XXX: Argument is being ignored.
257        This is not completely implemented. */
258
259     xasprintf (&response, "VERSION 1\r\n");
260     ttt_client_send (client, response);
261
262     free (response);
263     return TTT_ERROR_NONE;
264 }
265
266 static ttt_error_t
267 _ttt_client_execute_quit (ttt_client_t *client,
268                           char         **args,
269                           int          num_args)
270 {
271     char *notice;
272
273     assert (num_args == 0);
274
275     if (!client->registered)
276         return TTT_ERROR_QUIT_REQUESTED;
277     
278     xasprintf (&notice, "NOTICE QUIT %s\r\n",
279                client->name);
280     ttt_server_broadcast (client->server, notice);
281     
282     free (notice);
283
284     return TTT_ERROR_QUIT_REQUESTED;
285 }
286
287 static void
288 _free_request (ttt_client_t *client);
289
290 static void
291 _ttt_client_init (ttt_client_t  *client,
292                   ttt_server_t  *server,
293                   int            socket)
294 {
295     FILE *file;
296
297     pthread_mutex_init (&client->mutex, NULL);
298
299     client->server = server;
300     client->socket = socket;
301
302     file = xfdopen (socket, "r");
303     yylex_init (&client->scanner);
304     yyset_in (file, client->scanner);
305
306     client->request_strings = NULL;
307     client->num_request_strings = 0;
308
309     client->name = NULL;
310     client->registered = FALSE;
311     client->num_wins = 0;
312 }
313
314 static void
315 _ttt_client_fini (ttt_client_t *client)
316 {
317     pthread_mutex_lock (&client->mutex);
318
319     if (client->registered)
320         ttt_server_unregister_client (client->server, client);
321
322     free (client->name);
323     client->name = NULL;
324
325     yylex_destroy (client->scanner);
326     shutdown (client->socket, SHUT_RDWR);
327
328     _free_request (client);
329
330     pthread_mutex_unlock (&client->mutex);
331
332     pthread_mutex_destroy (&client->mutex);
333 }
334
335 /* XXX: The memory management for the request strings is pretty cheesy. */
336 static void
337 _append_to_request (ttt_client_t        *client,
338                     const char          *string)
339 {
340     client->num_request_strings++;
341     client->request_strings =
342         xrealloc (client->request_strings,
343                   client->num_request_strings * sizeof (char *));
344
345     client->request_strings[client->num_request_strings - 1] = xstrdup (string);
346 }
347
348 static void
349 _free_request (ttt_client_t *client)
350 {
351     int i;
352
353     for (i = 0; i < client->num_request_strings; i++)
354         free (client->request_strings[i]);
355
356     free (client->request_strings);
357
358     client->request_strings = NULL;
359     client->num_request_strings = 0;
360 }
361
362 static ttt_status_t
363 _read_request (ttt_client_t *client)
364 {
365     ttt_token_t token;
366     ttt_token_type_t token_type;
367
368     _free_request (client);
369
370     while (1) {
371         token_type = yylex (client->scanner, &token);
372         /* Yes, EOF in two different enums is pretty ugly. */
373         if (token_type == TTT_TOKEN_TYPE_EOF)
374             return TTT_STATUS_EOF;
375
376         if (token_type == TTT_TOKEN_TYPE_NEWLINE) {
377             if (client->num_request_strings)
378                 return TTT_STATUS_SUCCESS;
379             else
380                 continue;
381         }
382
383         assert (token_type == TTT_TOKEN_TYPE_STRING);
384          
385         _append_to_request (client, token.u.string);
386         
387         free (token.u.string);
388     }
389 }
390
391 static ttt_error_t
392 _execute_request (ttt_client_t *client)
393 {
394     int i;
395
396     char *command = client->request_strings[0];
397     int num_args = client->num_request_strings-1;
398     ttt_command_description_t *desc;
399
400     for (i = 0; i < strlen (command); i++)
401         command[i] = toupper (command[i]);
402
403     for (i = 0; i < ARRAY_SIZE(command_descriptions); i++) {
404         desc = &command_descriptions[i];
405         if (strcmp (command, desc->command) == 0) {
406             if ((num_args < desc->args_min) || (num_args > desc->args_max))
407                 return TTT_ERROR_SYNTAX;
408             return (desc->execute) (client,
409                                     &client->request_strings[1],
410                                     num_args);
411         }
412     }
413
414     return TTT_ERROR_COMMAND;
415 }
416
417 static void *
418 _handle_requests_thread (void *closure)
419 {
420     ttt_status_t status;
421     ttt_error_t error;
422     ttt_client_t *client = closure;
423
424     while (1) {
425
426         status = _read_request (client);
427         if (status == TTT_STATUS_EOF)
428             break;
429         if (status)
430             ASSERT_NOT_REACHED;
431
432         error = _execute_request (client);
433         if (error == TTT_ERROR_QUIT_REQUESTED)
434             break;
435         if (error)
436             ttt_client_send (client, ttt_error_string (error));
437     }
438
439     _ttt_client_fini (client);
440     free (client);
441
442     return (void *) 0;
443 }
444
445 /* Exported: See ttt-client.h for documentation. */
446 void
447 ttt_client_new (void *closure, int client_socket)
448 {
449     ttt_server_t *server = closure;
450     ttt_client_t *client;
451     int err;
452
453     client = xmalloc (sizeof (ttt_client_t));
454     
455     _ttt_client_init (client, server, client_socket);
456
457     err = pthread_create (&client->thread, NULL,
458                           _handle_requests_thread, client);
459     if (err != 0) {
460         fprintf (stderr, "Error: pthread_create failed: %s. Aborting.\r\n",
461                  strerror (err));
462         exit (1);
463     }
464 }
465
466 /* Exported: See ttt-client.h for documentation. */
467 void
468 ttt_client_send (ttt_client_t *client, const char *message)
469 {
470     pthread_mutex_lock (&client->mutex);
471
472     ttt_socket_write (client->socket, message, strlen (message));
473
474     pthread_mutex_unlock (&client->mutex);
475 }
476
477 /* Exported: See ttt-client.h for documentation. */
478 const char*
479 ttt_client_get_name (ttt_client_t *client)
480 {
481     return client->name;
482 }
483
484 /* Exported: See ttt-client.h for documentation. */
485 void
486 ttt_client_set_name (ttt_client_t *client, const char *name)
487 {
488     free (client->name);
489     client->name = xstrdup (name);
490 }
491
492 /* Exported: See ttt-client.h for documentation. */
493 int
494 ttt_client_get_num_wins (ttt_client_t *client)
495 {
496     return client->num_wins;
497 }