/* *protcol_details.c *Takes care of the lower-level details of the cribbagebber protocol. * Copyright (C) 2005 Joseph Pingenot This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #include #include #include #include #include #include #include #include #include "options.h" #include "datatypes.h" #include "protocol_details.h" /*Internal functions, implemented below*/ void say_hello(struct game *g, struct player *p); void say_protobroke(struct game *g, struct player *p); void say_weclome_control(struct game *g, struct player *p); void say_weclome(struct game *g, struct player *p); int get_nickname(struct game *g, struct player *p); int get_nickname_control(struct game *g, struct player *p); int waitphase_player_handler(struct game *g, struct player *p); int waitphase_controlplayer_handler(struct game *g); void say_gamefull(int fd); /*Starts up the connection *RETURNS: * -1 if failed to get socket * -2 if failed to bind * -3 if failed to listen */ int listen_in(const struct cribserv_options *opts) { int fd; struct sockaddr_in addr; /*Create a TCP/IPv4 socket*/ /*fd = socket(PF_INET, SOCK_STREAM, (getprotoent("TCP"))->p_proto);*/ fd = socket(PF_INET, SOCK_STREAM, 0); if(fd == -1) return -1; /*Bind the socket*/ addr.sin_family = AF_INET; addr.sin_port = htons(opts->port); addr.sin_addr.s_addr = INADDR_ANY; if(bind(fd, (struct sockaddr*)&addr, sizeof(struct sockaddr_in)) == -1) { fprintf(stderr, "listen_in: couldn't bind socket; errno=%d (%s)\n", errno, strerror(errno)); return -2; } /*listen in*/ if(listen(fd, 32) == -1) { fprintf(stderr, "listen_in: couldn't listen socket; errno=%d (%s)\n", errno, strerror(errno)); return -3; } return fd; } /*Takes care of getting the game setup *ARGS are obvious *RETURNS: * 0 if successful (ready to play!) * -1 if all the players quit before game started * -2 if error getting the control player. * -3 if problem with calling select() * *Essentially, we just need to listen and loop through until all are joined in. */ int assemble_players(const struct cribserv_options *opts, int cfd, struct game *g) { /*Set the phase to introduction.*/ g->phase = INTRODUCTION; struct sockaddr addr; socklen_t addrlen; fd_set read_fds; int bigfd; int newplayer; /*Welcome the players to the game as they appear.*/ /*We don't have any players yet, so just call accept(). This becomes the control * player. We loop because things can go wrong!*/ while(g->players[0].fd < 0) { /*First, init n to be -1 (since we don't want a potentially disconnected prior * control player's n to be used!*/ g->n = -1; fprintf(stderr, "calling accept to get control player\n"); if((g->players[0].fd = accept(cfd, &addr, &addrlen)) == -1) { fprintf(stderr, "accept failed, returned %d, errno %d (%s)\n", g->players[0].fd, errno, strerror(errno)); continue; } /*Update biggest FD for select()*/ bigfd = (cfd < g->players[0].fd)? g->players[0].fd : cfd; /*When this returns, we have a player!*/ /*Say hello*/ fprintf(stderr, "saying hello\n"); say_hello(g, &(g->players[0])); /*get nickname*/ if(get_control_nickname(g, &(g->players[0])) < 0) { fprintf(stderr, "saying protobroke\n"); say_protobroke(g, &(g->players[0])); /*Don't need to shift, 'cause nobody's around!*/ continue; } fprintf(stderr, "got nickname %s\n", g->players[0].nick); say_weclome_control(g, &(g->players[0])); /*We are now in the WAIT phase*/ g->phase = WAITING; /*Now we have to use select to pay attention to multiple fd's*/ FD_ZERO(&read_fds); FD_SET(cfd, &read_fds); FD_SET(g->players[0].fd, &read_fds); while((g->n < 0) || ((g->n == 2) && (g->players[1].fd <= 0)) || ((g->n == 3) && (g->players[2].fd <= 0)) || ((g->n == 4) && (g->players[3].fd <= 0))) { fprintf(stderr, "top of loop\n"); if((select(bigfd+1, &read_fds, NULL, NULL, NULL) < 0) && (errno != EINTR)) { fprintf(stderr, "assemble_players: select() failed while waiting for players (errno %d (%s))\n", errno, strerror(errno)); return -3; } /*If cfd needs attention, we need to accept()*/ if(FD_ISSET(cfd, &read_fds)){ fprintf(stderr, "SERVICE control socket\n"); if(g->players[1].fd < 0) { newplayer = 1; }else if(g->players[2].fd < 0) { newplayer = 2; }else if(g->players[3].fd < 0) { newplayer = 3; }else{ /*We should not care if accept() fails, and we should say gamefull * (which closes the link */ fprintf(stderr, "rejecting extra player; game is full\n"); if((newplayer = accept(cfd, &addr, &addrlen)) >= 0) say_gamefull(newplayer); newplayer = -1; } if(newplayer >= 0) { fprintf(stderr, "calling accept to get additional player\n"); if((g->players[newplayer].fd = accept(cfd, &addr, &addrlen)) == -1) { fprintf(stderr, "accept failed, returned %d, errno %d (%s)\n", g->players[newplayer].fd, errno, strerror(errno)); } /*now do the handshake*/ fprintf(stderr, "saying hello\n"); say_hello(g, &(g->players[newplayer])); /*Update bigfd*/ bigfd = (g->players[newplayer].fd > bigfd)? g->players[newplayer].fd : bigfd; /*Add this one into the fileset*/ FD_SET(g->players[newplayer].fd, &read_fds); /*We *would* get the player's nick, but we can do that as a normal wait * action. *HOWEVER, we cannot leave wait until all players have checked in! */ } } } /*if the control player is saying something, then we need to deal with that*/ if(FD_ISSET(g->players[0].fd, &read_fds)) { fprintf(stderr, "SERVICE control player\n"); /*control player may do something control-player specific, or general*/ if(waitphase_controlplayer_handler(g) != 0) { /*control player did something general. Call the general handler.*/ if(waitphase_player_handler(g, &(g->players[0])) < 0) { fprintf(stderr, "got bad request from player 0\n"); } } } /*Could be another player *REMEMBER that a negative fd means player isn't attached! */ if((g->players[1].fd >= 0) && FD_ISSET(g->players[1].fd, &read_fds)) { fprintf(stderr, "SERVICE player 1\n"); if(waitphase_player_handler(g, &(g->players[1])) < 0) { fprintf(stderr, "got bad request from player 1\n"); } } if((g->players[2].fd >= 0) && FD_ISSET(g->players[2].fd, &read_fds)) { fprintf(stderr, "SERVICE player 2\n"); if(waitphase_player_handler(g, &(g->players[2])) < 0) { fprintf(stderr, "got bad request from player 2\n"); } } if((g->players[3].fd >= 0) && FD_ISSET(g->players[3].fd, &read_fds)) { fprintf(stderr, "SERVICE player 3\n"); if(waitphase_player_handler(g, &(g->players[3])) < 0) { fprintf(stderr, "got bad request from player 3\n"); } } /*Exit the loop if we lost the control player (only should happen when * all players have left*/ if(g->players[0].fd <= 0) break; /*Exit the loop when all are ready!*/ } } return 0; } /*Takes care of a player quitting *RETURNS: * 0 on success * -1 if this was the last player */ int player_quit(const struct cribserv_options *opts, struct game *g, int p) { /*close this player's fd*/ close(g->players[p].fd); void_player(g, &(g->players[p])); return 0; } /*INTERNAL FUNCTIONS*/ /*Gets a response (response-string pair). All responses are lower-cased. *RETURNS: * 0 on success * -1 if broke protocol *NOTE: all response buffers should be 3 chars EXACTLY (2 chars + trailing NULL) *NOTE: all string sizes assume possible trailing NULL! [last byte will ALWAYS be NULL] *NOTE: an empty string (i.e. string[0] == '\0') is a valid response, in general */ int get_response(int fd, char *response, char *string, int stringsize) { /*buffer is stringsize + 2 char response, + : */ char buffer[stringsize + 3]; int i,j; int finished; finished = read(fd, &buffer, stringsize+3); /*NOTE: newlines are to be translated as NUL characters. We also want to * filter out non-allowed chars NOW*/ for(i=0,j=0; i < stringsize+3; i++) { //fprintf(stderr, "buffer[i=%d]=%d; buffer[j=%d]=%d; ", i, (int)buffer[i], j, (int)buffer[j]); switch(buffer[i]) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': case ';': case '/': case ':': case '\0': /*Copy. i will be incremented at the top of the loop; j needs incrementing manually*/ buffer[j++] = buffer[i]; //fprintf(stderr, "good; j is now %d", j); break; case '\n': /*Here, buffer[i] is actually a newline; we need to set it to be '\0' *NOTE that it will cause us to exit!*/ buffer[j++] = '\0'; //fprintf(stderr, "newline; j is now %d", j); break; default: //fprintf(stderr, "invalid; j is now %d", j); break; } /*Exit loop if we're done. We have to subtract 1, since we incremented j last*/ if(buffer[j-1] == '\0') { //fprintf(stderr, "; but is \\0, so break-ing\n"); break; }else{ //fprintf(stderr, "; NOT \\0, so looping\n"); } } finished = j; //fprintf(stderr, "finished=%d\n", finished); /*Make sure we at least have a valid 2 chars + colon*/ if(finished < 3) return -1; if((buffer[0] == '\0') || (buffer[0] == ':')) { return -1; }else{ response[0] = buffer[0]; } if((buffer[1] == '\0') || (buffer[1] == ':')) { return -1; }else{ response[1] = buffer[1]; } for(i=3; i < finished; i++) { string[i-3] = buffer[i]; } /*Set the final NUL chars*/ response[2] = string[stringsize-1] = '\0'; return 0; } #define HELLOSTRING "PR:0.1/\n" void say_hello(struct game *g, struct player *p) { /*We need to include the \0 to delimit messages*/ write(p->fd, HELLOSTRING, strlen(HELLOSTRING) + 1); } /*User broke protocol. This communicates that, and voids the player, and terminates * the connection.*/ #define PROTOBROKE_STRING "ER: You Brute!\n" /*NOTE: p is for player index*/ void say_protobroke(struct game *g, struct player *p) { /*We need to include the \0 to delimit messages*/ write(p->fd, PROTOBROKE_STRING, strlen(PROTOBROKE_STRING) + 1); /*Close the link*/ close(p->fd); void_player(g, p); /*Shift players down*/ //while( } /*Say welcome to the control player*/ void say_weclome_control(struct game *g, struct player *p) { char buff[NICKBUFF+3]; int i; buff[0] = 'C'; buff[1] = 'O'; buff[2] = ':'; for(i=3; i < (NICKBUFF+3); i++) { buff[i] = p->nick[i-3]; if(buff[i] == '\0') break; } buff[i] = '\n'; write(p->fd, buff, i+1); } void say_weclome(struct game *g, struct player *p) { char buff[NICKBUFF+3]; int i; buff[0] = 'W'; buff[1] = 'E'; buff[2] = ':'; for(i=3; i < (NICKBUFF+3); i++) { buff[i] = p->nick[i-3]; if(buff[i] == '\0') break; } buff[i] = '\n'; write(p->fd, buff, i+1); } /*RETURNS: * 0 on success * -1 if user didn't give a valid name * -2 if user already has a name */ int get_nickname(struct game *g, struct player *p, char *nick) { int i; char errbuf[ARGBUFF+50]; /*Cut off the nick at NICKBUFF*/ nick[NICKBUFF-1] = '\0'; /*first, make sure that this user's not already set their nick.*/ if(p->nick[0] != '\0') { fprintf(stderr, "get_nickname: user %s tried to set nick\n", p->nick); sprintf(errbuf, "WA:BADNICK (nick already set)\n", nick[i]); write(p->fd, errbuf, strlen(errbuf)); return -2; } /*Check to make sure this is a valid nick. Search for prohibited chars*/ for(i=0; i < NICKBUFF; i++) { case '/': case ';': case ':': case ' ': fprintf(stderr, "get_nickname: got bad nick (illegal char).\n"); sprintf(errbuf, "WA:BADNICK (illegal char %c)\n", nick[i]); write(p->fd, errbuf, strlen(errbuf)); return -1; } /*Now just store the nick!*/ strncpy(p->nick, nick, NICKBUFF); /*Again, hit make the last char NUL*/ p->nick[NICKBUFF - 1] = '\0'; fprintf(stderr, "get_nickname: set nickname to %s\n", p->nick); return 0; } /*This posts a message to all attached players. *p is the player who posted the message */ void post_message(struct game *g, struct player *p, char *arg) { char msg_buff[5+NICKBUFF+ARGBUFF]; /*Assemble the argument*/ sprintf(msg_buff, "ME:%s/%s\n", p->nick, arg); if(g->players[0].fd >= 0) write(g->players[0].fd, msg_buff, strlen(msg_buff)); if(g->players[1].fd >= 0) write(g->players[1].fd, msg_buff, strlen(msg_buff)); if(g->players[2].fd >= 0) write(g->players[2].fd, msg_buff, strlen(msg_buff)); if(g->players[3].fd >= 0) write(g->players[3].fd, msg_buff, strlen(msg_buff)); } /*WAIT PHASE*/ /*This is the generic player handler. *RETURNS * 0 on success (no further action required) * -1 on bad response string * -2 on bad request * 1 if user quit */ int waitphase_player_handler(struct game *g, struct player *p) { char response[3]; char arg[ARGBUFF]; if(get_response(p->fd, response, arg, ARGBUFF) < 0) { fprintf(stderr, "waitphase_player_handler: got bad response string.\n"); return -1; } /*valid responses are handled here with switch*/ switch(response[0]) { case 'i': switch(response[1]) { case 'm': /*User wants to set nick*/ get_nickname(g, p, arg); break; default: /*Bad message: i?*/ return -2; } case 'm': switch(response[1]) { case 'e': /*message. post this message.*/ post_message(g, p, arg); break; default: /*Bad message: m?*/ return -2; } case 'q': switch(response[1]) { case 'u': /*User quits*/ return -3; default: /*Bad message: q?*/ return -2; } default: /*Bad message: ??*/ return -2; } return 0; } /*Sets up the game, as specified by the control player *RETURNS: * 0 on success * -1 on bad n (bad number of players; invalid or something) * -2 on bad variant (no such variant may be set); n is set, however! */ #define BADN_STRING "WA:BADN (" #define BADVAR_STRING "WA:BADV (" int setup_game(struct game *g, char *arg) { char msg_buff[ARGBUFF+50]; int i = strlen(BADN_STRING); switch(arg[0]) { /*Must be a number, 2-4*/ case '2': g->n = 2; break; case '3': g->n = 2; break; case '4': g->n = 2; break; default: /*Bad number. tell control player*/ strcpy(msg_buff, BADN_STRING); msg_buff[i++] = arg[0]; msg_buff[i++] = ')'; msg_buff[i++] = '\n'; write(g->players[0].fd, msg_buff, i); return -1; } if(arg[1] != '/') { /*No slash!*/ sprintf(msg_buff, "%sno /)\n", BADN_STRING); write(g->players[0].fd, msg_buff, strlen(msg_buff)); return -2; } /*Currently, all flags are silently ignored.*/ return 0; } /*Same as above, but no need for a character index. *RETURNS: * 0 on success (no further action required) * -1 on bad response string * -2 on bad request * -3 on problem setting up game */ int waitphase_controlplayer_handler(struct game *g) { char response[3]; char arg[ARGBUFF]; if(get_response(g->players[0].fd, response, arg, ARGBUFF) < 0) { fprintf(stderr, "waitphase_player_handler: got bad response string.\n"); return -1; } /*valid responses are handled here with switch*/ switch(response[0]) { case 'e': switch(response[1]) { case 'j': /*Eject player. Not implemented yet.*/ break; default: /*Bad message: e?*/ return -2; } case 'n': switch(response[1]) { case 'p': /*Sets up game flags*/ if(setup_game(g, arg) < 0) { return -3; } default: /*Bad message: n?*/ return -2; } default: /*Bad message: ??*/ return -2; } return 0; } #define GAMEFULL_STRING "ER:GAMEFULL\n" void say_gamefull(int fd) { write(fd, GAMEFULL_STRING, strlen(GAMEFULL_STRING)); close(fd); } /*This is a special routine, since the control player has to set his nick before the * game is really established */