1 /* $Header: /home/jcb/MahJong/newmj/RCS/protocol.c,v 12.0 2009/06/28 20:43:13 jcb Rel $
2 * protocol.c
3 * implements the over-the-wire protocol for messages
4 */
5 /****************** COPYRIGHT STATEMENT **********************
6 * This file is Copyright (c) 2000 by J. C. Bradfield. *
7 * Distribution and use is governed by the LICENCE file that *
8 * accompanies this file. *
9 * The moral rights of the author are asserted. *
10 * *
11 ***************** DISCLAIMER OF WARRANTY ********************
12 * This code is not warranted fit for any purpose. See the *
13 * LICENCE file for further information. *
14 * *
15 *************************************************************/
16
17 static const char rcs_id[] = "$Header: /home/jcb/MahJong/newmj/RCS/protocol.c,v 12.0 2009/06/28 20:43:13 jcb Rel $";
18
19 #include "protocol.h"
20 #include "tiles.h"
21 #include "sysdep.h"
22
23 /* See protocol.h for documentation */
24 int protocol_version = PROTOCOL_VERSION;
25
26 /* The protocol is, for all the usual reasons, text-based,
27 and human readable. (So all you need to play is telnet...)
28 A message usually occupies a single line, terminated by CRLF or LF.
29 To transmit messages containing newlines, the internal newlines
30 are replaced by backslash-newline pairs; thus a telnet line ending
31 in a backslash indicates newline followed by the next telnet line.
32 Integers are represented in decimal.
33 The message type is given in text, obtained by removing the CMsg
34 or PMsg prefix from the type name.
35 Tiles are represented as a 2-character code, as follows:
36 1C ... 9C characters
37 1D ... 9D circles (dots)
38 1B ... 9B bamboos
39 EW ... NW winds
40 RD, WD, GD dragons
41 1S ... 4S seasons
42 1F ... 4F flowers
43 -- blank
44 Winds are represented as single letters.
45 The code for representing tiles in this way comes from the tiles
46 module, as it has uses outside this protocol.
47 ChowPositions are represented as the words Lower, Middle, Upper,AnyPos.
48 Strings are deemed to extend to end of line. This is safe because
49 the current protocol has at most one string field per message, which
50 is always final. There is unlikely to be a reason to change this
51 assumption.
52 */
53
54 /* Takes a TileWind and returns the corresponding letter */
windletter(TileWind w)55 static char windletter(TileWind w) {
56 switch ( w ) {
57 case EastWind: return 'E';
58 case SouthWind: return 'S';
59 case WestWind: return 'W';
60 case NorthWind: return 'N';
61 default: return '?';
62 }
63 }
64
65 /* given a wind letter, return the wind */
letterwind(char c)66 static TileWind letterwind(char c) {
67 if ( c == 'E' ) return EastWind;
68 if ( c == 'S' ) return SouthWind;
69 if ( c == 'W' ) return WestWind;
70 if ( c == 'N' ) return NorthWind;
71 return UnknownWind;
72 }
73
74 /* given a chowposition string, return the chowposition */
75 /* it is an undocumented :-) fact that this function will
76 accept l,m,u,a for Lower, Upper, Middle, AnyPos */
string_cpos(char * s)77 static ChowPosition string_cpos(char *s) {
78 if ( strcmp(s,"Lower") == 0 ) return Lower;
79 if ( strcmp(s,"l") == 0 ) return Lower;
80 if ( strcmp(s,"Middle") == 0 ) return Middle;
81 if ( strcmp(s,"m") == 0 ) return Middle;
82 if ( strcmp(s,"Upper") == 0 ) return Upper;
83 if ( strcmp(s,"u") == 0 ) return Upper;
84 if ( strcmp(s,"AnyPos") == 0 ) return AnyPos;
85 if ( strcmp(s,"a") == 0 ) return AnyPos;
86 return -1;
87 }
88
89
90 /* return a string code for a chowposition */
cpos_string(ChowPosition r)91 static char *cpos_string(ChowPosition r) {
92 switch ( r ) {
93 case Lower: return "Lower";
94 case Middle: return "Middle";
95 case Upper: return "Upper";
96 case AnyPos: return "AnyPos";
97 }
98 return "error";
99 }
100
101 /* functions to print and scan a GameOptionEntry; at the end */
102
103 static char *protocol_print_GameOptionEntry(const GameOptionEntry *t);
104 /* This function uses static storage; it's not reentrant */
105 static GameOptionEntry* protocol_scan_GameOptionEntry(const char *s);
106
107 /* This function takes a pointer to a controller message,
108 and returns a string (including terminating CRLF) encoding it.
109 The returned string is in static storage, and will be overwritten
110 the next time this function is called.
111 */
112
encode_cmsg(CMsgMsg * msg)113 char *encode_cmsg(CMsgMsg *msg) {
114 static char *buf = NULL;
115 static size_t buf_size = 0;
116 int badfield; /* used by internal code */
117
118 switch ( msg->type ) {
119 /* The body of this switch statement is automatically generated
120 from protocol.h by proto-enc-cmsg.pl; */
121 # include "enc_cmsg.c"
122 default:
123 warn("unknown message type %d\n",msg->type);
124 return (char *)0;
125 }
126 return buf;
127 }
128
129 /* This function takes a string, which is expected to be a complete
130 line (including terminators, although their absence is ignored).
131 It returns a pointer to a message structure.
132 Both the message structure and any strings it contains are malloc'd.
133 The argument string will be destroyed by this function.
134 */
135
decode_cmsg(char * arg)136 CMsgMsg *decode_cmsg(char *arg) {
137 char *s;
138 char *type;
139 int n;
140 int i;
141 int an_int;
142 char a_char;
143 char little_string[32];
144 GameOptionEntry *goe;
145
146 /* first eliminate the white space from the end of the string */
147 i=strlen(arg) - 1;
148 while ( i >= 0 && isspace(arg[i]) ) arg[i--] = '\000' ;
149 /* first get the type of the message, which is the first word */
150 /* SPECIAL hack: the comment message has a non alpha num type */
151 type = arg;
152 i = 0;
153 while ( isalnum(arg[i]) || (arg[i] == '#') ) i++;
154 arg[i++] = '\000' ; /* if this wasn't white space, summat's wrong anyway */
155 type = arg;
156 while ( isspace(arg[i]) ) i++; /* may as well advance to next item */
157 s = arg+i; /* s is what the rest of the code uses */
158 /* The rest of this function body is generated automatically from
159 protocol.h by proto-dec-cmsg.pl */
160 # include "dec_cmsg.c"
161 /* if we come out of this, it's an error because we haven't found
162 the keyword */
163 warn("decode_cmsg: unknown key in %s",arg);
164 return NULL;
165 }
166
167 /* The next two functions do the same for pmsgs */
168
169 /* This function takes a pointer to a player message,
170 and returns a string (including terminating CRLF) encoding it.
171 The returned string is in static storage, and will be overwritten
172 the next time this function is called.
173 */
174
encode_pmsg(PMsgMsg * msg)175 char *encode_pmsg(PMsgMsg *msg) {
176 static char *buf = NULL;
177 static size_t buf_size = 0;
178 int badfield; /* used by internal code */
179
180 switch ( msg->type ) {
181 /* The body of this switch statement is automatically generated
182 from protocol.h by proto-enc-pmsg.pl; */
183 # include "enc_pmsg.c"
184 default:
185 warn("unknown message type\n");
186 return (char *)0;
187 }
188 return buf;
189 }
190
191 /* This function takes a string, which is expected to be a complete
192 line (including terminators, although their absence is ignored).
193 It returns a pointer to a message structure.
194 Both the message structure and any strings it contains are malloc'd.
195 The argument string will be destroyed by this function.
196 */
197
decode_pmsg(char * arg)198 PMsgMsg *decode_pmsg(char *arg) {
199 char *s;
200 char *type;
201 int n;
202 int i;
203 int an_int;
204 char little_string[32];
205
206 /* first eliminate the white space from the end of the string */
207 i=strlen(arg)-1;
208 while ( i >= 0 && isspace(arg[i]) ) arg[i--] = '\000' ;
209 /* first get the type of the message, which is the first word */
210 type = arg;
211 i = 0;
212 while ( isalnum(arg[i]) ) i++;
213 arg[i++] = '\000' ; /* if this wasn't white space, summat's wrong anyway */
214 type = arg;
215 while ( isspace(arg[i]) ) i++; /* may as well advance to next item */
216 s = arg+i; /* s is what the rest of the code uses */
217 /* The rest of this function body is generated automatically from
218 protocol.h by proto-dec-pmsg.pl */
219 # include "dec_pmsg.c"
220 /* if we come out of this, it's an error because we haven't found
221 the keyword */
222 warn("decode_pmsg: unknown key in %s",arg);
223 return NULL;
224 }
225
226 /* the size functions */
227 #include "cmsg_size.c"
228 #include "pmsg_size.c"
229
230 /* enum functions */
231 #include "protocol-enums.c"
232
233 /* functions for GameOptionEntry */
protocol_scan_GameOptionEntry(const char * s)234 static GameOptionEntry *protocol_scan_GameOptionEntry(const char *s) {
235 const char *sp;
236 char tmp[100];
237 static GameOptionEntry goe;
238 int n;
239 sp = s;
240
241 if ( sscanf(sp,"%15s%n",goe.name,&n) == 0 ) {
242 warn("protocol error");
243 return NULL;
244 }
245 sp += n;
246
247 goe.option = protocol_scan_GameOption(goe.name);
248 if ( goe.option == (GameOption)(-1) ) goe.option = GOUnknown;
249
250 if ( sscanf(sp,"%15s%n",tmp,&n) == 0 ) {
251 warn("protocol error");
252 return NULL;
253 }
254 sp += n;
255
256 goe.type = protocol_scan_GameOptionType(tmp);
257 if ( goe.type == (GameOptionType)(-1) ) {
258 warn("unknown game option type");
259 return NULL;
260 }
261
262 if ( sscanf(sp,"%d%n",&goe.protversion,&n) == 0 ) {
263 warn("protocol error");
264 return NULL;
265 }
266 sp += n;
267
268 if ( sscanf(sp,"%d%n",&goe.enabled,&n) == 0 ) {
269 warn("protocol error");
270 return NULL;
271 }
272 sp += n;
273
274 switch ( goe.type ) {
275 case GOTBool:
276 if ( sscanf(sp,"%d%n",&goe.value.optbool,&n) == 0 ) {
277 warn("protocol error");
278 return NULL;
279 }
280 if ( goe.value.optbool != 0 && goe.value.optbool != 1 ) {
281 warn("bad value for boolean game option");
282 return NULL;
283 }
284 sp += n;
285 break;
286 case GOTNat:
287 case GOTInt:
288 case GOTScore:
289 if ( sscanf(sp,"%d%n",&goe.value.optint,&n) == 0 ) {
290 warn("protocol error");
291 return NULL;
292 }
293 sp += n;
294 break;
295 case GOTString:
296 if ( sscanf(sp,"%127s%n",goe.value.optstring,&n) == 0 ) {
297 warn("protocol error");
298 return NULL;
299 }
300 sp += n;
301 break;
302 }
303 if ( sscanf(sp," %127[^\r\n]",goe.desc) == 0 ) {
304 warn("protocol error");
305 return NULL;
306 }
307 goe.userdata = NULL;
308 return &goe;
309 }
310
protocol_print_GameOptionEntry(const GameOptionEntry * goe)311 static char *protocol_print_GameOptionEntry(const GameOptionEntry *goe) {
312 static char tmp[2*sizeof(GameOptionEntry)];
313 sprintf(tmp,"%s %s %d %d ",
314 goe->name,
315 protocol_print_GameOptionType(goe->type),
316 goe->protversion,
317 goe->enabled);
318 switch (goe->type) {
319 case GOTBool:
320 sprintf(tmp+strlen(tmp),"%d ",goe->value.optbool);
321 break;
322 case GOTNat:
323 case GOTInt:
324 case GOTScore:
325 sprintf(tmp+strlen(tmp),"%d ",goe->value.optint);
326 break;
327 case GOTString:
328 sprintf(tmp+strlen(tmp),"%s ",goe->value.optstring);
329 break;
330 }
331 strcat(tmp,goe->desc);
332 return tmp;
333 }
334
335
336 /* basic_expand_protocol_text: do the expansion of protocol text
337 according to the rules set out in protocol.h. We don't recognize
338 any codes or types. bloody emacs ' */
basic_expand_protocol_text(char * dest,const char * src,int n)339 int basic_expand_protocol_text(char *dest,const char *src,int n)
340 {
341 int destlen = 0; /* chars placed in destination string */
342 const char *argp[10]; /* pointers to start of argument texts (excluding {);
343 arg 0 is replacement text */
344 const char *curarg = NULL;
345 int i,j;
346 int numargs = 0;
347 int state= 0;
348 int done;
349
350 /* sanity check */
351 if ( !dest || !n ) {
352 warn("basic_expand_protocol_text: strange args");
353 return -1;
354 }
355
356 /* null source expands to empty string */
357 if ( !src ) {
358 dest[0] = 0;
359 return 0;
360 }
361
362 /* initialization */
363 for ( i=0 ; i<10 ; argp[i++] = NULL );
364 j = 0; /* current arg */
365
366 done = 0;
367 while ( !done ) {
368 switch ( state ) {
369 case 0: /* outside macro */
370 if ( ! *src ) { done=1 ; break ; }
371 if ( *src == '\\' ) {
372 src++;
373 if ( *src ) dest[destlen++] = *(src++);
374 } else if ( *src == '{' ) {
375 src++;
376 state = 1; /* reading macro defn */
377 /* skip the code */
378 while ( *src
379 && (*src != '#') && (*src != ':') && (*src != '}') ) src++;
380 numargs = 0;
381 }
382 else { dest[destlen++] = *(src++); }
383 break;
384 case 1: /* reading macro definition */
385 if ( ! *src ) { done=1; break; }
386 if ( *src == '#' ) {
387 numargs++;
388 src++;
389 /* skip the type */
390 while ( *src
391 && (*src != '#') && (*src != ':') && (*src != '}') ) src++;
392 } else if ( *src == ':' ) {
393 state = 2;
394 src++;
395 argp[0] = src;
396 } else if ( *src == '}' ) {
397 state = 2;
398 /* and read it again */
399 }
400 break;
401 case 2: /* reading replacement text */
402 /* do nothing; we've stored a pointer to it */
403 if ( !*src ) { done=1; break;}
404 if ( *src == '\\' ) {
405 src++;
406 if ( *src ) src++;
407 } else if ( *src == '}' ) {
408 src++;
409 /* are there any arguments? */
410 if ( numargs > 0 ) {
411 state = 3; /* reading argument texts */
412 j = 1; /* current arg */
413 } else {
414 state = 4; /* expanding macro */
415 }
416 } else src++;
417 break;
418 case 3: /* reading argument texts */
419 if ( !*src ) { done=1; break;}
420 if ( *src == '{' ) {
421 src++;
422 argp[j] = src;
423 } else if ( *src == '}' ) {
424 src++;
425 if ( j == numargs ) {
426 state = 4; /* expand the macro */
427 } else j++;
428 } else src++;
429 break;
430 case 4: /* expand the macro */
431 /* now we're looking at argp[0] */
432 if ( ! *argp[0] ) { state = 0; }
433 else if ( *argp[0] == '\\' ) {
434 argp[0]++;
435 if ( *argp[0] ) dest[destlen++] = *argp[0];
436 argp[0]++;
437 } else if ( *argp[0] == '}' ) {
438 state = 0;
439 } else if ( *argp[0] == '#' ) {
440 /* expand the argument */
441 argp[0]++;
442 j = *argp[0] - '0';
443 if ( j >= 1 && j <= 9 ) {
444 argp[0]++;
445 curarg = argp[j];
446 state = 5;
447 }
448 } else dest[destlen++] = *(argp[0]++);
449 break;
450 case 5: /* copying an argument */
451 if ( ! *curarg ) { state = 4; }
452 else if ( *curarg == '\\' ) {
453 curarg++;
454 if ( *curarg ) { dest[destlen++] = *(curarg++); }
455 } else if ( *curarg == '}' ) {
456 state = 4;
457 } else dest[destlen++] = *(curarg++);
458 break;
459 }
460 if ( destlen >= n - 1 ) { break ; }
461 }
462 if ( destlen > n-1 ) {
463 dest[n-1] = '\000';
464 return -1;
465 }
466 dest[destlen] = '\000';
467 if ( state > 0 ) {
468 warn("basic_expand_protocol_text: some error in expansion");
469 return 0;}
470 return 1;
471 }
472
expand_protocol_text(char * dest,const char * src,int n)473 int expand_protocol_text(char *dest,const char *src,int n)
474 {
475 return basic_expand_protocol_text(dest,src,n);
476 }
477