#define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include "global.h" #include "memory.h" #include "tcp.h" #define MAX_LISTEN (128) #define DEFAULT_PORT (1423) i64 max(i64 a,i64 b){ return a>b?a:b; } int max_i(int a,int b){ return a>b?a:b; } i64 uniqid(void){ static i64 id=1; return id++; } typedef struct Connection Connection; typedef struct Room Room; typedef struct CList{ Connection **d; i64 len,cap; } CList; typedef struct RList{ Room **d; i64 len,cap; } RList; struct Connection{ int sock; i64 id; RList rooms; }; struct Room{ char *gameid,*roomid; bool public; i64 capacity; CList members; }; RList rooms; CList connections; void rlist_make_inplace(RList *rlist){ rlist->cap=4; rlist->len=0; rlist->d=malloc(rlist->cap,Room*); } RList* rlist_make(void){ RList *rlist=malloc(1,RList); rlist_make_inplace(rlist); return rlist; } void rlist_add(RList *rlist,Room *room){ if(rlist->len==rlist->cap){ rlist->cap*=2; if(rlist->cap==0)throw("Overflow"); rlist->d=realloc(rlist->d,rlist->cap,Room*); } rlist->d[rlist->len++]=room; } void rlist_remove(RList *rlist,Room *room){ i64 i; for(i=0;ilen;i++){ if(rlist->d[i]==room)break; } if(i==rlist->len)throw("Room %p not found in list %p in rlist_remove",room,rlist); memmove(rlist->d+i,rlist->d+i+1,(rlist->len-i-1)*sizeof(Room*)); rlist->len--; } void clist_make_inplace(CList *clist){ clist->cap=4; clist->len=0; clist->d=malloc(clist->cap,Connection*); } CList* clist_make(void){ CList *clist=malloc(1,CList); clist_make_inplace(clist); return clist; } void clist_add(CList *clist,Connection *conn){ if(clist->len==clist->cap){ clist->cap*=2; if(clist->cap==0)throw("Overflow"); clist->d=realloc(clist->d,clist->cap,Connection*); } clist->d[clist->len++]=conn; } void clist_remove(CList *clist,Connection *conn){ i64 i; for(i=0;ilen;i++){ if(clist->d[i]==conn)break; } if(i==clist->len)throw("Connection %p not found in list %p in clist_remove",conn,clist); memmove(clist->d+i,clist->d+i+1,(clist->len-i-1)*sizeof(Connection*)); clist->len--; } //Returns -1 if not found. i64 list_find_d(void **d,i64 len,void *value){ for(i64 i=0;id,(_list)->len,(void*)(_value)) void room_broadcast_line(Room *room,const char *line){ for(i64 i=0;imembers.len;i++){ tcp_send_line(room->members.d[i]->sock,line); } } __attribute__((format (printf,2,3))) void room_broadcast_line_f(Room *room,const char *format,...){ va_list ap; va_start(ap,format); char *buf; if(vasprintf(&buf,format,ap)<0)throw("vasprintf: allocation error"); room_broadcast_line(room,buf); free(buf); } //Returns false iff conn was already in specified room. bool room_join(Room *room,Connection *conn){ if(LIST_FIND(&room->members,conn)!=-1)return false; room_broadcast_line_f(room,"room_join %s %s %" PRIi64,room->gameid,room->roomid,conn->id); clist_add(&room->members,conn); rlist_add(&conn->rooms,room); return true; } void room_destroy(Room *room); //Returns false iff conn was not in that room. bool room_leave(Room *room,Connection *conn){ i64 idx=LIST_FIND(&room->members,conn); if(idx==-1)return false; clist_remove(&room->members,conn); rlist_remove(&conn->rooms,room); room_broadcast_line_f(room,"room_leave %s %s %" PRIi64,room->gameid,room->roomid,conn->id); if(room->members.len==0){ room_destroy(room); } return true; } Connection* connection_add(int sock){ Connection *conn=malloc(1,Connection); conn->sock=sock; conn->id=uniqid(); rlist_make_inplace(&conn->rooms); clist_add(&connections,conn); return conn; } void connection_destroy(Connection *conn){ for(i64 j=conn->rooms.len-1;j>=0;j--){ room_leave(conn->rooms.d[j],conn); } clist_remove(&connections,conn); free(conn); } Room* room_add(char *gameid,char *roomid,bool public,i64 capacity){ Room *room=malloc(1,Room); room->gameid=gameid; room->roomid=roomid; room->public=public; room->capacity=capacity; clist_make_inplace(&room->members); rlist_add(&rooms,room); return room; } void room_destroy(Room *room){ for(i64 j=room->members.len-1;j>=0;j--){ room_leave(room,room->members.d[j]); } rlist_remove(&rooms,room); free(room); } Room* room_find(RList *rlist,const char *gameid,const char *roomid){ i64 idx; for(idx=0;idxlen;idx++){ if(strcmp(rlist->d[idx]->gameid,gameid)==0&&strcmp(rlist->d[idx]->roomid,roomid)==0){ return rlist->d[idx]; } } return NULL; } //Returns -1 on error or connection closure. i64 handle_message(Connection *conn){ static i64 bufsz=0; static char *buf=NULL; if(tcp_read_line(conn->sock,&buf,&bufsz)==-1)return -1; char *walker=buf; char *cmdname=strsep(&walker," "); if(cmdname==NULL)return 0; //Empty message i64 nargs=0; if(walker!=NULL){ nargs=1; for(i64 i=0;walker[i]!='\0';i++){ if(walker[i]==' ')nargs++; } if(nargs>=10){ return tcp_send_line_f(conn->sock,"error %s Too many arguments",cmdname); } } char **args=malloc(nargs,char*); char *token; nargs=0; while((token=strsep(&walker," "))!=NULL)args[nargs++]=token; if(strcmp(cmdname,"ping")==0){ // ping return tcp_send_line(conn->sock,"pong"); } else if(strcmp(cmdname,"room_create")==0){ // room_create gameid roomid public capacity if(nargs!=4)return tcp_send_line(conn->sock,"error room_create Invalid command format"); char *endp; i64 capacity=strtoll(args[3],&endp,10); if(strlen(args[0])==0||strlen(args[1])==0||strlen(args[2])!=1|| strchr("10",args[2][0])==NULL||args[3][0]=='\0'||*endp!='\0'||capacity<=0){ return tcp_send_line(conn->sock,"error room_create Invalid command format"); } char *gameid=strdup(args[0]),*roomid=strdup(args[1]); bool public=args[2][0]=='1'; Room *room=room_find(&rooms,gameid,roomid); if(room!=NULL){ free(gameid); free(roomid); return tcp_send_line(conn->sock,"error room_create Room already exists"); } room=room_add(gameid,roomid,public,capacity); room_join(room,conn); return tcp_send_line(conn->sock,"ok room_create"); } else if(strcmp(cmdname,"room_join")==0){ // room_join gameid roomid if(nargs!=2)return tcp_send_line(conn->sock,"error room_join Invalid command format"); if(strlen(args[0])==0||strlen(args[1])==0){ return tcp_send_line(conn->sock,"error room_join Invalid command format"); } const char *gameid=args[0],*roomid=args[1]; Room *room=room_find(&rooms,gameid,roomid); if(room==NULL)return tcp_send_line(conn->sock,"error room_join Room not found"); if(LIST_FIND(&room->members,conn)!=-1){ return tcp_send_line(conn->sock,"error room_join Already in room"); } if(room->members.len>=room->capacity){ return tcp_send_line(conn->sock,"error room_join Room full"); } room_join(room,conn); return tcp_send_line(conn->sock,"ok room_join"); } else if(strcmp(cmdname,"room_leave")==0){ // room_leave gameid roomid if(nargs!=2)return tcp_send_line(conn->sock,"error room_leave Invalid command format"); if(strlen(args[0])==0||strlen(args[1])==0){ return tcp_send_line(conn->sock,"error room_leave Invalid command format"); } const char *gameid=args[0],*roomid=args[1]; Room *room=room_find(&rooms,gameid,roomid); if(room==NULL)return tcp_send_line(conn->sock,"error room_leave Room not found"); if(LIST_FIND(&room->members,conn)==-1)return tcp_send_line(conn->sock,"error room_leave Not in room"); room_leave(room,conn); return tcp_send_line(conn->sock,"ok room_leave"); } else if(strcmp(cmdname,"room_query")==0){ // room_query -> list if(nargs!=0)return tcp_send_line(conn->sock,"error room_query Invalid command format"); if(conn->rooms.len==0)return tcp_send_list(conn->sock,"room_query",NULL,0); const char **idlist=(const char**)malloc(conn->rooms.len,char*); for(i64 i=0;irooms.len;i++)idlist[i]=conn->rooms.d[i]->roomid; i64 ret=tcp_send_list(conn->sock,"room_query",idlist,conn->rooms.len); free(idlist); return ret; } else if(strcmp(cmdname,"room_list")==0){ // room_list gameid -> list if(nargs!=1)return tcp_send_line(conn->sock,"error room_list Invalid command format"); const char *gameid=args[0]; if(strlen(gameid)==0)return tcp_send_line(conn->sock,"error room_list Invalid command format"); i64 nrooms=0; for(i64 i=0;ipublic&&strcmp(rooms.d[i]->gameid,gameid)==0)nrooms++; } if(nrooms==0)return tcp_send_list(conn->sock,"room_list",NULL,0); const char **roomids=(const char**)malloc(nrooms,Room*); nrooms=0; for(i64 i=0;ipublic&&rooms.d[i]->members.lencapacity&& strcmp(rooms.d[i]->gameid,gameid)==0){ roomids[nrooms++]=rooms.d[i]->roomid; } } i64 ret=tcp_send_list(conn->sock,"room_list",roomids,nrooms); free(roomids); return ret; } else if(strcmp(cmdname,"room_player_list")==0){ // room_player_list gameid roomid -> list if(nargs!=2)return tcp_send_line(conn->sock,"error room_player_list Invalid command format"); if(strlen(args[0])==0||strlen(args[1])==0){ return tcp_send_line(conn->sock,"error room_player_list Invalid command format"); } const char *gameid=args[0],*roomid=args[1]; Room *room=room_find(&rooms,gameid,roomid); if(room==NULL)return tcp_send_line(conn->sock,"error room_player_list Room not found"); if(LIST_FIND(&room->members,conn)==-1)return tcp_send_line(conn->sock,"error room_player_list Not in room"); if(room->members.len==0)return tcp_send_list(conn->sock,"room_player_list",NULL,0); char **names=malloc(room->members.len,char*); for(i64 i=0;imembers.len;i++){ asprintf(&names[i],"%" PRIi64,room->members.d[i]->id); } i64 ret=tcp_send_list(conn->sock,"room_player_list",(const char*const*)names,room->members.len); for(i64 i=0;imembers.len;i++)free(names[i]); free(names); return ret; } else if(strcmp(cmdname,"room_message")==0){ // room_message gameid roomid target length if(nargs!=4)return tcp_send_line(conn->sock,"error room_message Invalid command format"); if(strlen(args[0])==0||strlen(args[1])==0){ return tcp_send_line(conn->sock,"error room_message Invalid command format"); } char *endp; i64 targetid=strtoll(args[2],&endp,10); if(args[2][0]=='\0'||*endp!='\0'||targetid<=0){ return tcp_send_line(conn->sock,"error room_message Invalid command format"); } i64 msglength=strtoll(args[3],&endp,10); if(args[3][0]=='\0'||*endp!='\0'||msglength<0){ return tcp_send_line(conn->sock,"error room_message Invalid command format"); } const char *gameid=args[0],*roomid=args[1]; Room *room=room_find(&rooms,gameid,roomid); if(room==NULL)return tcp_send_line(conn->sock,"error room_message Room not found"); if(LIST_FIND(&room->members,conn)==-1)return tcp_send_line(conn->sock,"error room_message Not in room"); i64 idx; for(idx=0;idxmembers.len;idx++){ if(room->members.d[idx]->id==targetid)break; } if(idx==room->members.len)return tcp_send_line(conn->sock,"error room_message Target not found"); if(msglength==0)return 0; char *buf=malloc(msglength,char); if(tcp_read_data(conn->sock,buf,msglength)==-1){ free(buf); return -1; } tcp_send_line_f(room->members.d[idx]->sock,"room_message %s %s %" PRIi64 " %" PRIi64, room->gameid,room->roomid,conn->id,msglength); tcp_send_data(room->members.d[idx]->sock,buf,msglength); free(buf); return tcp_send_line(conn->sock,"ok room_message"); } else if(strcmp(cmdname,"id")==0){ // id -> int return tcp_send_int(conn->sock,"id",conn->id); } else if(strcmp(cmdname,"exit")==0){ // exit return -1; } else { return tcp_send_line_f(conn->sock,"error %s Unrecognised command",cmdname); } } void handle_disconnect(Connection *conn){ i64 idx=LIST_FIND(&connections,conn); if(idx==-1)throw("Connection %p not found in list in handle_disconnect",conn); RList *r=&connections.d[idx]->rooms; for(i64 i=r->len-1;i>=0;i--){ room_leave(r->d[i],conn); } } static void usage(const char *argv0){ fprintf(stderr,"Usage: %s [-p portnum=%i]\n",argv0,DEFAULT_PORT); } int main(int argc,char **argv){ int portnum=DEFAULT_PORT; int c; while((c=getopt(argc,argv,"hp:"))!=-1){ switch(c){ case 'h': usage(argv[0]); return 0; case 'p':{ char *endp; portnum=(int)strtol(optarg,&endp,10); if(*optarg=='\0'||*endp!='\0'){ fprintf(stderr,"Invalid port number\n"); return 1; } break; } case '?': default: usage(argv[0]); return 1; } } argc-=optind; argv+=optind; struct sockaddr_in name; int listensock=socket(AF_INET,SOCK_STREAM,0); int i=1; if(setsockopt(listensock,SOL_SOCKET,SO_REUSEADDR,&i,sizeof i)==-1){ perror("setsockopt"); return 1; } name.sin_family=AF_INET; name.sin_addr.s_addr=htonl(INADDR_ANY); name.sin_port=htons(portnum); if(bind(listensock,(struct sockaddr*)&name,sizeof name)==-1){ perror("bind"); return 1; } if(listen(listensock,MAX_LISTEN)==-1){ perror("listen"); return 1; } printf("Bound on port %d.\n",portnum); rlist_make_inplace(&rooms); clist_make_inplace(&connections); while(true){ fd_set readset; FD_ZERO(&readset); FD_SET(listensock,&readset); int maxfd=listensock; for(i64 i=0;isock,&readset); maxfd=max_i(maxfd,connections.d[i]->sock); } int ret=select(maxfd+1,&readset,NULL,NULL,NULL); if(ret<0){ if(errno==EINTR)continue; perror("select"); return 1; } if(ret==0){ //Nonexistent timeout expired continue; } if(FD_ISSET(listensock,&readset)){ int sock=accept(listensock,NULL,NULL); if(sock==-1){ perror("accept"); } else { Connection *conn=connection_add(sock); printf("New socket %d; connection %p\n",sock,conn); } } for(i64 i=0;isock,&readset)){ if(handle_message(connections.d[i])==-1){ printf("Socket %d for connection %p closed\n",connections.d[i]->sock,connections.d[i]); close(connections.d[i]->sock); handle_disconnect(connections.d[i]); connection_destroy(connections.d[i]); i--; continue; } } } } }