From c4ae8d3ac1288d36fe0e67fb23b7f80a9b730cb3 Mon Sep 17 00:00:00 2001 From: tomsmeding Date: Wed, 18 Jan 2017 20:09:17 +0100 Subject: Initial --- .gitignore | 3 + Makefile | 23 +++ main.c | 596 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ memory.c | 11 ++ memory.h | 15 ++ test.py | 157 ++++++++++++++++ 6 files changed, 805 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 main.c create mode 100644 memory.c create mode 100644 memory.h create mode 100755 test.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9bd28ec --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.o +*.dSYM +roomserver diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..b997c37 --- /dev/null +++ b/Makefile @@ -0,0 +1,23 @@ +CC = gcc +CFLAGS = -Wall -Wextra -Wwrite-strings -Wconversion -Wno-sign-conversion -std=c11 -g -fwrapv +LDFLAGS = + +BIN = roomserver + +obj_files = $(patsubst %.c,%.o,$(wildcard *.c)) + +.PHONY: all clean remake + +all: $(BIN) + +clean: + rm -rf $(BIN) *.o *.dSYM + +remake: clean + make all + +$(BIN): $(obj_files) + $(CC) -o $@ $^ $(LDFLAGS) + +%.o: %.c *.h + $(CC) $(CFLAGS) -c -o $@ $< diff --git a/main.c b/main.c new file mode 100644 index 0000000..8cff385 --- /dev/null +++ b/main.c @@ -0,0 +1,596 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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;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)) + +//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;idxlen;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(gotsock,&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;irooms.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;ipublic&&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;ipublic&&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;imembers.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;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 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;idxmembers.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;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; + } + } + } + } +} diff --git a/memory.c b/memory.c new file mode 100644 index 0000000..6f8253f --- /dev/null +++ b/memory.c @@ -0,0 +1,11 @@ +#include +#include "memory.h" + + +void* check_after_allocation(const char *funcname, void *ptr){ + if(ptr == NULL){ + perror(funcname); + exit(1); + } + return ptr; +} diff --git a/memory.h b/memory.h new file mode 100644 index 0000000..6216cd9 --- /dev/null +++ b/memory.h @@ -0,0 +1,15 @@ +#pragma once + +#include + + +#define malloc(num, type) \ + ((type*)check_after_allocation("malloc", malloc((num)*sizeof(type)))) +#define calloc(num, type) \ + ((type*)check_after_allocation("calloc", calloc((num), sizeof(type)))) +#define realloc(ptr, num, type) \ + ((type*)check_after_allocation("realloc", realloc(ptr, (num)*sizeof(type)))) +#define strdup(str) \ + ((char*)check_after_allocation("strdup", strdup(str))) + +void* check_after_allocation(const char *funcname, void *ptr); diff --git a/test.py b/test.py new file mode 100755 index 0000000..d6d06ae --- /dev/null +++ b/test.py @@ -0,0 +1,157 @@ +#!/usr/bin/env python3 + +import sys, socket, inspect + +opensockets=[] + +encoding="latin-1" + +def connect(): + sock=socket.socket() + sock.connect(("localhost",1423)) + opensockets.append(sock) + return sock + +def close(sock): + sock.close() + opensockets.remove(sock) + +def sendraw(sock,text): + sock.sendall(text.encode(encoding)) + +def sendline(sock,line): + sendraw(sock,line+"\n") + +def recvraw(sock,length): + text="" + while len(text)> All OK!\x1B[0m") + + + +@testfunction("Pingpong") +def testping(s): + sendline(s,"ping") + expect(s,"pong") + +@testfunction("Create room, list, query") +def testroom(s,t): + sendlinelist(s,"room_query",[]) + sendlinelist(t,"room_query",[]) + sendlinelist(s,"room_list game",[]) + sendlinelist(t,"room_list game",[]) + sendlineok(s,"room_create game room 1 2") + sendlinelist(s,"room_query",["room"]) + sendlinelist(t,"room_query",[]) + sendlinelist(s,"room_list game",["room"]) + sendlinelist(t,"room_list game",["room"]) + +@testfunction("Create private room, list, query") +def testprivateroom(s,t): + sendlineok(s,"room_create game room 0 2") + sendlinelist(s,"room_query",["room"]) + sendlinelist(t,"room_query",[]) + sendlinelist(s,"room_list game",[]) + sendlinelist(t,"room_list game",[]) + +@testfunction("Join room") +def testjoin(s,t): + sendlineok(s,"room_create game room 1 2") + sendlineok(t,"room_join game room") + sendlinelist(t,"room_query",["room"]) + sendlineok(t,"room_create game room2 1 1") + sendlinelist(t,"room_query",["room","room2"]) + sendlineerror(s,"room_join game room2","Room full") + +@testfunction("Player list, messaging") +def testid(s,t): + sendline(s,"id"); sid=recvint(s) + sendline(t,"id"); tid=recvint(t) + sendlineok(s,"room_create game room 0 2") + sendlineok(t,"room_join game room") + sendlinelist(s,"room_player_list game room",[str(sid),str(tid)]) + sendlinelist(t,"room_player_list game room",[str(sid),str(tid)]) + + sendline(s,"room_message game room "+str(tid)+" 4") + sendraw(s,"kaas") + expect(s,"ok room_message") + expect(t,"room_message "+str(sid)+" 4") + expectraw(t,"kaas") + + sendline(t,"room_message game room "+str(sid)+" 256") + sendraw(t,"".join(chr(i) for i in range(256))) + expect(t,"ok room_message") + expect(s,"room_message "+str(tid)+" 256") + expectraw(s,"".join(chr(i) for i in range(256))) + + + +if __name__=="__main__": + runtests() -- cgit v1.2.3-54-g00ecf