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