Mise à jour effectuée, merci de nous signaler tout dysfonctionnement ! | Upgrade done, please let us know about any dysfunction!

Commit 21b7b830 authored by ZettaScript's avatar ZettaScript
Browse files

Log, source, client communication, structure

parent cffc1dfd
......@@ -44,7 +44,7 @@ Le protocole utilisé entre clients et serveur est très simple.
VERSION/LANG/LENGTH\nDATA
**VERSION** étant la version du protocole utilisée pour le paquet, **LANG** le langage utilisé pour coder les données, **LENGTH** la taille des données, **DATA** les données.
Pour le moment, seul le JSON est utilisé.
Les champs de l'entête sont séparés par un slash, l'entête est suivie d'une nouvelle ligne puis des données. Pour le moment, seul le JSON est utilisé.
Les données JSON comprennent un champ "error".
......@@ -53,20 +53,27 @@ Les données JSON comprennent un champ "error".
17: Communication error: unsupported protocol version
18: Communication error: unknown language
19: Communication error: decoding JSON
48: Game management error: that game name is already used
49: Game management error: miss new game name
50: Game management error: miss game name
51: Game management error: there is no game with this name
52: Game management error: miss admin password
53: Game management error: wrong admin password
54: Game management error: that player does not exist
55: Game management error: wrong player password
56: Game management error: miss player password
48: Game error: that game name is already used
49: Game error: miss new game name
50: Game error: miss game name
51: Game error: there is no game with this name
52: Game error: miss admin password
53: Game error: wrong admin password
54: Game error: that player does not exist
55: Game error: wrong player password
56: Game error: miss player password
57: Game error: miss exchange data
128: Server error: source not available
Exemple de paquet valide :
geco42/json/109
{"error": 0, "server_info": {"version": "1.2.3", "protocol_versions": ["geco40","geco41","geco42","geco43"]}}
#### Échanges
**(Non implémenté)** Un échange de valeurs et/ou d'argent entre joueurs nécessite l'accord de tous les joueurs devant fournir de la richesse.
Pour proposer un échange, les joueurs négocient entre eux (en P2P) les conditions. Quand ils sont en accord, ils envoient chacun au serveur les données de l'échange, contenant la liste des transactions. Le serveur valide les transactions dès qu'il a reçu la confirmation de tous les joueurs impliqués (devant donner quelque chose).
## Sources
http://www.trm.creationmonetaire.info/TheorieRelativedelaMonnaie.pdf
......
......@@ -17,33 +17,9 @@
along with Pygeconomicus-server. If not, see <https://www.gnu.org/licenses/>.
"""
from sys import argv
from time import time
import configparser, socket, json, os
from random import randint, choice
import money_free, money_debt
from utils import *
# Default game constants
CONSTS = {
"N_VALUES": 4, # number of values types
"N_WVALUES": 1, # number of waiting values types
"TURN_DURATION": 300, # in seconds
"N_TURNS": 10, # number of turns in a game
"TURN_YEARS": 8, # number of years by turn
"LIFESPAN": 10, # max age
"N_DIGITS": 0, # number of decimals
"START_VALUES": 4 # number of random values for beginning
}
# Default server constants
PATH_CONF = "server.ini" # config file relative path
RECBUF = 1024 # reception buffer length
PROTOCOL_VERSIONS = ["geco0"]# supported protocol versions
VERSION = "0.0.0"
HOST = socket.gethostname() # server host name
PORT = 8651 # server port
games = []
games_index = {}
......@@ -66,7 +42,7 @@ class Game():
def __init__(self, name, public, admin_name, admin_address, admin_psw, consts):
# Consts
self.name = name
self.creation_time = int(time())
self.creation_time = int(time.time())
self.public = public
self.admin_name = admin_name
self.admin_address = admin_address
......@@ -81,6 +57,7 @@ class Game():
self.turn = None # current turn number
self.endturn = None # end turn timestamp
self.values = [None] * self.C["N_VALUES"] # values order (the last is the waiting value)
self.exchanges = [] # pending exchanges
# Add one player
# address is ("address", port)
......@@ -110,7 +87,7 @@ class Game():
# Start with random values
player.values = [0] * self.C["N_VALUES"]
for i in range(self.C["START_VALUES"]):
player.values[randint(0, self.C["N_VALUES"]-self.C["N_WVALUES"])-1] += 1
player.values[random.randint(0, self.C["N_VALUES"]-self.C["N_WVALUES"])-1] += 1
# Init in MoneySystem
player.wallet = 0
......@@ -134,7 +111,7 @@ class Game():
age = (age+1) % self.C["LIFESPAN"]
for player in self.players:
age_p = choice(ages)
age_p = random.choice(ages)
player.age = age_p
ages.remove(age_p)
self.initPlayer(player, False)
......@@ -157,33 +134,11 @@ class Game():
player.age = 0
self.initPlayer(player, True)
# Read config
def readConfig():
conf = configparser.ConfigParser()
conf.read(PATH_CONF)
if "Server" in conf:
if "Host" in conf["Server"]:
host = conf["Server"]["Host"]
if "Port" in conf["Server"]:
port = int(conf["Server"]["Port"])
if "Game" in conf:
for key, value in conf["Game"].items():
CONSTS[key] = value
def sendErrorToClient(client, errcode, resp_lng="json"):
if resp_lng == "json":
resp_raw = json.dumps({"error":errcode}).encode()
client.sendall(resp_raw)
client.close()
"""
Partie serveur en dessous, pas encore complètement fonctionnelle.
"""
if __name__ == "__main__":
# Config
readConfig()
if "--help" in argv:
if "--help" in sys.argv:
print("pygeconomicus-server "+VERSION+"\n\
Copyright 2018 Pascal Engélibert (GNU AGPL v3+)\n\
\n\
......@@ -195,27 +150,14 @@ Options:\n\
")
exit()
if "--source" in argv:
import tarfile
def tarReset(tarinfo):
tarinfo.uid = tarinfo.gid = 0
tarinfo.uname = tarinfo.gname = "root"
return tarinfo
tar = tarfile.open("sourcecode.tar.gz", "w:gz")
tar.add("server.py", filter=tarReset)
tar.add("money_free.py", filter=tarReset)
tar.add("money_debt.py", filter=tarReset)
tar.add("utils.py", filter=tarReset)
tar.add("README.md", filter=tarReset)
tar.add("LICENSE", filter=tarReset)
tar.close()
print("Archive successfully created!")
if "--source" in sys.argv:
tarSource()
exit()
HOST = getargv(argv, "-h", HOST)
HOST = getargv(sys.argv, "-h", HOST)
try:
PORT = int(getargv(argv, "-p", PORT))
PORT = int(getargv(sys.argv, "-p", PORT))
except ValueError:
print("Error: port must be an integer")
exit(1)
......@@ -227,7 +169,7 @@ Options:\n\
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(server_address)
sock.listen(1)
print("Server started at "+str(server_address))
logPrint("Server started at "+str(server_address), LOG_INFO)
while loop:
client, addr = sock.accept()
client.settimeout(5)
......@@ -289,10 +231,29 @@ Options:\n\
sendErrorToClient(client, ERROR_COM_JSON, resp_lng)# Communication error: decoding JSON
continue
if "query" in p_data:
if "source" in p_data:
logPrint("Received source code request", LOG_TRACE)
p_resp = True
source_time = os.path.getmtime(SOURCE_ARCHIVE)
for source_file in SOURCE_FILES:
if os.path.getmtime(source_file) > source_time:
tarSource()
break
if os.path.isfile(SOURCE_ARCHIVE):
resp_lng = "raw"
f = open(SOURCE_ARCHIVE, "rb")
resp_raw = f.read()
f.close()
else:
logPrint("Source archived but file does not exist: '"+SOURCE_ARCHIVE+"'", LOG_ERROR)
sendErrorToClient(client, ERROR_SRV_NOSOURCE, resp_lng)# Server error: source not available
continue
elif "query" in p_data:
p_resp = True
if "games" in p_data["query"]:
logPrint("Received games list query", LOG_TRACE)
resp["games"] = []
for game in games:
if game.public:
......@@ -302,13 +263,14 @@ Options:\n\
resp["games"].append(gp)
if "game_info" in p_data["query"]:
logPrint("Received game list query", LOG_TRACE)
if not "game_name" in p_data:
sendErrorToClient(client, ERROR_MGAME_MISSGAMENAME, resp_lng)# Game management error: miss game name
sendErrorToClient(client, ERROR_GAME_MISSGAMENAME, resp_lng)# Game error: miss game name
continue
g_name = p_data["game_name"]
if not g_name in games_index:
sendErrorToClient(client, ERROR_MGAME_NONAMED, resp_lng)# Game management error: there is no game with this name
sendErrorToClient(client, ERROR_GAME_NONAMED, resp_lng)# Game error: there is no game with this name
continue
game = games_index[g_name]
......@@ -319,9 +281,11 @@ Options:\n\
resp["game_info"]["players"].append({"player_name":player.name, "player_uid":player.uid, "player_address":player.address[0], "player_port":player.address[1]})
if "server_info" in p_data["query"]:
logPrint("Received server info query", LOG_TRACE)
resp["server_info"] = {"version":VERSION, "protocol_versions":PROTOCOL_VERSIONS}
if "new_game" in p_data:
logPrint("Received new game command", LOG_TRACE)
p_resp = True
g = p_data["new_game"]
......@@ -336,7 +300,7 @@ Options:\n\
g_admin_name = g["admin_name"]
if not "admin_psw" in g:
sendErrorToClient(client, ERROR_MGAME_MISSADMINPSW, resp_lng)# Game management error: miss admin password
sendErrorToClient(client, ERROR_GAME_MISSADMINPSW, resp_lng)# Game error: miss admin password
continue
g_admin_psw = g["admin_psw"]
......@@ -349,10 +313,10 @@ Options:\n\
g_public = g["public"]
if not "name" in g:
sendErrorToClient(client, ERROR_MGAME_MISSNEWGAMENAME, resp_lng)# Game management error: miss new game name
sendErrorToClient(client, ERROR_GAME_MISSNEWGAMENAME, resp_lng)# Game error: miss new game name
continue
if g["name"] in games_index:
sendErrorToClient(client, ERROR_MGAME_USEDGAMENAME, resp_lng)# Game management error: that game name is already used
sendErrorToClient(client, ERROR_GAME_USEDGAMENAME, resp_lng)# Game error: that game name is already used
continue
game = Game(g["name"], g_public, g_admin_name, g_admin_address, g_admin_psw, g_consts)
games.append(game)
......@@ -360,14 +324,15 @@ Options:\n\
resp["new_game_info"] = {"name":game.name, "public":game.public, "creation_time":game.creation_time, "admin_name":game.admin_name, "admin_address":game.admin_address, "consts":game.C}
if "edit_game" in p_data:
logPrint("Received edit game command", LOG_TRACE)
p_resp = True
if not "game_name" in p_data:
sendErrorToClient(client, ERROR_MGAME_MISSGAMENAME, resp_lng)# Game management error: miss game name
sendErrorToClient(client, ERROR_GAME_MISSGAMENAME, resp_lng)# Game error: miss game name
continue
g_name = p_data["game_name"]
if not g_name in games_index:
sendErrorToClient(client, ERROR_MGAME_NONAMED, resp_lng)# Game management error: there is no game with this name
sendErrorToClient(client, ERROR_GAME_NONAMED, resp_lng)# Game error: there is no game with this name
continue
game = games_index[g_name]
......@@ -375,7 +340,7 @@ Options:\n\
if "admin_psw" in p_data:
g_admin_psw = p_data["admin_psw"]
if g_admin_psw != game.admin_psw:
sendErrorToClient(client, ERROR_MGAME_WRONGADMINPSW, resp_lng)# Game management error: wrong admin password
sendErrorToClient(client, ERROR_GAME_WRONGADMINPSW, resp_lng)# Game error: wrong admin password
continue
g = p_data["edit_game"]
......@@ -396,15 +361,16 @@ Options:\n\
resp["edited_game_info"] = {"name":game.name, "public":game.public, "creation_time":game.creation_time, "admin_name":game.admin_name, "admin_address":game.admin_address, "consts":game.C}
if "rm_game" in p_data:
logPrint("Received remove game command", LOG_TRACE)
p_resp = True
if not "game_name" in p_data:
sendErrorToClient(client, ERROR_MGAME_MISSGAMENAME, resp_lng)# Game management error: miss game name
sendErrorToClient(client, ERROR_GAME_MISSGAMENAME, resp_lng)# Game error: miss game name
continue
g_name = p_data["game_name"]
if not g_name in games_index:
sendErrorToClient(client, ERROR_MGAME_NONAMED, resp_lng)# Game management error: there is no game with this name
sendErrorToClient(client, ERROR_GAME_NONAMED, resp_lng)# Game error: there is no game with this name
continue
game = games_index[g_name]
......@@ -412,7 +378,7 @@ Options:\n\
if "admin_psw" in p_data:
g_admin_psw = p_data["admin_psw"]
if g_admin_psw != game.admin_psw:
sendErrorToClient(client, ERROR_MGAME_WRONGADMINPSW, resp_lng)# Game management error: wrong admin password
sendErrorToClient(client, ERROR_GAME_WRONGADMINPSW, resp_lng)# Game error: wrong admin password
continue
games_index.pop(game.name)
......@@ -422,15 +388,16 @@ Options:\n\
resp["rm_game_done"] = True
if "new_player" in p_data:
logPrint("Received new player command", LOG_TRACE)
p_resp = True
if not "game_name" in p_data:
sendErrorToClient(client, ERROR_MGAME_MISSGAMENAME, resp_lng)# Game management error: miss game name
sendErrorToClient(client, ERROR_GAME_MISSGAMENAME, resp_lng)# Game error: miss game name
continue
g_name = p_data["game_name"]
if not g_name in games_index:
sendErrorToClient(client, ERROR_MGAME_NONAMED, resp_lng)# Game management error: there is no game with this name
sendErrorToClient(client, ERROR_GAME_NONAMED, resp_lng)# Game error: there is no game with this name
continue
game = games_index[g_name]
......@@ -455,15 +422,16 @@ Options:\n\
resp["new_player_done"] = {"player_name":player.name, "player_uid":player.uid, "player_address":player.address[0], "player_port":player.address[1]}
if "rm_player" in p_data:
logPrint("Received remove player command", LOG_TRACE)
p_resp = True
if not "game_name" in p_data:
sendErrorToClient(client, ERROR_MGAME_MISSGAMENAME, resp_lng)# Game management error: miss game name
sendErrorToClient(client, ERROR_GAME_MISSGAMENAME, resp_lng)# Game error: miss game name
continue
g_name = p_data["game_name"]
if not g_name in games_index:
sendErrorToClient(client, ERROR_MGAME_NONAMED, resp_lng)# Game management error: there is no game with this name
sendErrorToClient(client, ERROR_GAME_NONAMED, resp_lng)# Game error: there is no game with this name
continue
game = games_index[g_name]
......@@ -474,21 +442,21 @@ Options:\n\
g_player_uid = g["player_uid"]
if not g_player_uid in game.players_index:
sendErrorToClient(client, ERROR_MGAME_PLAYERNOEXIST, resp_lng)# Game management error: that player does not exist
sendErrorToClient(client, ERROR_GAME_PLAYERNOEXIST, resp_lng)# Game error: that player does not exist
continue
if "admin_psw" in g:
g_admin_psw = g["admin_psw"]
if g_admin_psw != game.admin_psw:
sendErrorToClient(client, ERROR_MGAME_WRONGADMINPSW, resp_lng)# Game management error: wrong admin password
sendErrorToClient(client, ERROR_GAME_WRONGADMINPSW, resp_lng)# Game error: wrong admin password
continue
elif "player_psw" in g:
g_player_psw = g["player_psw"]
if g_player_psw != game.players_index[g_player_uid].psw:
sendErrorToClient(client, ERROR_MGAME_WRONGPLAYERPSW, resp_lng)# Game management error: wrong player password
sendErrorToClient(client, ERROR_GAME_WRONGPLAYERPSW, resp_lng)# Game error: wrong player password
continue
else:
sendErrorToClient(client, ERROR_MGAME_MISSPLAYERPSW, resp_lng)# Game management error: miss player password
sendErrorToClient(client, ERROR_GAME_MISSPLAYERPSW, resp_lng)# Game error: miss player password
continue
player = game.removePlayer(g_player_uid)
......@@ -496,6 +464,45 @@ Options:\n\
resp["rm_player_done"] = {"player_name":player.name, "player_uid":player.uid, "player_address":player.address[0], "player_port":player.address[1]}
player = None
if "exchange" in p_data:
logPrint("Received exchange command", LOG_TRACE)
p_resp = True
if not "game_name" in p_data:
sendErrorToClient(client, ERROR_GAME_MISSGAMENAME, resp_lng)# Game error: miss game name
continue
g_name = p_data["game_name"]
if not g_name in games_index:
sendErrorToClient(client, ERROR_GAME_NONAMED, resp_lng)# Game error: there is no game with this name
continue
game = games_index[g_name]
g = p_data["exchange"]
g_player_uid = None
if "player_uid" in g:
g_player_uid = g["player_uid"]
if not g_player_uid in game.players_index:
sendErrorToClient(client, ERROR_GAME_PLAYERNOEXIST, resp_lng)# Game error: that player does not exist
continue
if "player_psw" in g:
g_player_psw = g["player_psw"]
if g_player_psw != game.players_index[g_player_uid].psw:
sendErrorToClient(client, ERROR_GAME_WRONGPLAYERPSW, resp_lng)# Game error: wrong player password
continue
else:
sendErrorToClient(client, ERROR_GAME_MISSPLAYERPSW, resp_lng)# Game error: miss player password
continue
if not "exchange_data" in g:
sendErrorToClient(client, ERROR_GAME_MISSEXCHDATA, resp_lng)# Game error: miss exchange data
continue
# TODO: check data, add to exchanges list, confirm, apply
else:
sendErrorToClient(client, ERROR_COM_LANGUAGE, resp_lng)# Communication error: unknown language
......@@ -504,6 +511,6 @@ Options:\n\
if p_resp:
if resp_lng == "json":
resp_raw = json.dumps(resp).encode()
client.sendall(resp_raw)
client.sendall(encodeData(resp_raw, resp_lng))
client.close()
......@@ -4,7 +4,7 @@ from utils import *
PROTOCOL_VERSION = b"geco0"
PROTOCOL_LANG = b"json"
BUFFER = 1024
RECBUF = 1024
ADDR = socket.gethostname()
# Default game constants
......@@ -70,8 +70,66 @@ def sdata(data, response_intended, lng="json"):
sock.sendall(msg)
resp = None
if response_intended:
resp = sock.recv(BUFFER)
sock.close()
paquet = b""
p_len_tmp = ""
p_len = None
p_data_index = None
i = 0
while True:
try:
raw = sock.recv(RECBUF)
except socket.timeout:
break
if raw:
for c in raw:
if p_len == None:
if c == 10:# LF
p_len = int(p_len_tmp, 16)
p_data_index = len(paquet)+1
elif (c >= 48 and c <= 57) or (c >= 97 and c <= 102) or (c >= 65 and c <= 70):# [0-9a-fA-F]
p_len_tmp += chr(c)
else:
p_len_tmp = ""
else:
i += 1
paquet += bytes([c])
if p_len != None and i >= p_len:
break
else:
break
sock.close()
# Parse paquet
try:
seps = [paquet.index(b"/")]
p_ver = paquet[:seps[0]]# protocol version
if p_ver != PROTOCOL_VERSION:
print("Communication error: unsupported protocol version");
return None
seps.append(paquet.index(b"/", seps[0]+1))
seps.append(paquet.index(b"\n", seps[1]+1))
except ValueError:
print("Communication error: decoding header");
return None
p_lng = paquet[seps[0]+1:seps[1]]# data language
if p_len > 0:
p_raw = paquet[p_data_index:]
if p_lng == b"json":
try:
p_data = json.loads(p_raw)
except json.JSONDecodeError:
print("Communication error: decoding JSON");
return None
resp = str(p_data)
elif p_lng == b"raw":
resp = p_raw
else:
sock.close()
return resp
def info():
......@@ -113,6 +171,13 @@ def removeplayer(name="Test game", player_uid=1, player_psw="", admin_psw=None):
else:
return sdata({"game_name":name, "rm_player":{"player_uid":player_uid, "player_psw":player_psw}}, True)
def getsource(outfile="serversourcecode.tar.gz"):
"""Request for source code"""
f = open(outfile, "wb")
f.write(sdata({"source":True}, True))
f.close()
return outfile
def h():
"""Show help"""
help("__main__")
......
......@@ -17,20 +17,53 @@
along with Pygeconomicus-server. If not, see <https://www.gnu.org/licenses/>.
"""
import configparser, socket, json, os, time, random, sys
ERROR_OK = 0
ERROR_COM_HEADER = 16
ERROR_COM_PROTOCOL = 17
ERROR_COM_LANGUAGE = 18
ERROR_COM_JSON = 19
ERROR_MGAME_USEDGAMENAME = 48
ERROR_MGAME_MISSNEWGAMENAME = 49
ERROR_MGAME_MISSGAMENAME = 50
ERROR_MGAME_NONAMED = 51
ERROR_MGAME_MISSADMINPSW = 52
ERROR_MGAME_WRONGADMINPSW = 53
ERROR_MGAME_PLAYERNOEXIST = 54
ERROR_MGAME_WRONGPLAYERPSW = 55
ERROR_MGAME_MISSPLAYERPSW = 56
ERROR_GAME_USEDGAMENAME = 48
ERROR_GAME_MISSNEWGAMENAME = 49
ERROR_GAME_MISSGAMENAME = 50
ERROR_GAME_NONAMED = 51
ERROR_GAME_MISSADMINPSW = 52
ERROR_GAME_WRONGADMINPSW = 53
ERROR_GAME_PLAYERNOEXIST = 54
ERROR_GAME_WRONGPLAYERPSW = 55
ERROR_GAME_MISSPLAYERPSW = 56
ERROR_GAME_MISSEXCHDATA = 57
ERROR_SRV_NOSOURCE = 128
LOG_INFO = 1
LOG_TRACE = 2
LOG_WARN = 3
LOG_ERROR = 4
LOGMSG_TYPES = {LOG_INFO:"\033[96minfo\033[0m", LOG_TRACE:"\033[39mtrace\033[0m", LOG_WARN:"\033[93mwarn\033[0m", LOG_ERROR:"\033[91merror\033[0m"}
# Default game constants
CONSTS = {
"N_VALUES": 4, # number of values types
"N_WVALUES": 1, # number of waiting values types
"TURN_DURATION": 300, # in seconds
"N_TURNS": 10, # number of turns in a game
"TURN_YEARS": 8, # number of years by turn
"LIFESPAN": 10, # max age
"N_DIGITS": 0, # number of decimals
"START_VALUES": 4 # number of random values for beginning
}
# Default server constants
PATH_CONF = "server.ini" # config file relative path
RECBUF = 1024 # reception buffer length
PROTOCOL_VERSIONS = ["geco0"]# supported protocol versions
DEFAULT_PROTOCOL = "geco0"
VERSION = "0.0.0"
HOST = socket.gethostname() # server host name
PORT = 8651 # server port
SOURCE_FILES = ["server.py", "money_free.py", "money_debt.py", "utils.py", "README.md", "LICENSE"]
SOURCE_ARCHIVE = "sourcecode.tar.gz"
# Server functions
......@@ -40,6 +73,41 @@ def getargv(argv, arg, default=""):
else:
return default
def readConfig():
conf = configparser.ConfigParser()
conf.read(PATH_CONF)
if "Server" in conf:
if "Host" in conf["Server"]:
host = conf["Server"]["Host"]
if "Port" in conf["Server"]:
port = int(conf["Server"]["Port"])
if "Game" in conf:
for key, value in conf["Game"].items():
CONSTS[key] = value
def encodeData(raw, lng="json", protocol=DEFAULT_PROTOCOL):
return protocol.encode()+b"/"+lng.encode()+b"/"+hex(len(raw))[2:].encode()+b"\n"+raw
def sendErrorToClient(client, errcode, resp_lng="json"):
resp_raw = b""
if resp_lng == "json":
resp_raw = json.dumps({"error":errcode}).encode()
client.sendall(encodeData(resp_raw))
client.close()
logPrint("Sent error "+str(errcode)+" to client", LOG_TRACE)
def tarSource():
import tarfile
def tarReset(tarinfo):
tarinfo.uid = tarinfo.gid = 0
tarinfo.uname = tarinfo.gname = "root"
return tarinfo
tar = tarfile.open(SOURCE_ARCHIVE, "w:gz")
for source_file in SOURCE_FILES:
tar.add(source_file, filter=tarReset)
tar.close()
logPrint("Source archive created at '"+SOURCE_ARCHIVE+"'", LOG_INFO)
# Money functions