diff options
| -rw-r--r-- | .gitignore | 3 | ||||
| -rw-r--r-- | Makefile | 23 | ||||
| -rw-r--r-- | main.c | 596 | ||||
| -rw-r--r-- | memory.c | 11 | ||||
| -rw-r--r-- | memory.h | 15 | ||||
| -rwxr-xr-x | test.py | 157 | 
6 files changed, 805 insertions, 0 deletions
| 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 $@ $< @@ -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; +				} +			} +		} +	} +} 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 <stdio.h> +#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 <stdlib.h> + + +#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); @@ -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)<length: +		text+=sock.recv(length-len(text)).decode(encoding) +	return text + +def recvline(sock): +	line="" +	while True: +		c=sock.recv(1).decode(encoding) +		if c=="\n": +			break +		line+=c +	return line + +def recvint(sock): +	line=recvline(sock) +	spl=line.split(" ") +	assert len(spl)==3 and spl[0]=="int" +	return int(spl[2]) + +def sendlineok(sock,line): +	sendline(sock,line) +	cmd=line.split(" ")[0] +	expect(sock,"ok "+cmd) + +def sendlineerror(sock,line,msg): +	sendline(sock,line) +	cmd=line.split(" ")[0] +	expect(sock,"error "+cmd+" "+msg) + +def sendlinelist(sock,line,lst): +	sendline(sock,line) +	cmd=line.split(" ")[0] +	expect(sock,"list "+cmd+" "+str(len(lst))+("" if len(lst)==0 else " "+" ".join(lst))) + +def expectraw(sock,text): +	got=recvraw(sock,len(text)) +	if got!=text: +		print("\nEXPECTRAW error: expected '"+text+"', got '"+got+"'") +		sys.exit(1) + +def expect(sock,line): +	got=recvline(sock) +	if got!=line: +		print("\nEXPECT error: expected '"+line+"', got '"+got+"'") +		sys.exit(1) + + +testfunctions=[] + +def testfunction(tag): +	def dec(func): +		def newfunc(): +			global opensockets +			socks=[] +			for i in range(len(inspect.signature(func).parameters)): +				socks.append(connect()) +			func(*socks) +			for sock in opensockets: sock.close() +			opensockets=[] +		testfunctions.append((tag,newfunc)) +	return dec + +def runtests(): +	for (tag,f) in testfunctions: +		print("- "+tag+": ",end="") +		sys.stdout.flush() +		f() +		print("\x1B[32mOK\x1B[0m") +	print("\x1B[32m>> 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() | 
