diff options
Diffstat (limited to 'main.c')
-rw-r--r-- | main.c | 596 |
1 files changed, 596 insertions, 0 deletions
@@ -0,0 +1,596 @@ +#define _GNU_SOURCE +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <stdarg.h> +#include <stdbool.h> +#include <string.h> +#include <inttypes.h> +#include <getopt.h> +#include <netdb.h> +#include <sys/socket.h> +#include <sys/select.h> +#include <errno.h> +#include "memory.h" + +#define MAX_LISTEN (128) + +typedef int64_t i64; + + +__attribute__((noreturn, format (printf, 1, 2))) +void throw(const char *format,...){ + char *buf; + va_list ap; + va_start(ap,format); + vasprintf(&buf,format,ap); + va_end(ap); + fprintf(stderr,"THROW: %s\n",buf); + exit(1); +} + +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;i<rlist->len;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;i<clist->len;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;i<len;i++)if(d[i]==value)return i; + return -1; +} + +#define LIST_FIND(_list,_value) list_find_d((void**)(_list)->d,(_list)->len,(void*)(_value)) + +//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; + 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; + //TODO: notify other members + clist_remove(&room->members,conn); + rlist_remove(&conn->rooms,room); + 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;idx<rlist->len;idx++){ + if(strcmp(rlist->d[idx]->gameid,gameid)==0&&strcmp(rlist->d[idx]->roomid,roomid)==0){ + return rlist->d[idx]; + } + } + return NULL; +} + + +//Returns line length; reallocates buf if needed. +//If *bufsz==0 || *buf==NULL, allocates buf newly. +//Returns -1 on error or socket closure. +i64 sock_read_line(int sock,char **buf,i64 *bufsz){ + if(*bufsz==0||*buf==NULL){ + *bufsz=512; + *buf=malloc(*bufsz,char); + } + i64 len=0; + while(true){ + if(len==*bufsz-1){ + *bufsz*=2; + *buf=realloc(*buf,*bufsz,char); + } + i64 ret=recv(sock,*buf+len,1,0); + if(ret<=0)return -1; + if((*buf)[len]=='\n'){ + (*buf)[len]='\0'; + return len; + } + len++; + } +} + +//Returns -1 on error or connection closure. +i64 sock_read_length(int sock,char *buf,i64 length){ + i64 got=0; + while(got<length){ + i64 ret=recv(sock,buf+got,length-got,0); + if(ret<=0)return -1; + got+=ret; + } + return 0; +} + +//Returns -1 on error or connection closure. +i64 sock_send_data(int sock,const char *buf,i64 length){ + i64 sent=0; + while(sent<length){ + i64 ret=send(sock,buf+sent,length-sent,0); + if(ret==-1){ + if(errno==EINTR)continue; + return -1; + } + sent+=ret; + } + return 0; +} + +//Returns -1 on error or connection closure. +i64 sock_send(int sock,const char *str){ + return sock_send_data(sock,str,strlen(str)); +} + +//Returns -1 on error or connection closure. +i64 sock_send_line(int sock,const char *str){ + if(sock_send(sock,str)==-1)return -1; + return sock_send(sock,"\n"); +} + +//Returns -1 on error or connection closure. +__attribute__((format (printf,2,3))) +i64 sock_send_line_f(int sock,const char *format,...){ + va_list ap; + va_start(ap,format); + char *buf; + vasprintf(&buf,format,ap); + va_end(ap); + if(buf==NULL)throw("vasprintf: allocation failure"); + i64 ret=sock_send_line(sock,buf); + free(buf); + return ret; +} + +//Returns -1 on error or connection closure. +i64 sock_send_list(int sock,const char *tag,const char *const *list,i64 len){ + char *buf; + asprintf(&buf,"list %s %" PRIi64,tag,len); + if(buf==NULL)throw("asprintf: allocation failure"); + if(len==0){ + i64 ret=sock_send_line(sock,buf); + free(buf); + return ret; + } + + if(sock_send(sock,buf)==-1){free(buf); return -1;} + for(i64 i=0;i<len;i++){ + if(sock_send(sock," ")==-1){free(buf); return -1;} + if(sock_send(sock,list[i])==-1){free(buf); return -1;} + } + return sock_send(sock,"\n"); +} + +//Returns -1 on error or connection closure. +i64 sock_send_int(int sock,const char *tag,i64 value){ + char *buf; + asprintf(&buf,"int %s %" PRIi64,tag,value); + if(buf==NULL)throw("asprintf: allocation failure"); + i64 ret=sock_send_line(sock,buf); + free(buf); + return ret; +} + +//Returns -1 on error or connection closure. +i64 handle_message(Connection *conn){ + static i64 bufsz=0; + static char *buf=NULL; + if(sock_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 sock_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 sock_send_line(conn->sock,"pong"); + } else if(strcmp(cmdname,"room_create")==0){ // room_create gameid roomid public capacity + if(nargs!=4)return sock_send_line(conn->sock,"error room_create Invalid command format"); + char *endp; + i64 capacity=strtol(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 sock_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_add(gameid,roomid,public,capacity); + room_join(room,conn); + return sock_send_line(conn->sock,"ok room_create"); + } else if(strcmp(cmdname,"room_join")==0){ // room_join gameid roomid + if(nargs!=2)return sock_send_line(conn->sock,"error room_join Invalid command format"); + if(strlen(args[0])==0||strlen(args[1])==0){ + return sock_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 sock_send_line(conn->sock,"error Room not found"); + if(LIST_FIND(&room->members,conn)!=-1){ + return sock_send_line(conn->sock,"error room_join Already in room"); + } + if(room->members.len>=room->capacity){ + return sock_send_line(conn->sock,"error room_join Room full"); + } + + room_join(room,conn); + return sock_send_line(conn->sock,"ok room_join"); + } else if(strcmp(cmdname,"room_leave")==0){ // room_leave gameid roomid + if(nargs!=2)return sock_send_line(conn->sock,"error room_leave Invalid command format"); + if(strlen(args[0])==0||strlen(args[1])==0){ + return sock_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 sock_send_line(conn->sock,"error room_leave Room not found"); + if(LIST_FIND(&room->members,conn)==-1)return sock_send_line(conn->sock,"error room_leave Not in room"); + + room_leave(room,conn); + return sock_send_line(conn->sock,"ok room_leave"); + } else if(strcmp(cmdname,"room_query")==0){ // room_query -> list + if(nargs!=0)return sock_send_line(conn->sock,"error room_query Invalid command format"); + + if(conn->rooms.len==0)return sock_send_list(conn->sock,"room_query",NULL,0); + + const char **idlist=(const char**)malloc(conn->rooms.len,char*); + for(i64 i=0;i<conn->rooms.len;i++)idlist[i]=conn->rooms.d[i]->roomid; + i64 ret=sock_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 sock_send_line(conn->sock,"error room_list Invalid command format"); + const char *gameid=args[0]; + if(strlen(gameid)==0)return sock_send_line(conn->sock,"error room_list Invalid command format"); + + i64 nrooms=0; + for(i64 i=0;i<rooms.len;i++){ + if(rooms.d[i]->public&&strcmp(rooms.d[i]->gameid,gameid)==0)nrooms++; + } + if(nrooms==0)return sock_send_list(conn->sock,"room_list",NULL,0); + + const char **roomids=(const char**)malloc(nrooms,Room*); + nrooms=0; + for(i64 i=0;i<rooms.len;i++){ + if(rooms.d[i]->public&&strcmp(rooms.d[i]->gameid,gameid)==0){ + roomids[nrooms++]=rooms.d[i]->roomid; + } + } + i64 ret=sock_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 sock_send_line(conn->sock,"error room_player_list Invalid command format"); + if(strlen(args[0])==0||strlen(args[1])==0){ + return sock_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 sock_send_line(conn->sock,"error room_player_list Room not found"); + if(LIST_FIND(&room->members,conn)==-1)return sock_send_line(conn->sock,"error room_player_list Not in room"); + + if(room->members.len==0)return sock_send_list(conn->sock,"room_player_list",NULL,0); + + char **names=malloc(room->members.len,char*); + for(i64 i=0;i<room->members.len;i++){ + asprintf(&names[i],"%" PRIi64,room->members.d[i]->id); + } + i64 ret=sock_send_list(conn->sock,"room_player_list",(const char*const*)names,room->members.len); + for(i64 i=0;i<room->members.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 sock_send_line(conn->sock,"error room_message Invalid command format"); + if(strlen(args[0])==0||strlen(args[1])==0){ + return sock_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 sock_send_line(conn->sock,"error room_message Room not found"); + if(LIST_FIND(&room->members,conn)==-1)return sock_send_line(conn->sock,"error room_message Not in room"); + + char *endp; + i64 targetid=strtol(args[2],&endp,10); + if(args[2][0]=='\0'||*endp!='\0'||targetid<=0){ + return sock_send_line(conn->sock,"error room_message Invalid command format"); + } + i64 msglength=strtol(args[3],&endp,10); + if(args[3][0]=='\0'||*endp!='\0'||msglength<0){ + return sock_send_line(conn->sock,"error room_message Invalid command format"); + } + + i64 idx; + for(idx=0;idx<room->members.len;idx++){ + if(room->members.d[idx]->id==targetid)break; + } + if(idx==room->members.len)return sock_send_line(conn->sock,"error room_message Target not found"); + + if(msglength==0)return 0; + + char *buf=malloc(msglength,char); + if(sock_read_length(conn->sock,buf,msglength)==-1){ + free(buf); + return -1; + } + sock_send_line_f(room->members.d[idx]->sock,"room_message %" PRIi64 " %" PRIi64,conn->id,msglength); + sock_send_data(room->members.d[idx]->sock,buf,msglength); + free(buf); + return sock_send_line(conn->sock,"ok room_message"); + } else if(strcmp(cmdname,"id")==0){ // id -> int + return sock_send_int(conn->sock,"id",conn->id); + } else if(strcmp(cmdname,"exit")==0){ // exit + return -1; + } else { + return sock_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]\n",argv0); +} + +int main(int argc,char **argv){ + int portnum=1423; + 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;i<connections.len;i++){ + FD_SET(connections.d[i]->sock,&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;i<connections.len;i++){ + if(FD_ISSET(connections.d[i]->sock,&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; + } + } + } + } +} |