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