1 /*
2 * src/gmp/engine.c, part of Complete Goban (game program)
3 * Copyright (C) 1995-1997 William Shubert.
4 * See "configure.h.in" for more copyright information.
5 */
6
7 /*
8 * This is based on David Fotland's go modem protocol code. I've pretty much
9 * completely rewritten it though.
10 */
11
12 #include <wms.h>
13 #include <but/but.h>
14 #include <but/timer.h>
15 #include <wms/str.h>
16 #include "../cgoban.h"
17 #include "../goBoard.h"
18 #include "../msg.h"
19 #ifdef _GMP_ENGINE_H_
20 #error Levelization Error.
21 #endif
22 #include "engine.h"
23
24
25 /**********************************************************************
26 * Constants
27 **********************************************************************/
28
29 #define GMP_NUMQUERIESTOASK 4
30
31 #define GMP_TIMEOUTSECS 60
32
33
34 /**********************************************************************
35 * Data types
36 **********************************************************************/
37 typedef enum {
38 cmd_ack, cmd_deny, cmd_reset, cmd_query, cmd_respond, cmd_move,
39 cmd_undo
40 } Command;
41
42
43 typedef enum {
44 query_game, query_bufSize, query_protocol, query_stones,
45 query_bTime, query_wTime, query_charSet, query_rules, query_handicap,
46 query_boardSize, query_timeLimit, query_color, query_who
47 } Query;
48
49
50 /**********************************************************************
51 * Globals
52 **********************************************************************/
53 #if DEBUG
54 static const char *cmdNames[] = {
55 "ACK", "DENY", "RESET", "QUERY", "RESPOND", "MOVE", "UNDO"};
56
57 static bool showTransfers = TRUE;
58 #endif
59
60
61 /**********************************************************************
62 * Forward Declarations
63 **********************************************************************/
64 static ButOut dataReadyForRecv(void *packet, int fd);
65 static ButOut getPacket(GmpEngine *ge);
66 static unsigned char checksum(unsigned char p[4]);
67 static ButOut parsePacket(GmpEngine *ge);
68 static ButOut processCommand(GmpEngine *ge, Command command, int val);
69 static ButOut gotQueryResponse(GmpEngine *ge, int val);
70 static ButOut putCommand(GmpEngine *ge, Command cmd, int val);
71 static ButOut heartbeat(ButTimer *timer);
72 static void respond(GmpEngine *ge, Query query);
73 static void askQuery(GmpEngine *ge);
74 static void processQ(GmpEngine *ge);
75 static char **makeArgv(const char *cmdLine);
76
77
78 /**********************************************************************
79 * Functions
80 **********************************************************************/
81
gmpEngine_init(Cgoban * cg,GmpEngine * ge,int inFile,int outFile,const GmpActions * actions,void * packet)82 GmpEngine *gmpEngine_init(Cgoban *cg, GmpEngine *ge,
83 int inFile, int outFile,
84 const GmpActions *actions, void *packet) {
85 struct timeval oneSecond;
86
87 MAGIC_SET(ge);
88 ge->stopped = FALSE;
89 ge->inFile = inFile;
90 ge->outFile = outFile;
91 ge->boardSize = 0;
92 ge->handicap = 0;
93 ge->komi = 0.0;
94 ge->chineseRules = TRUE;
95 ge->iAmWhite = 0;
96 ge->queriesAcked = 0;
97 ge->lastQuerySent = 0;
98
99 ge->recvSoFar = 0;
100 ge->sendsQueued = 0;
101 ge->sendFailures = 0;
102 ge->waitingHighAck = FALSE;
103 ge->lastSendTime = 0;
104 ge->myLastSeq = 0;
105 ge->hisLastSeq = 0;
106
107 ge->cg = cg;
108 ge->env = cg->env;
109 ge->packet = packet;
110 ge->actions = actions;
111
112 butEnv_addFile(cg->env, BUT_READFILE, inFile, ge, dataReadyForRecv);
113 oneSecond.tv_usec = 0;
114 oneSecond.tv_sec = 1;
115 ge->heartbeat = butTimer_create(ge, NULL, oneSecond, oneSecond,
116 FALSE, heartbeat);
117 return(ge);
118 }
119
120
gmpEngine_deinit(GmpEngine * ge)121 GmpEngine *gmpEngine_deinit(GmpEngine *ge) {
122 assert(MAGIC(ge));
123 butEnv_rmFile(ge->env, BUT_READFILE, ge->inFile);
124 butTimer_destroy(ge->heartbeat);
125 MAGIC_UNSET(ge);
126 return(ge);
127 }
128
129
dataReadyForRecv(void * packet,int fd)130 static ButOut dataReadyForRecv(void *packet, int fd) {
131 GmpEngine *ge = packet;
132
133 assert(MAGIC(ge));
134 assert(fd == ge->inFile);
135 return(getPacket(ge));
136 }
137
138
getPacket(GmpEngine * ge)139 static ButOut getPacket(GmpEngine *ge) {
140 unsigned char charsIn[4], c;
141 int count = 0, cNum;
142 Str errDesc;
143 ButOut result = 0;
144
145 count = read(ge->inFile, charsIn, 4 - ge->recvSoFar);
146 if (ge->stopped)
147 return(0);
148 if (count < 0) {
149 str_init(&errDesc);
150 str_print(&errDesc, msg_gmpDead,
151 strerror(errno));
152 ge->actions->errorRecvd(ge, ge->packet, str_chars(&errDesc));
153 str_deinit(&errDesc);
154 return(BUTOUT_ERR);
155 } else if (count == 0)
156 return(0);
157
158 for (cNum = 0; cNum < count; ++cNum) {
159 c = charsIn[cNum];
160 if (!ge->recvSoFar) {
161 /* idle, looking for start of packet */
162 if ((c & 0xfc) == 0) { /* start of packet */
163 ge->recvData[0] = c;
164 ge->recvSoFar = 1;
165 }
166 } else {
167 /* We're in the packet. */
168 if ((c & 0x80) == 0) { /* error */
169 ge->recvSoFar = 0;
170 if ((c & 0xfc) == 0) {
171 ge->recvData[ge->recvSoFar++] = c;
172 }
173 } else {
174 /* A valid character for in a packet. */
175 ge->recvData[ge->recvSoFar++] = c;
176 if (ge->recvSoFar == 4) { /* check for extra bytes */
177 assert(cNum + 1 == count);
178 if (checksum(ge->recvData) == ge->recvData[1])
179 result = parsePacket(ge);
180 ge->recvSoFar = 0;
181 }
182 }
183 }
184 }
185 return(result);
186 }
187
188
checksum(unsigned char p[4])189 static unsigned char checksum(unsigned char p[4]) {
190 unsigned char sum;
191 sum = p[0] + p[2] + p[3];
192 sum |= 0x80; /* set sign bit */
193 return(sum);
194 }
195
196
parsePacket(GmpEngine * ge)197 static ButOut parsePacket(GmpEngine *ge) {
198 int seq, ack, val;
199 Command command;
200 ButOut result = 0;
201
202 seq = ge->recvData[0] & 1;
203 ack = (ge->recvData[0] & 2) >> 1;
204 if (ge->recvData[2] & 0x08) /* Not-understood command. */
205 return(0);
206 command = (ge->recvData[2] >> 4) & 7;
207 #if DEBUG
208 if (showTransfers) {
209 fprintf(stderr, "GMP: Command %s received from port %d:%d.\n",
210 cmdNames[command], ge->inFile, ge->outFile);
211 }
212 #endif
213 if ((command != cmd_deny) && (command != cmd_reset) &&
214 !ge->iAmWhite && !ge->gameStarted) {
215 ge->gameStarted = TRUE;
216 if (ge->actions->newGame != NULL)
217 ge->actions->newGame(ge, ge->packet, ge->boardSize, ge->handicap,
218 ge->komi, ge->chineseRules, ge->iAmWhite);
219 }
220 val = ((ge->recvData[2] & 7) << 7) | (ge->recvData[3] & 0x7f);
221 if (!ge->waitingHighAck) {
222 if ((command == cmd_ack) || /* An ack. We don't need an ack now. */
223 (ack != ge->myLastSeq)) { /* He missed my last message. */
224 #if DEBUG
225 if (showTransfers) {
226 fprintf(stderr, "GMP: ACK does not match myLastSeq.\n");
227 }
228 #endif
229 return(0);
230 } else if (seq == ge->hisLastSeq) { /* Seen this one before. */
231 #if DEBUG
232 if (showTransfers) {
233 fprintf(stderr, "GMP: Repeated packet.\n");
234 }
235 #endif
236 putCommand(ge, cmd_ack, ~0);
237 } else {
238 ge->hisLastSeq = seq;
239 ge->sendFailures = 0;
240 result = processCommand(ge, command, val);
241 }
242 } else {
243 /* Waiting for OK. */
244 if (command == cmd_ack) {
245 if ((ack != ge->myLastSeq) || (seq != ge->hisLastSeq)) {
246 /* Sequence error. */
247 return(result);
248 }
249 ge->sendFailures = 0;
250 ge->waitingHighAck = FALSE;
251 processQ(ge);
252 } else if (seq == ge->hisLastSeq) {
253 /* His command is old. */
254 } else if (ack == ge->myLastSeq) {
255 ge->sendFailures = 0;
256 ge->waitingHighAck = FALSE;
257 ge->hisLastSeq = seq;
258 result = processCommand(ge, command, val);
259 processQ(ge);
260 } else {
261 /* Conflict with opponent. */
262 #if DEBUG
263 if (showTransfers) {
264 fprintf(stderr, "GMP: Received a packet, expected an ACK.\n");
265 }
266 #endif
267 /*
268 * This code seems wrong.
269 *
270 * ge->myLastSeq = 1 - ge->myLastSeq;
271 * ge->waitingHighAck = FALSE;
272 * processQ(ge);
273 */
274 }
275 }
276 return(result);
277 }
278
279
processCommand(GmpEngine * ge,Command command,int val)280 static ButOut processCommand(GmpEngine *ge, Command command, int val) {
281 int s, x, y;
282 ButOut result = 0;
283
284 switch(command) {
285 case cmd_deny:
286 return(putCommand(ge, cmd_ack, ~0));
287 break;
288 case cmd_query:
289 respond(ge, val);
290 break;
291 case cmd_reset: /* New game. */
292 ge->queriesAcked = 0;
293 askQuery(ge);
294 break;
295 case cmd_undo: /* Take back moves. */
296 result = putCommand(ge, cmd_ack, ~0);
297 assert(ge->actions->undoMoves != NULL);
298 result |= ge->actions->undoMoves(ge, ge->packet, val);
299 return(result);
300 break;
301 case cmd_move:
302 s = val & 0x1ff;
303 if (s == 0) {
304 x = -1;
305 y = 0;
306 } else {
307 --s;
308 x = (s % ge->boardSize);
309 y = ge->boardSize - 1 - (s / ge->boardSize);
310 }
311 result = putCommand(ge, cmd_ack, ~0);
312 if (val & 0x200)
313 result |= ge->actions->moveRecvd(ge, ge->packet, goStone_white, x, y);
314 else
315 result |= ge->actions->moveRecvd(ge, ge->packet, goStone_black, x, y);
316 return(result);
317 break;
318 case cmd_respond:
319 return(gotQueryResponse(ge, val));
320 break;
321 default: /* Don't understand command. */
322 return(putCommand(ge, cmd_deny, 0));
323 break;
324 }
325 return(0);
326 }
327
328
putCommand(GmpEngine * ge,Command cmd,int val)329 static ButOut putCommand(GmpEngine *ge, Command cmd, int val) {
330 int writeResult;
331
332 if (ge->waitingHighAck &&
333 (cmd != cmd_ack) && (cmd != cmd_respond) && (cmd != cmd_deny)) {
334 if (ge->sendsQueued < GMP_SENDBUFSIZE) {
335 ge->sendsPending[ge->sendsQueued].cmd = cmd;
336 ge->sendsPending[ge->sendsQueued].val = val;
337 ++ge->sendsQueued;
338 } else {
339 return(ge->actions->errorRecvd(ge, ge->packet,
340 msg_gmpSendBufFull));
341 }
342 return(0);
343 }
344 if ((cmd == cmd_ack) && (ge->sendsQueued)) {
345 ge->waitingHighAck = FALSE;
346 processQ(ge);
347 return(0);
348 }
349 if (cmd != cmd_ack)
350 ge->myLastSeq ^= 1;
351 ge->sendData[0] = ge->myLastSeq | (ge->hisLastSeq << 1);
352 ge->sendData[2] = 0x80 | (cmd << 4) | ((val >> 7) & 7);
353 ge->sendData[3] = 0x80 | val;
354 ge->sendData[1] = checksum(ge->sendData);
355 ge->lastSendTime = time(NULL);
356 #if DEBUG
357 if (showTransfers) {
358 fprintf(stderr,
359 "GMP: Writing command %s to port %d:%d.\n",
360 cmdNames[cmd], ge->inFile, ge->outFile);
361 }
362 #endif
363 writeResult = write(ge->outFile, ge->sendData, 4);
364 assert(writeResult == 4);
365 ge->waitingHighAck = (cmd != cmd_ack);
366 return(0);
367 }
368
369
respond(GmpEngine * ge,Query query)370 static void respond(GmpEngine *ge, Query query) {
371 int response;
372
373 if (query & 0x200) {
374 /*
375 * Oops! This really means "do you support this extended command."
376 * I don't support any extended commands, so the response should always
377 * be zero.
378 */
379 response = 0;
380 /*
381 * * Do you support this query? *
382 *
383 * query &= ~0x200;
384 * if ((query == query_game) || (query == query_rules) ||
385 * (query == query_handicap) || (query == query_boardSize) ||
386 * (query == query_color))
387 * response = 15; * Yes. *
388 * else
389 * response = 0; * No. *
390 */
391 } else {
392 ge->waitingHighAck = TRUE;
393 switch(query) {
394 case query_game:
395 response = 1; /* GO */
396 break;
397 case query_rules:
398 if (ge->chineseRules)
399 response = 2;
400 else
401 response = 1;
402 break;
403 case query_handicap:
404 response = ge->handicap;
405 if (response == 0)
406 response = 1;
407 break;
408 case query_boardSize:
409 response = ge->boardSize;
410 break;
411 case query_color:
412 if (ge->iAmWhite)
413 response = 1;
414 else
415 response = 2;
416 break;
417 default:
418 response = 0;
419 break;
420 }
421 }
422 putCommand(ge, cmd_respond, response);
423 }
424
425
askQuery(GmpEngine * ge)426 static void askQuery(GmpEngine *ge) {
427 static const Query queryList[GMP_NUMQUERIESTOASK] = {
428 query_rules, query_handicap, query_boardSize, query_color};
429 int query;
430
431 assert(ge->queriesAcked < GMP_NUMQUERIESTOASK);
432 query = queryList[ge->queriesAcked];
433 ge->lastQuerySent = query;
434 putCommand(ge, cmd_query, query);
435 }
436
437
gotQueryResponse(GmpEngine * ge,int val)438 static ButOut gotQueryResponse(GmpEngine *ge, int val) {
439 static const char *ruleNames[] = {"Japanese", "Chinese"};
440 static const char *colorNames[] = {"Black", "White"};
441 Str err;
442 ButOut result = 0;
443
444 switch(ge->lastQuerySent) {
445 case query_handicap:
446 if (val <= 1)
447 --val;
448 if ((val != -1) && (val != ge->handicap)) {
449 str_init(&err);
450 str_print(&err, "Handicaps do not agree; I want %d, he wants %d.",
451 ge->handicap, val);
452 result |= ge->actions->errorRecvd(ge, ge->packet, str_chars(&err));
453 str_deinit(&err);
454 }
455 break;
456 case query_boardSize:
457 assert(ge->boardSize != 0);
458 if ((val != 0) && (val != ge->boardSize)) {
459 str_init(&err);
460 str_print(&err, "Board sizes do not agree; I want %d, he wants %d.",
461 ge->boardSize, val);
462 result |= ge->actions->errorRecvd(ge, ge->packet, str_chars(&err));
463 str_deinit(&err);
464 }
465 break;
466 case query_rules:
467 if (val != 0) {
468 if (ge->chineseRules != (val == 2)) {
469 str_init(&err);
470 str_print(&err, "Rule sets do not agree; I want %s, he wants %s.",
471 ruleNames[ge->chineseRules], ruleNames[val == 2]);
472 result |= ge->actions->errorRecvd(ge, ge->packet, str_chars(&err));
473 str_deinit(&err);
474 }
475 }
476 break;
477 case query_color:
478 if (val != 0) {
479 if (ge->iAmWhite == (val == 1)) {
480 str_init(&err);
481 str_print(&err, "Colors sets do not agree; we both want to be %s.",
482 colorNames[ge->iAmWhite]);
483 result |= ge->actions->errorRecvd(ge, ge->packet, str_chars(&err));
484 str_deinit(&err);
485 }
486 }
487 }
488 if ((result & BUTOUT_ERR) == 0) {
489 ++ge->queriesAcked;
490 if (ge->queriesAcked < GMP_NUMQUERIESTOASK)
491 askQuery(ge);
492 else {
493 result |= putCommand(ge, cmd_ack, ~0);
494 if (ge->actions->newGame != NULL)
495 ge->actions->newGame(ge,
496 ge->packet,
497 ge->boardSize,
498 ge->handicap,
499 ge->komi,
500 ge->chineseRules,
501 ge->iAmWhite);
502 }
503 }
504 return(result);
505 }
506
507
heartbeat(ButTimer * timer)508 static ButOut heartbeat(ButTimer *timer) {
509 GmpEngine *ge = butTimer_packet(timer);
510 int writeResult;
511
512 assert(MAGIC(ge));
513 if (ge->waitingHighAck && (time(NULL) != ge->lastSendTime)) {
514 if (++ge->sendFailures > GMP_TIMEOUTSECS) {
515 return(ge->actions->errorRecvd(ge, ge->packet,
516 msg_gmpTimeout));
517 } else {
518 ge->lastSendTime = time(NULL);
519 #if DEBUG
520 if (showTransfers) {
521 fprintf(stderr,
522 "GMP: Writing command %s to port %d:%d (retry %d).\n",
523 cmdNames[(ge->sendData[2] >> 4) & 7], ge->inFile, ge->outFile,
524 ge->sendFailures);
525 }
526 #endif
527 writeResult = write(ge->outFile, ge->sendData, 4);
528 if (writeResult != 4) {
529 return(BUTOUT_ERR);
530 }
531 }
532 }
533 return(0);
534 }
535
536
gmpEngine_startGame(GmpEngine * ge,int size,int handicap,float komi,bool chineseRules,bool iAmWhite)537 void gmpEngine_startGame(GmpEngine *ge, int size, int handicap, float komi,
538 bool chineseRules, bool iAmWhite) {
539 ge->boardSize = size;
540 ge->handicap = handicap;
541 ge->komi = komi;
542 ge->chineseRules = chineseRules;
543 ge->iAmWhite = iAmWhite;
544 ge->gameStarted = FALSE;
545 if (!iAmWhite) {
546 putCommand(ge, cmd_reset, 0);
547 }
548 }
549
550
gmpEngine_sendPass(GmpEngine * ge)551 void gmpEngine_sendPass(GmpEngine *ge) {
552 int arg;
553
554 assert(MAGIC(ge));
555 if (ge->iAmWhite)
556 arg = 0x200;
557 else
558 arg = 0;
559 putCommand(ge, cmd_move, arg);
560 }
561
562
gmpEngine_sendMove(GmpEngine * ge,int x,int y)563 void gmpEngine_sendMove(GmpEngine *ge, int x, int y) {
564 int val;
565
566 assert(MAGIC(ge));
567 val = x + ge->boardSize * (ge->boardSize - 1 - y) + 1;
568 if (ge->iAmWhite)
569 val |= 0x200;
570 putCommand(ge, cmd_move, val);
571 }
572
573
gmpEngine_sendUndo(GmpEngine * ge,int numUndos)574 void gmpEngine_sendUndo(GmpEngine *ge, int numUndos) {
575 assert(MAGIC(ge));
576 putCommand(ge, cmd_undo, numUndos);
577 }
578
579
processQ(GmpEngine * ge)580 static void processQ(GmpEngine *ge) {
581 int i;
582
583 if (!ge->waitingHighAck && ge->sendsQueued) {
584 putCommand(ge, ge->sendsPending[0].cmd, ge->sendsPending[0].val);
585 --ge->sendsQueued;
586 for (i = 0; i < ge->sendsQueued; ++i) {
587 ge->sendsPending[i] = ge->sendsPending[i + 1];
588 }
589 }
590 }
591
592
gmp_forkProgram(Cgoban * cg,int * inFile,int * outFile,const char * progName,int mainTime,int byTime)593 int gmp_forkProgram(Cgoban *cg, int *inFile, int *outFile,
594 const char *progName, int mainTime, int byTime) {
595 int pid, result, fd;
596 int cgToProg[2], progToCg[2];
597 int i;
598 char **argv;
599 Str cmdLine;
600
601 result = pipe(cgToProg);
602 if (result)
603 return(-1);
604 result = pipe(progToCg);
605 if (result) {
606 close(cgToProg[0]);
607 close(cgToProg[1]);
608 return(-1);
609 }
610 pid = fork();
611 if (pid < 0) {
612 close(cgToProg[0]);
613 close(cgToProg[1]);
614 close(progToCg[0]);
615 close(progToCg[1]);
616 return(-1);
617 }
618 if (pid == 0) {
619 /* The child. */
620 for (fd = 0; fd < 100; ++fd) {
621 if ((fd != 2) && /* Don't close stderr! */
622 (fd != cgToProg[0]) && (fd != progToCg[1]))
623 close(fd);
624 }
625 if (dup(cgToProg[0]) != 0) {
626 fprintf(stderr, "I couldn't fdup into stdin!\n");
627 exit(1);
628 }
629 if (dup(progToCg[1]) != 1) {
630 fprintf(stderr, "I couldn't fdup into stdout!\n");
631 exit(1);
632 }
633 str_init(&cmdLine);
634 str_print(&cmdLine, progName, mainTime, byTime);
635 argv = makeArgv(str_chars(&cmdLine));
636 i = execve(argv[0], argv, cg->envp);
637 /*
638 * If we got here, then the execve() must have failed.
639 * No need to clean up, free cmdLine, etc...we're dead!
640 */
641 exit(GMP_EXECVEFAILED);
642 } else {
643 close(cgToProg[0]);
644 close(progToCg[1]);
645 *inFile = progToCg[0];
646 *outFile = cgToProg[1];
647 return(pid);
648 }
649 }
650
651
makeArgv(const char * cmdLine)652 static char **makeArgv(const char *cmdLine) {
653 char *buf;
654 char **args;
655 int i, numWords, len;
656
657 len = strlen(cmdLine);
658 buf = wms_malloc(len + 1);
659 strcpy(buf, cmdLine);
660 numWords = 1;
661 for (i = 1; i < len; ++i) {
662 if ((buf[i - 1] == ' ') && (buf[i] != ' '))
663 ++numWords;
664 }
665 args = wms_malloc((numWords + 1) * sizeof(char *));
666 args[0] = buf;
667 numWords = 1;
668 for (i = 1; i < len; ++i) {
669 if (buf[i] == ' ')
670 buf[i] = '\0';
671 if ((buf[i - 1] == '\0') && (buf[i] != '\0')) {
672 args[numWords++] = buf + i;
673 }
674 }
675 args[numWords++] = NULL;
676 return(args);
677 }
678