summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authortomsmeding <tom.smeding@gmail.com>2017-01-18 20:09:17 +0100
committertomsmeding <tom.smeding@gmail.com>2017-01-18 20:09:17 +0100
commitc4ae8d3ac1288d36fe0e67fb23b7f80a9b730cb3 (patch)
tree369ab54761b1b9d2feb8e9bdcee0032a8b89d064
Initial
-rw-r--r--.gitignore3
-rw-r--r--Makefile23
-rw-r--r--main.c596
-rw-r--r--memory.c11
-rw-r--r--memory.h15
-rwxr-xr-xtest.py157
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 $@ $<
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 <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);
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)<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()