#define DEFAULT_PORTSTR "12001" #include #include #include #include #include #include #include #include extern const char *__progname; #include "oq.h" #include "pollloop.h" #include "protocol.h" #include "linesender.h" #include "linereader.h" static char *host; static char *port; typedef enum { DD_NO = 1, DD_YES, DD_CLEAR } DISPDIRTY; typedef enum { GT_OPEN = 1, GT_CLOSED } GAMETYPE; typedef enum { MUIS_IDLE = 1, MUIS_JOIN_SEL, MUIS_JOIN_PW, MUIS_NEW } MUISTATE; typedef struct stateops STATEOPS; typedef struct player PLAYER; typedef struct game GAME; typedef struct str STR; struct str { unsigned char *s; int l; } ; #define STR_CLEAR ((STR){.s=0,.l=0}) struct game { GAME *flink; GAME *blink; GAMETYPE type; STR id; unsigned int players_tot; unsigned int players_cur; unsigned int pcsize; unsigned int bdsize; STR name; PLAYER *players[4]; } ; struct player { PLAYER *flink; PLAYER *blink; int us; STR id; STR addr; STR ident; STR name; } ; struct stateops { void (*open)(void); void (*input)(const void *, int); void (*update)(void); void (*kb)(unsigned char); void (*close)(void); } ; static int netfd; static LINEREADER *netlr; static LINESENDER *netls; static OQ netoq; static DISPDIRTY dispdirty; static const STATEOPS *ops; static STR myid; static PLAYER *players; static int player_scroll; static PLAYER *player_cursor; static GAME *games; static int game_scroll; static GAME *game_cursor; static MUISTATE muistate; static int maxchat; static char **chattext; static char *msgtext; static void handleargs(int ac, char **av) { int skip; int errs; host = 0; port = strdup(DEFAULT_PORTSTR); skip = 0; errs = 0; for (ac--,av++;ac;ac--,av++) { if (skip > 0) { skip --; continue; } if (**av != '-') { if (! host) { char *slash; slash = index(*av,'/'); if (slash) { free(host); host = malloc((slash-*av)+1); bcopy(*av,host,slash-*av); host[slash-*av] = '\0'; free(port); port = strdup(slash+1); } } else { fprintf(stderr,"%s: extra argument `%s'\n",__progname,*av); errs ++; } continue; } if (0) { needarg:; fprintf(stderr,"%s: %s needs a following argument\n",__progname,*av); errs ++; continue; } #define WANTARG() do { if (++skip >= ac) goto needarg; } while (0) if (!strcmp(*av,"-host")) { WANTARG(); free(host); host = strdup(av[skip]); continue; } if (!strcmp(*av,"-port")) { WANTARG(); free(port); port = strdup(av[skip]); continue; } #undef WANTARG fprintf(stderr,"%s: unrecognized option `%s'\n",__progname,*av); errs ++; } if (! host) { fprintf(stderr,"%s: must specify a host\n",__progname); errs ++; } if (errs) exit(1); } static void setup_network(void) { struct addrinfo hints; int e; struct addrinfo *ai0; struct addrinfo *ai; int s; char hnbuf[NI_MAXHOST]; char pnbuf[NI_MAXSERV]; char *textform; hints.ai_flags = 0; hints.ai_family = PF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = 0; hints.ai_addrlen = 0; hints.ai_canonname = 0; hints.ai_addr = 0; hints.ai_next = 0; e = getaddrinfo(host,port,&hints,&ai0); if (e) { fprintf(stderr,"%s: %s/%s: %s\n",__progname,host,port,gai_strerror(e)); exit(1); } if (! ai0) { fprintf(stderr,"%s: %s/%s: successful lookup but no addresses?\n",__progname,host,port); exit(1); } textform = 0; for (ai=ai0;ai;ai=ai->ai_next) { free(textform); if (getnameinfo(ai->ai_addr,ai->ai_addrlen,&hnbuf[0],NI_MAXHOST,&pnbuf[0],NI_MAXSERV,NI_NUMERICHOST|NI_NUMERICSERV)) { fprintf(stderr,"%s: can't get numeric hostname info: %s\n",__progname,strerror(errno)); exit(1); } else { asprintf(&textform,"%s/%s",&hnbuf[0],&pnbuf[0]); } s = socket(ai->ai_family,ai->ai_socktype,ai->ai_protocol); if (s < 0) { fprintf(stderr,"%s: socket %s: %s\n",__progname,textform,strerror(errno)); continue; } if (connect(s,ai->ai_addr,ai->ai_addrlen) < 0) { fprintf(stderr,"%s: connect %s: %s\n",__progname,textform,strerror(errno)); continue; } free(textform); freeaddrinfo(ai0); netfd = s; return; } exit(1); } static void setdd(int v) { switch (v) { case DD_CLEAR: dispdirty = DD_CLEAR; break; case DD_YES: if (dispdirty != DD_CLEAR) dispdirty = DD_YES; break; default: abort(); break; } } static void rd_stdin(int id __attribute__((__unused__)), void *arg __attribute__((__unused__))) { int c; c = getch(); if (c == 12) /* ^L */ { setdd(DD_CLEAR); return; } (*ops->kb)(c); } static void kb_justbeep(unsigned char c __attribute__((__unused__))) { beep(); } static int wtest_net(int id __attribute__((__unused__)), void *arg __attribute__((__unused__))) { return(oq_nonempty(&netoq)); } static void rd_net(int id __attribute__((__unused__)), void *arg __attribute__((__unused__))) { char buf[8192]; int r; r = read(netfd,&buf[0],sizeof(buf)); if (r < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; break; } endwin(); fprintf(stderr,"%s: network read error: %s\n",__progname,strerror(errno)); exit(1); } if (r == 0) { endwin(); fprintf(stderr,"%s: network EOF\n",__progname); exit(1); } if (linereader_input(netlr,&buf[0],r)) { endwin(); fprintf(stderr,"%s: network protocol error\n",__progname); exit(1); } } static void wr_net(int id __attribute__((__unused__)), void *arg __attribute__((__unused__))) { int w; w = oq_writev(&netoq,netfd,0); if (w < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; break; } endwin(); fprintf(stderr,"%s: write error: %s\n",__progname,strerror(errno)); exit(1); } oq_dropdata(&netoq,w); } static void net_send(const void *data, int len, void *cookie __attribute__((__unused__))) { oq_queue_copy(&netoq,data,len); } static void send_magic(void) { send_line(netls, SL_BLOCK("MAGIC:",SL_STRLEN), SL_STRING(&magic[0],magiclen), SL_OCTET(MAGIC_DIR_CLIENT), SL_NUMBER(1), SL_END); } static void send_reg(void) { const char *u; int l; u = getenv("USER"); if (u == 0) u = "Anonymous"; l = strlen(u); if (l > 64) l = 64; send_line(netls, SL_BLOCK("REG:",SL_STRLEN), SL_STRING(u,l), SL_END); } static void simple_message_update(const char *msg) { clear(); move(LINES/2,(COLS-strlen(msg))/2); printw("%s",msg); } static int pingpong(const void *data, int len) { const unsigned char *body; int bodylen; void (*xfn)(void); static void got_ping(void) { send_line(netls, SL_BLOCK("PONG:",SL_STRLEN), SL_BLOCK(body,bodylen), SL_END); } static void got_pong(void) { } if (recv_line(data,len, RL_ONEOF(), RL_OPTION("PING:"), RL_SET_FN(&xfn,&got_ping), RL_REST(&body,&bodylen), RL_OPTION("PONG:"), RL_SET_FN(&xfn,&got_pong), RL_REST(&body,&bodylen), RL_ENDOF(), RL_END) == 0) { (*xfn)(); return(1); } return(0); } static void str_free(STR s) { free(s.s); } static int str_equal(STR a, STR b) { return((a.l == b.l) && !bcmp(a.s,b.s,a.l)); } static int chatter(const void *data, int len) { unsigned char status; unsigned char kind; STR from; STR msg; void (*xfn)(void); static void got_chatstat(void) { /* XXX */ } static void got_msg(void) { /* XXX */ str_free(from); str_free(msg); } if (recv_line(data,len, RL_ONEOF(), RL_OPTION("CHATSTAT:"), RL_SET_FN(&xfn,&got_chatstat), RL_OCTET(&status), RL_OPTION("MSG:"), RL_SET_FN(&xfn,&got_msg), RL_STRING(&from.s,&from.l), RL_OCTET(&kind), RL_STRING(&msg.s,&msg.l), RL_ENDOF(), RL_END) == 0) { (*xfn)(); return(1); } return(0); } static void switch_ops(const STATEOPS *new) { (*ops->close)(); ops = new; (*ops->open)(); setdd(DD_CLEAR); } static void unlink_free_player(PLAYER *p) { if (p == player_cursor) player_cursor = p->flink ? : p->blink; if (p->flink) p->flink->blink = p->blink; if (p->blink) p->blink->flink = p->flink; else players = p->flink; str_free(p->id); str_free(p->addr); str_free(p->ident); str_free(p->name); free(p); } static void unlink_free_game(GAME *g) { if (g == game_cursor) game_cursor = g->flink ? : g->blink; if (g->flink) g->flink->blink = g->blink; if (g->blink) g->blink->flink = g->flink; else games = g->flink; str_free(g->id); str_free(g->name); free(g); } static void oc_null(void) { } static void open_meeting(void) { int i; if (players || maxchat || chattext || msgtext) abort(); player_scroll = 0; game_scroll = 0; player_cursor = 0; game_cursor = 0; maxchat = 3; chattext = malloc(maxchat*sizeof(char *)); for (i=maxchat-1;i>=0;i--) chattext[i] = 0; msgtext = 0; clearok(stdscr,1); wclear(stdscr); muistate = MUIS_IDLE; } static void input_meeting(const void *data, int len) { void (*xfn)(void); STR id; STR addr; STR ident; STR name; STR flags; unsigned char status; unsigned int players_tot; unsigned int players_cur; unsigned int pcsize; unsigned int bdsize; unsigned int ournum; const unsigned char *rest; int restlen; static void got_player_plus(void) { PLAYER *p; PLAYER *lastp; setdd(DD_YES); for (p=players;p;p=p->flink) { if (str_equal(p->id,id)) { str_free(p->addr); p->addr = addr; str_free(p->ident); p->ident = ident; str_free(p->name); p->name = name; str_free(id); return; } lastp = p; } p = malloc(sizeof(PLAYER)); p->us = str_equal(id,myid); p->id = id; p->addr = addr; p->ident = ident; p->name = name; if (p->us && players && players->us) abort(); if (p->us || !players) { p->flink = players; p->blink = 0; if (players) players->blink = p; players = p; } else { p->flink = 0; p->blink = lastp; lastp->flink = p; } } static void got_player_minus(void) { PLAYER *p; for (p=players;p;p=p->flink) { if (str_equal(p->id,id)) { unlink_free_player(p); setdd(DD_YES); break; } } str_free(id); } static void got_game_plus(void) { GAME *g; GAME *lastg; STR plid; PLAYER *pv[4]; PLAYER *p; int i; if ((players_tot < 1) || (players_tot > 4)) { endwin(); fprintf(stderr,"%s: protocol error: GAME:+ total player count %d\n",__progname,players_tot); exit(1); } if ((players_cur < 1) || (players_tot > players_tot)) { endwin(); fprintf(stderr,"%s: protocol error: GAME:+ current/total player counts %d/%d\n",__progname,players_cur,players_tot); exit(1); } for <"players"> (i=0;iflink) { if (str_equal(p->id,plid)) { pv[i] = p; str_free(plid); continue <"players">; } } endwin(); fprintf(stderr,"%s: protocol error: nonexistent player ID in game\n",__progname); exit(1); } if (recv_line(rest,restlen,RL_END) < 0) { endwin(); fprintf(stderr,"%s: protocol error: GAME:+ junk after last player ID\n",__progname); exit(1); } setdd(DD_YES); do <"havegame"> { for (g=games;g;g=g->flink) { if (str_equal(g->id,id)) { str_free(p->name); str_free(id); break <"havegame">; } lastg = g; } g = malloc(sizeof(GAME)); g->id = id; g->flink = 0; if (games) { g->blink = lastg; lastg->flink = g; } else { g->blink = 0; games = g; } } while (0); g->type = memchr(flags.s,GAME_FLAG_CLOSED,flags.l) ? GT_CLOSED : GT_OPEN; g->players_tot = players_tot; g->players_cur = players_cur; g->pcsize = pcsize; g->bdsize = bdsize; p->name = name; for (i=players_cur-1;i>=0;i--) g->players[i] = pv[i]; } static void got_game_minus(void) { GAME *g; for (g=games;g;g=g->flink) { if (str_equal(g->id,id)) { unlink_free_game(g); setdd(DD_YES); break; } } str_free(id); } static void got_joinstat(void) { /* XXX */ } static void got_begin(void) { /* XXX */ } if (pingpong(data,len)) return; if (chatter(data,len)) return; if (recv_line(data,len, RL_ONEOF(), RL_OPTION("PLAYER:+"), RL_SET_FN(&xfn,&got_player_plus), RL_STRING(&id.s,&id.l), RL_STRING(&addr.s,&addr.l), RL_STRING(&ident.s,&ident.l), RL_STRING(&name.s,&name.l), RL_OPTION("PLAYER:-"), RL_SET_FN(&xfn,&got_player_minus), RL_STRING(&id.s,&id.l), RL_OPTION("GAME:+"), RL_SET_FN(&xfn,&got_game_plus), RL_STRING(&id.s,&id.l), RL_STRING(&flags.s,&flags.l), RL_NUMBER(&players_tot), RL_NUMBER(&players_cur), RL_NUMBER(&pcsize), RL_NUMBER(&bdsize), RL_STRING(&name.s,&name.l), RL_REST(&rest,&restlen), RL_OPTION("GAME:-"), RL_SET_FN(&xfn,&got_game_minus), RL_STRING(&id.s,&id.l), RL_OPTION("JOINSTAT:"), RL_SET_FN(&xfn,&got_joinstat), RL_OCTET(&status), RL_OPTION("BEGIN:"), RL_SET_FN(&xfn,&got_begin), RL_NUMBER(&players_tot), RL_NUMBER(&pcsize), RL_NUMBER(&bdsize), RL_NUMBER(&ournum), RL_REST(&rest,&restlen), RL_ENDOF(), RL_END) < 0) { endwin(); fprintf(stderr,"%s: protocol error: bad MEETING-mode message\n",__progname); exit(1); } (*xfn)(); } static void update_meeting(void) { PLAYER *p; GAME *g; int y; int skip; FILE *f; int spc; int x; int w; int below; int listlines; static int wr(void *cookie __attribute__((__unused__)), const char *data, int len) { int n; n = (len < spc) ? len : spc; if (n > 0) { printw("%.*s",n,data); spc -= n; } return(len); } listlines = LINES - 1 - maxchat; if (listlines < 4) { clear(); mvprintw(0,0,"Display too small"); return; } w = (COLS - 1) / 2; skip = player_scroll; if (skip) { mvprintw(1,2,"[%d more]",skip); y = 2; } else { y = 1; } below = 0; p = players; if (p && p->us) { spc = w; f = fwopen(0,&wr); move(0,0); fprintf( f,"%c %.*s %.*s [%.*s]", (p == player_cursor) ? '>' : ' ', p->name.l, p->name.s, p->addr.l, p->addr.s, p->ident.l, p->ident.s ); fflush(f); if (spc) fprintf(f,"%*s",spc,""); fclose(f); p = p->flink; } for (;p;p=p->flink) { if (p->us) abort(); if (skip > 0) { skip --; } else { if (y < listlines) { spc = w; f = fwopen(0,&wr); move(y,0); fprintf( f,"%c %.*s %.*s [%.*s]", (p == player_cursor) ? '>' : ' ', p->name.l, p->name.s, p->addr.l, p->addr.s, p->ident.l, p->ident.s ); fflush(f); if (spc) fprintf(f,"%*s",spc,""); fclose(f); y ++; } else { below ++; } } } for (;y=0;y--) mvaddch(y,w,'|'); x = w + 1; w = COLS - 1 - x; skip = game_scroll; if (skip) { mvprintw(0,x+2,"[%d more]",skip); y = 1; } else { y = 0; } below = 0; for (g=games;g;g=g->flink) { if (skip > 0) { skip --; } else { if (y < listlines) { spc = w; f = fwopen(0,&wr); move(y,x); fprintf( f,"%c %.*s", (g == game_cursor) ? '>' : ' ', g->name.l, g->name.s ); fflush(f); if (spc) fprintf(f,"%*s",spc,""); fclose(f); y ++; } else { below ++; } } } for (;yflink : players; break; case 'k': new = player_cursor ? player_cursor->blink : players; break; default: beep(); return; } if (new) { player_cursor = new; setdd(DD_YES); } } static void close_meeting(void) { while (players) unlink_free_player(players); } static const STATEOPS ops_meeting = { &open_meeting, &input_meeting, &update_meeting, &kb_meeting, &close_meeting }; static void input_reg(const void *data, int len) { if (pingpong(data,len)) return; if (recv_line(data,len, RL_ONEOF(), RL_OPTION("YOUARE:"), RL_STRING(&myid.s,&myid.l), RL_ENDOF(), RL_END) < 0) { endwin(); fprintf(stderr,"%s: protocol error: bad registration return\n",__progname); exit(1); } switch_ops(&ops_meeting); } static void update_reg(void) { simple_message_update("Registering..."); } static const STATEOPS ops_reg = { &oc_null, &input_reg, &update_reg, &kb_justbeep, &oc_null }; static void input_magic(const void *data, int len) { unsigned char *rmagic; int rmlen; unsigned char dir; unsigned int ver; if ( (recv_line(data,len, RL_ONEOF(), RL_OPTION("MAGIC:"), RL_STRING(&rmagic,&rmlen), RL_OCTET(&dir), RL_NUMBER(&ver), RL_ENDOF(), RL_END) < 0) || (rmlen != magiclen) || bcmp(rmagic,&magic[0],magiclen) || (dir != MAGIC_DIR_SERVER) || (ver != 1) ) { endwin(); fprintf(stderr,"%s: protocol error: magic number message wrong\n",__progname); exit(1); } free(rmagic); send_reg(); switch_ops(&ops_reg); } static void update_magic(void) { simple_message_update("Initializing..."); } static const STATEOPS ops_magic = { &oc_null, &input_magic, &update_magic, &kb_justbeep, &oc_null }; static void lrgot_net(char *line, int len, void *cookie __attribute__((__unused__))) { (*ops->input)(line,len); } static int update_display(void *arg __attribute__((__unused__))) { switch (dispdirty) { case DD_CLEAR: clearok(curscr,1); /* fall through */ case DD_YES: (*ops->update)(); refresh(); dispdirty = DD_NO; return(BLOCK_LOOP); break; case DD_NO: return(BLOCK_NIL); break; } abort(); } int main(int, char **); int main(int ac, char **av) { handleargs(ac,av); setup_network(); initscr(); cbreak(); noecho(); players = 0; maxchat = 0; chattext = 0; dispdirty = DD_CLEAR; oq_init(&netoq); netls = linesender_open(&net_send,0); add_block_fn(&update_display,0); add_poll_fd(0,&rwtest_always,&rwtest_never,&rd_stdin,0,0); add_poll_fd(netfd,&rwtest_always,&wtest_net,&rd_net,&wr_net,0); send_magic(); netlr = linereader_open(&lrgot_net,0); ops = &ops_magic; (*ops->open)(); while (1) { pre_poll(); if (do_poll() < 0) { if (errno == EINTR) continue; fprintf(stderr,"poll: %s",strerror(errno)); exit(1); } post_poll(); } }