Merge branch 'feature/user_management' of jonas/ultimate_tictactoe_server into master
This commit is contained in:
commit
86b766e0b5
100
.vscode/tags
vendored
Normal file
100
.vscode/tags
vendored
Normal file
@ -0,0 +1,100 @@
|
||||
!_TAG_FILE_FORMAT 2 /extended format; --format=1 will not append ;" to lines/
|
||||
!_TAG_FILE_SORTED 1 /0=unsorted, 1=sorted, 2=foldcase/
|
||||
!_TAG_PROGRAM_AUTHOR Darren Hiebert /dhiebert@users.sourceforge.net/
|
||||
!_TAG_PROGRAM_NAME Exuberant Ctags //
|
||||
!_TAG_PROGRAM_URL http://ctags.sourceforge.net /official site/
|
||||
!_TAG_PROGRAM_VERSION 5.9~svn20110310 //
|
||||
Base64Decode ../match.py /^def Base64Decode(jsonDump):$/;" kind:function line:13
|
||||
Base64Encode ../match.py /^def Base64Encode(ndarray):$/;" kind:function line:9
|
||||
DatabaseConnection ../database_connection.py /^class DatabaseConnection(object):$/;" kind:class line:16
|
||||
FIELD_DRAW ../match.py /^FIELD_DRAW = 3$/;" kind:variable line:33
|
||||
FIELD_EMPTY ../match.py /^FIELD_EMPTY = 0$/;" kind:variable line:30
|
||||
FIELD_USER_A ../match.py /^FIELD_USER_A = 1$/;" kind:variable line:31
|
||||
FIELD_USER_B ../match.py /^FIELD_USER_B = 2$/;" kind:variable line:32
|
||||
GameManager ../game_manager.py /^class GameManager(object):$/;" kind:class line:69
|
||||
Match ../match.py /^class Match(object):$/;" kind:class line:36
|
||||
MatchManager ../match_manager.py /^class MatchManager(object):$/;" kind:class line:6
|
||||
SQLInjectionError ../database_connection.py /^class SQLInjectionError(Exception):$/;" kind:class line:9
|
||||
SessionManager ../session_manager.py /^class SessionManager(object):$/;" kind:class line:7
|
||||
SimpleDecode ../match.py /^def SimpleDecode(jsonDump):$/;" kind:function line:26
|
||||
SimpleEncode ../match.py /^def SimpleEncode(ndarray):$/;" kind:function line:22
|
||||
User ../user.py /^class User(object):$/;" kind:class line:1
|
||||
UserManager ../user_manager.py /^class UserManager(object):$/;" kind:class line:8
|
||||
__init__ ../database_connection.py /^ def __init__(self):$/;" kind:member line:10
|
||||
__init__ ../database_connection.py /^ def __init__(self,$/;" kind:member line:58
|
||||
__init__ ../game_manager.py /^ def __init__(self, player_a_id, player_b_id, start_player, socket_a, socket_b):$/;" kind:member line:70
|
||||
__init__ ../match.py /^ def __init__(self, n, a_begins, player_a_name, player_b_name, json_state=None):$/;" kind:member line:37
|
||||
__init__ ../match_manager.py /^ def __init__(self):$/;" kind:member line:7
|
||||
__init__ ../session_manager.py /^ def __init__(self, session_lifespan_timedelta):$/;" kind:member line:8
|
||||
__init__ ../user.py /^ def __init__(self, id, name):$/;" kind:member line:2
|
||||
__init__ ../user_manager.py /^ def __init__(self):$/;" kind:member line:9
|
||||
_on_end_game ../game_manager.py /^ async def _on_end_game(self, player_id):$/;" kind:member line:172
|
||||
_on_move ../game_manager.py /^ async def _on_move(self, player_id, move_data):$/;" kind:member line:129
|
||||
cert_file ../settings.py /^cert_file = None$/;" kind:variable line:1
|
||||
check_win ../match.py /^ def check_win(self, field, x, y):$/;" kind:member line:105
|
||||
close ../database_connection.py /^ def close(self):$/;" kind:member line:92
|
||||
commit ../database_connection.py /^ def commit(self):$/;" kind:member line:96
|
||||
create_database.py ../create_database.py 1;" kind:file line:1
|
||||
create_new_match ../game_manager.py /^async def create_new_match():$/;" kind:function line:42
|
||||
create_session_for_registered_user ../session_manager.py /^ def create_session_for_registered_user(self, user_name):$/;" kind:member line:27
|
||||
create_session_for_temp_user ../session_manager.py /^ def create_session_for_temp_user(self, user_name):$/;" kind:member line:38
|
||||
create_tables ../create_database.py /^def create_tables():$/;" kind:function line:8
|
||||
create_test_manager ../user_manager.py /^def create_test_manager():$/;" kind:function line:51
|
||||
create_user ../user_manager.py /^ def create_user(self, user_name, pw):$/;" kind:member line:33
|
||||
database_connection.py ../database_connection.py 1;" kind:file line:1
|
||||
db_charset ../settings.py /^db_charset = 'utf8mb4'$/;" kind:variable line:13
|
||||
db_db ../settings.py /^db_db = "tictactoe"$/;" kind:variable line:11
|
||||
db_host ../settings.py /^db_host = "127.0.0.1"$/;" kind:variable line:6
|
||||
db_port ../settings.py /^db_port = 3306$/;" kind:variable line:7
|
||||
db_pw ../settings.py /^db_pw = "T0eT4cT!c"$/;" kind:variable line:10
|
||||
db_user ../settings.py /^db_user = "tictactoe"$/;" kind:variable line:9
|
||||
delete_session ../session_manager.py /^ def delete_session(self, session_id):$/;" kind:member line:49
|
||||
delete_user ../user_manager.py /^ def delete_user(self, user_name):$/;" kind:member line:16
|
||||
from_json_state ../match.py /^ def from_json_state(self, json_state):$/;" kind:member line:51
|
||||
game_manager.py ../game_manager.py 1;" kind:file line:1
|
||||
get_all_users ../user_manager.py /^ def get_all_users(self):$/;" kind:member line:46
|
||||
get_current_player ../match.py /^ def get_current_player(self):$/;" kind:member line:72
|
||||
get_cursor ../database_connection.py /^ def get_cursor(self):$/;" kind:member line:89
|
||||
get_id ../user.py /^ def get_id(self):$/;" kind:member line:6
|
||||
get_match ../match_manager.py /^ def get_match(self, id):$/;" kind:member line:10
|
||||
get_name ../user.py /^ def get_name(self):$/;" kind:member line:9
|
||||
get_session_by_id ../session_manager.py /^ def get_session_by_id(self, session_id):$/;" kind:member line:11
|
||||
get_session_by_registered_user ../session_manager.py /^ def get_session_by_registered_user(self, user_name):$/;" kind:member line:19
|
||||
get_session_by_temp_user ../session_manager.py /^ def get_session_by_temp_user(self, user_name):$/;" kind:member line:23
|
||||
get_sql_time ../database_connection.py /^def get_sql_time(datetime_object):$/;" kind:function line:5
|
||||
get_user ../user_manager.py /^ def get_user(self, user_name):$/;" kind:member line:12
|
||||
global_close ../database_connection.py /^ def global_close():$/;" kind:member line:29
|
||||
global_commit ../database_connection.py /^ def global_commit():$/;" kind:member line:34
|
||||
global_cursor ../database_connection.py /^ def global_cursor():$/;" kind:member line:24
|
||||
global_single_execution ../database_connection.py /^ def global_single_execution(sql_statement):$/;" kind:member line:49
|
||||
global_single_query ../database_connection.py /^ def global_single_query(query):$/;" kind:member line:39
|
||||
instance ../database_connection.py /^ instance = None$/;" kind:variable line:21
|
||||
is_full ../match.py /^ def is_full(self, field):$/;" kind:member line:102
|
||||
is_move_valid ../match.py /^ def is_move_valid(self, sub_x, sub_y, x, y):$/;" kind:member line:75
|
||||
key_file ../settings.py /^key_file = None$/;" kind:variable line:2
|
||||
main.py ../main.py 1;" kind:file line:1
|
||||
match.py ../match.py 1;" kind:file line:1
|
||||
match_manager.py ../match_manager.py 1;" kind:file line:1
|
||||
matchmaking ../game_manager.py /^async def matchmaking():$/;" kind:function line:60
|
||||
move ../match.py /^ def move(self, move_dict):$/;" kind:member line:139
|
||||
player_games ../game_manager.py /^player_games = {}$/;" kind:variable line:10
|
||||
player_id_queue ../game_manager.py /^player_id_queue = set()$/;" kind:variable line:7
|
||||
processPlayerMessage ../game_manager.py /^ async def processPlayerMessage(self, player_id, json_str):$/;" kind:member line:108
|
||||
process_message ../game_manager.py /^async def process_message(id, json):$/;" kind:function line:37
|
||||
register_user ../game_manager.py /^async def register_user(id, socket):$/;" kind:function line:13
|
||||
revoke_inactive_sessions ../session_manager.py /^ def revoke_inactive_sessions(self):$/;" kind:member line:53
|
||||
server_port ../settings.py /^server_port = 5556$/;" kind:variable line:4
|
||||
session_manager.py ../session_manager.py 1;" kind:file line:1
|
||||
settings.py ../settings.py 1;" kind:file line:1
|
||||
socket_worker ../main.py /^async def socket_worker(websocket, path):$/;" kind:function line:9
|
||||
sockets ../game_manager.py /^sockets = {}$/;" kind:variable line:8
|
||||
ssl_context ../main.py /^ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)$/;" kind:variable line:64
|
||||
startMatch ../game_manager.py /^ async def startMatch(self):$/;" kind:member line:81
|
||||
start_server ../main.py /^start_server = websockets.serve(socket_worker, host='', port=server_port, ssl=ssl_context)$/;" kind:variable line:67
|
||||
to_json_state ../match.py /^ def to_json_state(self):$/;" kind:member line:60
|
||||
touch_session ../session_manager.py /^ def touch_session(self, session_id):$/;" kind:member line:15
|
||||
touch_user ../user_manager.py /^ def touch_user(self, user_name):$/;" kind:member line:40
|
||||
unregister_user ../game_manager.py /^async def unregister_user(id):$/;" kind:function line:26
|
||||
user.py ../user.py 1;" kind:file line:1
|
||||
user_manager.py ../user_manager.py 1;" kind:file line:1
|
||||
verify_user ../user_manager.py /^ def verify_user(self, user_name, pw):$/;" kind:member line:20
|
259
README.md
259
README.md
@ -6,7 +6,7 @@ communication with the web client is done by a (far from any standard and almost
|
||||
|
||||
|
||||
|
||||
**register as player and in game queue:**
|
||||
**register as player:**
|
||||
|
||||
```json
|
||||
{
|
||||
@ -89,3 +89,260 @@ response:
|
||||
```
|
||||
|
||||
(response?)
|
||||
|
||||
|
||||
|
||||
## new version:
|
||||
|
||||
**json match state:**
|
||||
|
||||
```json
|
||||
{
|
||||
complete_field: '[[...],[...],...]',
|
||||
global_field: '[[...],[...],...]',
|
||||
last_move: {
|
||||
"sub_x": "...",
|
||||
"sub_y": "...",
|
||||
"x": "...",
|
||||
"y": "..."
|
||||
}
|
||||
game_over: <true | false>,
|
||||
player_won: <null | <player_name>>
|
||||
current_player: <null | <player_name>>
|
||||
player_a: "..."
|
||||
player_b: "..."
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
**match**:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "match_update",
|
||||
"data": {
|
||||
"id": "...",
|
||||
"match_state": <null| <match_state>>
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
**new temp session**
|
||||
|
||||
client
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "temp_session",
|
||||
"data": {
|
||||
"name": "<player_name>"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
server response:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "login_response",
|
||||
"data": {
|
||||
"success": <true|false>,
|
||||
"id": "<session-id>",
|
||||
"msg": "..."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**connect by session id**
|
||||
|
||||
client
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "reconnect",
|
||||
"data": {
|
||||
"id": "<session-id>",
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
server response:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "reconnect_response",
|
||||
"data": {
|
||||
"success": <true|false>,
|
||||
"msg": "..."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**login or register**:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "login",
|
||||
"data": {
|
||||
"name": "<player_name>",
|
||||
"pw": "<password>"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
response:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "login_response",
|
||||
"data": {
|
||||
"success": <true|false>,
|
||||
"id": "<session-id>",
|
||||
"msg": "..."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
**match_request**:
|
||||
|
||||
client (or server for sending invites)
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "match_request",
|
||||
"data": {
|
||||
"player": <null | <opponent_name>>
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
server_response:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "match_request_response",
|
||||
"data": {
|
||||
"success": <true|false>
|
||||
"msg": "..."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**match_move**:
|
||||
|
||||
client
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "move",
|
||||
"data": {
|
||||
"id": "match_id",
|
||||
"sub_x": "...",
|
||||
"sub_y": "...",
|
||||
"x": "...",
|
||||
"y": "..."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
**match update**
|
||||
|
||||
(also send on match start and send for all matches after login)
|
||||
|
||||
server:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "match_update",
|
||||
"data": {
|
||||
"id": "<match_id>",
|
||||
"match_state": "<json match state>"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**match close**
|
||||
|
||||
client:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "end_match",
|
||||
"data": {
|
||||
"id": "<match_id>"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
**friend request**:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "friend_request",
|
||||
"data" : {
|
||||
"user": "<friend>"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
response:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "friend_request_response",
|
||||
"data": {
|
||||
"success": <true|false>
|
||||
"msg": "..."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**unfriend**:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "unfriend_request",
|
||||
"data" : {
|
||||
"user": "<friend>"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
response:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "unfriend_request_response",
|
||||
"data": {
|
||||
"success": <true|false>
|
||||
"msg": "..."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
**friend update**:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "friends_update",
|
||||
"data": {
|
||||
"friends": "<list of friends>"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
610
connection_handler.py
Normal file
610
connection_handler.py
Normal file
@ -0,0 +1,610 @@
|
||||
import asyncio
|
||||
import websockets
|
||||
import json
|
||||
from session_manager import SessionManager
|
||||
from user_manager import UserManager
|
||||
from match_manager import MatchManager
|
||||
from match import Match
|
||||
|
||||
|
||||
def parse_message(msg: str):
|
||||
# TODO: make it more robust by validating each part of a message
|
||||
msg_obj = json.loads(msg)
|
||||
if "type" not in msg_obj or "data" not in msg_obj:
|
||||
print("got strange message")
|
||||
return None
|
||||
|
||||
return msg_obj
|
||||
|
||||
|
||||
class Connection(object):
|
||||
def __init__(self,
|
||||
id,
|
||||
user_name: str,
|
||||
registered: bool,
|
||||
websocket: websockets.WebSocketServerProtocol):
|
||||
self.id = id
|
||||
self.user_name = user_name
|
||||
self.websocket = websocket
|
||||
self.is_registered_user = registered
|
||||
self.is_closed = False
|
||||
|
||||
async def send(self, msg):
|
||||
try:
|
||||
await self.websocket.send(msg)
|
||||
except Exception as e:
|
||||
print("error sending message to user " +
|
||||
self.user_name + ". Reason: " + str(e))
|
||||
|
||||
async def close(self):
|
||||
try:
|
||||
if self.is_closed:
|
||||
return
|
||||
self.websocket.close()
|
||||
self.is_closed = True
|
||||
|
||||
except Exception as e:
|
||||
print("error closing session to user " +
|
||||
self.user_name + ". Reason: " + str(e))
|
||||
|
||||
|
||||
class ConnectionHandler(object):
|
||||
def __init__(self,
|
||||
session_manager: SessionManager,
|
||||
user_manager: UserManager,
|
||||
match_manager: MatchManager):
|
||||
self.session_manager = session_manager
|
||||
self.user_manager = user_manager
|
||||
self.match_manager = match_manager
|
||||
|
||||
self.open_connections_by_user = {}
|
||||
self.open_connections_by_id = {}
|
||||
|
||||
self.match_queue = set()
|
||||
|
||||
def reconnect_session(self,
|
||||
socket: websockets.WebSocketServerProtocol,
|
||||
session_id: str):
|
||||
# check whether id exists
|
||||
tmp = self.session_manager.get_session_by_id(session_id)
|
||||
if len(tmp) == 0:
|
||||
# session not available!
|
||||
return None
|
||||
|
||||
session_obj = tmp[0]
|
||||
|
||||
is_registerd = session_obj['registered_user'] is not None
|
||||
|
||||
user_name = session_obj['registered_user'] if is_registerd else session_obj['temp_user']
|
||||
|
||||
conn = Connection(id=session_id,
|
||||
user_name=user_name,
|
||||
registered=is_registerd,
|
||||
websocket=socket)
|
||||
|
||||
self.session_manager.touch_session(session_id)
|
||||
|
||||
return conn
|
||||
|
||||
def close_connection(self, conn: Connection):
|
||||
if conn.id in self.open_connections_by_id:
|
||||
del(self.open_connections_by_id[conn.id])
|
||||
del(self.open_connections_by_user[conn.user_name])
|
||||
conn.close()
|
||||
|
||||
def close_session(self, id):
|
||||
tmp = self.session_manager.get_session_by_id(id)
|
||||
if len(tmp) == 0:
|
||||
return
|
||||
|
||||
if id in self.open_connections_by_id:
|
||||
self.close_connection([id])
|
||||
|
||||
self.session_manager.delete_session(id)
|
||||
|
||||
def _add_connection(self, conn):
|
||||
self.open_connections_by_id[conn.id] = conn
|
||||
self.open_connections_by_user[conn.user_name] = conn
|
||||
|
||||
def _del_connection(self, conn):
|
||||
del(self.open_connections_by_id[conn.id])
|
||||
del(self.open_connections_by_user[conn.user_name])
|
||||
|
||||
async def new_connection(self,
|
||||
socket: websockets.WebSocketServerProtocol,
|
||||
login_msg: str):
|
||||
msg = parse_message(login_msg)
|
||||
print("new incomming connection...")
|
||||
if msg is None:
|
||||
return None
|
||||
|
||||
if msg['type'] == 'reconnect':
|
||||
conn = self.reconnect_session(socket, msg['data']['id'])
|
||||
if conn is not None:
|
||||
self._add_connection(conn)
|
||||
await socket.send(json.dumps({
|
||||
"type": "reconnect_response",
|
||||
"data": {
|
||||
"success": True,
|
||||
"id": conn.id,
|
||||
"user": conn.user_name,
|
||||
"msg": ""
|
||||
}
|
||||
}))
|
||||
await self.send_friends(conn)
|
||||
await self._on_match_state_req(conn, None)
|
||||
return conn
|
||||
await conn.send(json.dumps({
|
||||
"type": "reconnect_response",
|
||||
"data": {
|
||||
"success": False,
|
||||
"id": conn.id,
|
||||
"user": conn.user_name,
|
||||
"msg": "session not available"
|
||||
}
|
||||
}))
|
||||
return None
|
||||
|
||||
elif msg['type'] == 'temp_session':
|
||||
name = msg['data']['name'].lower()
|
||||
if len(self.session_manager.get_session_by_temp_user(name)) == 0:
|
||||
if len(self.user_manager.get_user(name)) == 0:
|
||||
if len(msg['data']['name']) < 16 and ';' not in name and '\'' not in name and '\"' not in name:
|
||||
id = self.session_manager.create_session_for_temp_user(
|
||||
name)
|
||||
|
||||
conn = Connection(
|
||||
id=id, user_name=name, registered=False, websocket=socket)
|
||||
|
||||
self._add_connection(conn)
|
||||
|
||||
await socket.send(json.dumps({
|
||||
"type": "login_response",
|
||||
"data": {
|
||||
"success": True,
|
||||
"id": id,
|
||||
"msg": "logged in as temporary user " + name
|
||||
}
|
||||
}))
|
||||
|
||||
return conn
|
||||
|
||||
await socket.send(json.dumps({
|
||||
"type": "login_response",
|
||||
"data": {
|
||||
"success": False,
|
||||
"id": None,
|
||||
"msg": "user name not available"
|
||||
}
|
||||
}))
|
||||
return None
|
||||
|
||||
elif msg['type'] == 'login':
|
||||
response_msg = ""
|
||||
success = False
|
||||
session_id = None
|
||||
name = None
|
||||
pw = None
|
||||
conn = None
|
||||
|
||||
try:
|
||||
|
||||
name = msg['data']['name'].lower()
|
||||
pw = msg['data']['pw']
|
||||
|
||||
if len(name) <= 16 and len(pw) <= 32 and len(name) > 0 and len(pw) > 0:
|
||||
|
||||
users = self.user_manager.get_user(name)
|
||||
if len(users) == 0:
|
||||
# user does not exists:
|
||||
self.user_manager.create_user(name, pw)
|
||||
session_id = self.session_manager.create_session_for_registered_user(
|
||||
name)
|
||||
response_msg = "successful registered and logged in user " + name
|
||||
success = True
|
||||
|
||||
elif self.user_manager.verify_user(name, pw):
|
||||
session_id = self.session_manager.create_session_for_registered_user(
|
||||
name)
|
||||
response_msg = "successful logged in as user " + name
|
||||
success = True
|
||||
|
||||
else:
|
||||
response_msg = "invalid password for user " + name
|
||||
else:
|
||||
response_msg = "invalid username or pw"
|
||||
|
||||
except Exception as e:
|
||||
response_msg = "invalid username or pw"
|
||||
|
||||
if success:
|
||||
conn = Connection(id=session_id, user_name=name,
|
||||
registered=True, websocket=socket)
|
||||
self._add_connection(conn)
|
||||
|
||||
await socket.send(json.dumps({
|
||||
"type": "login_response",
|
||||
"data": {
|
||||
"success": success,
|
||||
"id": session_id,
|
||||
"msg": response_msg
|
||||
}
|
||||
}))
|
||||
|
||||
if success:
|
||||
await self.send_friends(conn)
|
||||
await self._on_match_state_req(conn, None)
|
||||
|
||||
return conn
|
||||
|
||||
return None
|
||||
|
||||
async def _start_match(self, user_a, user_b):
|
||||
m = self.match_manager.create_new_match(user_a, user_b)
|
||||
state = json.loads(m.to_json_state())
|
||||
if user_a in self.open_connections_by_user:
|
||||
await self.open_connections_by_user[user_a].websocket.send(
|
||||
json.dumps(
|
||||
{
|
||||
"type": "match_update",
|
||||
"data": {
|
||||
"id": m.id,
|
||||
"match_state": state
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
if user_b in self.open_connections_by_user:
|
||||
await self.open_connections_by_user[user_b].websocket.send(
|
||||
json.dumps(
|
||||
{
|
||||
"type": "match_update",
|
||||
"data": {
|
||||
"id": m.id,
|
||||
"match_state": state
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
async def _on_match_req(self, conn, data):
|
||||
n_open_matches = len(
|
||||
self.match_manager.get_matches_for_user(conn.user_name))
|
||||
|
||||
if n_open_matches >= 5:
|
||||
await conn.websocket.send(
|
||||
json.dumps(
|
||||
{
|
||||
"type": "match_request_response",
|
||||
"data": {
|
||||
"success": False,
|
||||
"msg": "you have too many active matches to search for a new one"
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
if data['player'] is None:
|
||||
if conn.user_name in self.match_queue:
|
||||
await conn.websocket.send(
|
||||
json.dumps(
|
||||
{
|
||||
"type": "match_request_response",
|
||||
"data": {
|
||||
"success": False,
|
||||
"msg": "you are already searching for a random match"
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
if len(self.match_queue) > 0:
|
||||
# it's a match!
|
||||
user_a = self.match_queue.pop()
|
||||
await self._start_match(user_a, conn.user_name)
|
||||
|
||||
else:
|
||||
if conn.user_name not in self.match_queue:
|
||||
self.match_queue.add(conn.user_name)
|
||||
|
||||
await conn.websocket.send(
|
||||
json.dumps(
|
||||
{
|
||||
"type": "match_request_response",
|
||||
"data": {
|
||||
"success": True,
|
||||
"msg": "created match request"
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
else:
|
||||
opponent = data['player'].lower()
|
||||
if opponent == conn.user_name:
|
||||
await conn.websocket.send(
|
||||
json.dumps(
|
||||
{
|
||||
"type": "match_request_response",
|
||||
"data": {
|
||||
"success": False,
|
||||
"msg": "you cannot play against yourself"
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
return
|
||||
try:
|
||||
if len(opponent) <= 16 and '\'' not in opponent and '"' not in opponent:
|
||||
if len(self.user_manager.get_user(opponent)) > 0:
|
||||
|
||||
if len(self.match_manager.get_matches_for_user(conn.user_name)) >= 5:
|
||||
await conn.websocket.send(
|
||||
json.dumps(
|
||||
{
|
||||
"type": "match_request_response",
|
||||
"data": {
|
||||
"success": False,
|
||||
"msg": "player " + opponent + " has too many open matches"
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
if (self.match_manager.is_match(conn.user_name, opponent)):
|
||||
await conn.websocket.send(
|
||||
json.dumps(
|
||||
{
|
||||
"type": "match_request_response",
|
||||
"data": {
|
||||
"success": False,
|
||||
"msg": "you are already plaing against " + opponent
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
await self._start_match(conn.user_name, opponent)
|
||||
|
||||
await conn.websocket.send(
|
||||
json.dumps(
|
||||
{
|
||||
"type": "match_request_response",
|
||||
"data": {
|
||||
"success": True,
|
||||
"msg": "startet match against " + opponent
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
else:
|
||||
await conn.websocket.send(
|
||||
json.dumps(
|
||||
{
|
||||
"type": "match_request_response",
|
||||
"data": {
|
||||
"success": False,
|
||||
"msg": "user " + opponent + " not found :("
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
print("error processing match request: " + str(data) + str(e))
|
||||
|
||||
async def _on_match_state_req(self, conn, data):
|
||||
db_matches = self.match_manager.get_matches_for_user(conn.user_name)
|
||||
for db_match in db_matches:
|
||||
match = self.match_manager.get_match(db_match['id'])
|
||||
await conn.send(json.dumps({
|
||||
"type": "match_update",
|
||||
"data": {
|
||||
"id": db_match['id'],
|
||||
"match_state": json.loads(match.to_json_state())
|
||||
}
|
||||
}))
|
||||
if match.game_over:
|
||||
if match.player_won is None or match.player_won != conn.user_name:
|
||||
self.match_manager.delete_match(match.id)
|
||||
|
||||
async def _on_match_move(self, conn, data):
|
||||
|
||||
match = None
|
||||
|
||||
try:
|
||||
sub_x = int(data['sub_x'])
|
||||
sub_y = int(data['sub_y'])
|
||||
x = int(data['x'])
|
||||
y = int(data['y'])
|
||||
|
||||
if type(sub_x) is int and type(sub_y) is int:
|
||||
if type(x) is int and type(y) is int:
|
||||
if type(data['id']) is str:
|
||||
match = self.match_manager.apply_move(data)
|
||||
|
||||
finally:
|
||||
match_state = None
|
||||
if match is not None:
|
||||
match_state = match.to_json_state()
|
||||
|
||||
print(match_state)
|
||||
|
||||
await conn.send(json.dumps({
|
||||
'type': 'match_update',
|
||||
'data': {
|
||||
'id': match.id,
|
||||
'match_state': json.loads(match_state)
|
||||
}
|
||||
}))
|
||||
|
||||
other_user = match.player_a_name if conn.user_name == match.player_b_name else match.player_b_name
|
||||
|
||||
if other_user in self.open_connections_by_user:
|
||||
other_conn = self.open_connections_by_user[other_user]
|
||||
await other_conn.send(json.dumps({
|
||||
'type': 'match_update',
|
||||
'data': {
|
||||
'id': match.id,
|
||||
'match_state': json.loads(match_state)
|
||||
}
|
||||
}))
|
||||
if match.game_over:
|
||||
self.match_manager.delete_match(match.id)
|
||||
|
||||
async def _on_match_close(self, conn, data):
|
||||
match = None
|
||||
try:
|
||||
match_id = data['id']
|
||||
if type(match_id) is str:
|
||||
match = self.match_manager.get_match(match_id)
|
||||
|
||||
if (match is None):
|
||||
return
|
||||
|
||||
match.game_over = True
|
||||
|
||||
match_state = match.to_json_state()
|
||||
|
||||
opponent = match.player_a_name if match.player_a_name != conn.user_name else match.player_b_name
|
||||
|
||||
response = json.dumps({
|
||||
'type': 'match_update',
|
||||
'data': {
|
||||
'id': match_id,
|
||||
'match_state': json.loads(match_state)
|
||||
}
|
||||
})
|
||||
|
||||
if opponent in self.open_connections_by_user:
|
||||
await self.open_connections_by_user[opponent].websocket.send(response)
|
||||
|
||||
await conn.send(response)
|
||||
|
||||
self.match_manager.delete_match(match_id)
|
||||
|
||||
except Exception as e:
|
||||
match_state = None
|
||||
if match is not None:
|
||||
match_state = match.to_json_state()
|
||||
|
||||
await conn.send(json.dumps({
|
||||
'type': 'match_update',
|
||||
'data': {
|
||||
'match_state': json.loads(match_state)
|
||||
}
|
||||
}))
|
||||
|
||||
async def _on_friend_request(self, conn, data):
|
||||
msg = "error in handling friend request"
|
||||
success = False
|
||||
try:
|
||||
friend = data['user'].lower()
|
||||
|
||||
# check for user:
|
||||
if "\"" not in friend and "'" not in friend and ";" not in friend:
|
||||
if friend in self.user_manager.get_friends_for_user(conn.user_name):
|
||||
success = False
|
||||
msg = f"'{friend}' is already your friend"
|
||||
|
||||
elif self.user_manager.add_friend_to_user(conn.user_name, friend):
|
||||
success = True
|
||||
msg = f"added '{friend}' as a friend"
|
||||
|
||||
else:
|
||||
success = False
|
||||
msg = f"player '{friend}' not found"
|
||||
|
||||
else:
|
||||
success = False
|
||||
msg = "misformated friend request"
|
||||
|
||||
finally:
|
||||
await conn.send(json.dumps({
|
||||
'type': 'friend_request_response',
|
||||
'data': {
|
||||
'success': success,
|
||||
'msg': msg
|
||||
}
|
||||
}))
|
||||
|
||||
if success:
|
||||
await self.send_friends(conn)
|
||||
|
||||
async def _on_unfriend_request(self, conn, data):
|
||||
success = False
|
||||
msg = "error in handling unfriend request"
|
||||
try:
|
||||
friend = data['user'].lower()
|
||||
|
||||
if "\"" not in friend and "'" not in friend and ";" not in friend:
|
||||
if friend not in self.user_manager.get_friends_for_user(conn.user_name):
|
||||
success = False
|
||||
msg = f"cannot end friendship with '{friend}': it's not one of your friends"
|
||||
else:
|
||||
self.user_manager.remove_friend_from_user(
|
||||
conn.user_name, friend)
|
||||
|
||||
success = True
|
||||
msg = f"removed '{friend}' from your friend list"
|
||||
|
||||
finally:
|
||||
await conn.send(json.dumps({
|
||||
'type': 'unfriend_request_response',
|
||||
'data': {
|
||||
'success': success,
|
||||
'msg': msg
|
||||
}
|
||||
}))
|
||||
|
||||
if success:
|
||||
await self.send_friends(conn)
|
||||
|
||||
async def send_friends(self, conn):
|
||||
friends = list(self.user_manager.get_friends_for_user(conn.user_name))
|
||||
|
||||
await conn.send(json.dumps({
|
||||
'type': 'friends_update',
|
||||
'data': {
|
||||
'friends': friends
|
||||
}
|
||||
}))
|
||||
|
||||
async def disconnect(self, conn):
|
||||
self._del_connection(conn)
|
||||
|
||||
async def handle_message(self, conn, msg_str):
|
||||
msg = parse_message(msg_str)
|
||||
|
||||
print("incoming message" + str(msg))
|
||||
|
||||
if msg is None:
|
||||
return None
|
||||
|
||||
t = msg['type']
|
||||
|
||||
self.user_manager.touch_user(conn.user_name)
|
||||
if t == "match_request":
|
||||
await self._on_match_req(conn, msg['data'])
|
||||
|
||||
elif t == "move":
|
||||
await self._on_match_move(conn, msg['data'])
|
||||
|
||||
elif t == "end_match":
|
||||
await self._on_match_close(conn, msg['data'])
|
||||
|
||||
elif t == "match_states_request":
|
||||
await self._on_match_state_req(conn, msg['data'])
|
||||
|
||||
elif t == "friend_request":
|
||||
await self._on_friend_request(conn, msg['data'])
|
||||
|
||||
elif t == "unfriend_request":
|
||||
await self._on_unfriend_request(conn, msg['data'])
|
||||
|
||||
else:
|
||||
print("could not interpret message: " + msg_str)
|
35
create_database.py
Executable file
35
create_database.py
Executable file
@ -0,0 +1,35 @@
|
||||
#!/usr/bin/env python3
|
||||
# this script creates the necessary tables in the databse
|
||||
|
||||
import settings
|
||||
from database_connection import DatabaseConnection
|
||||
|
||||
|
||||
def create_tables():
|
||||
|
||||
DatabaseConnection(settings.db_host,
|
||||
settings.db_port,
|
||||
settings.db_user,
|
||||
settings.db_pw,
|
||||
settings.db_db,
|
||||
settings.db_charset)
|
||||
|
||||
queries = [
|
||||
"DROP TABLE IF EXISTS matches",
|
||||
"DROP TABLE IF EXISTS sessions",
|
||||
"DROP TABLE IF EXISTS users",
|
||||
"DROP TABLE IF EXISTS friends",
|
||||
"CREATE TABLE users (name varchar(16) NOT NULL, pw_hash varchar(128) NOT NULL, pw_salt varchar(32) NOT NULL, last_seen datetime NOT NULL, PRIMARY KEY (name)) CHARACTER SET " + settings.db_charset,
|
||||
"CREATE TABLE matches (id varchar(32) NOT NULL, user_a varchar(16) NOT NULL, user_b varchar(16) NOT NULL, match_state varchar(4096) NOT NULL, active_user varchar(16), last_active datetime NOT NULL, FOREIGN KEY (user_a) REFERENCES users(name), FOREIGN KEY (user_b) REFERENCES users(name), FOREIGN KEY (active_user) REFERENCES users(name)) CHARACTER SET " + settings.db_charset,
|
||||
"CREATE TABLE sessions (id varchar(32) NOT NULL, registered_user varchar(16), temp_user varchar(16), last_seen datetime NOT NULL, PRIMARY KEY (id), FOREIGN KEY(registered_user) REFERENCES users(name)) CHARACTER SET " + settings.db_charset,
|
||||
"CREATE TABLE friends (user varchar(16) NOT NULL, friend varchar(16) NOT NULL, FOREIGN KEY(user) REFERENCES users(name), FOREIGN KEY(friend) REFERENCES users(name)) CHARACTER SET " + settings.db_charset
|
||||
]
|
||||
|
||||
for query in queries:
|
||||
DatabaseConnection.global_single_execution(query)
|
||||
|
||||
DatabaseConnection.global_close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
create_tables()
|
106
database_connection.py
Normal file
106
database_connection.py
Normal file
@ -0,0 +1,106 @@
|
||||
import pymysql.cursors
|
||||
import sys
|
||||
|
||||
|
||||
def get_sql_time(datetime_object):
|
||||
return datetime_object.strftime('%Y-%m-%d %H:%M:%S')
|
||||
|
||||
|
||||
class SQLInjectionError(Exception):
|
||||
def __init__(self):
|
||||
|
||||
# Call the base class constructor with the parameters it needs
|
||||
super().__init__("Detected possible SQL injection attack!")
|
||||
|
||||
|
||||
class DatabaseConnection(object):
|
||||
"""
|
||||
a singleton class for a global database connection
|
||||
"""
|
||||
|
||||
instance = None
|
||||
|
||||
@staticmethod
|
||||
def global_cursor():
|
||||
assert DatabaseConnection.instance is not None
|
||||
return DatabaseConnection.instance.get_cursor()
|
||||
|
||||
@staticmethod
|
||||
def global_close():
|
||||
assert DatabaseConnection.instance is not None
|
||||
DatabaseConnection.instance.close()
|
||||
|
||||
@staticmethod
|
||||
def global_commit():
|
||||
assert DatabaseConnection.instance is not None
|
||||
DatabaseConnection.instance.commit()
|
||||
|
||||
@staticmethod
|
||||
def global_single_query(query):
|
||||
if ';' in query:
|
||||
# Possible injection!
|
||||
raise SQLInjectionError()
|
||||
|
||||
with DatabaseConnection.global_cursor() as c:
|
||||
c.execute(query)
|
||||
return c.fetchall()
|
||||
|
||||
@staticmethod
|
||||
def global_single_execution(sql_statement):
|
||||
if ';' in sql_statement:
|
||||
# Possible injection detected!
|
||||
raise SQLInjectionError()
|
||||
|
||||
with DatabaseConnection.global_cursor() as c:
|
||||
c.execute(sql_statement)
|
||||
DatabaseConnection.global_commit()
|
||||
|
||||
def __init__(self,
|
||||
host: str,
|
||||
port: int,
|
||||
user: str,
|
||||
password: str,
|
||||
db: str,
|
||||
charset: str):
|
||||
|
||||
assert DatabaseConnection.instance is None
|
||||
try:
|
||||
self.connection = pymysql.connect(
|
||||
host=host,
|
||||
port=port,
|
||||
user=user,
|
||||
password=password,
|
||||
db=db,
|
||||
charset=charset,
|
||||
cursorclass=pymysql.cursors.DictCursor)
|
||||
DatabaseConnection.instance = self
|
||||
except Exception as e:
|
||||
sys.stderr.write("could not connect to database '" +
|
||||
str(db) +
|
||||
"' at " +
|
||||
user +
|
||||
"@" +
|
||||
host +
|
||||
":" +
|
||||
str(port) +
|
||||
"\nCheck the configuration in settings.py!\n")
|
||||
raise Exception('could not connec to database')
|
||||
|
||||
def get_cursor(self):
|
||||
return self.connection.cursor()
|
||||
|
||||
def close(self):
|
||||
self.connection.close()
|
||||
DatabaseConnection.instance = None
|
||||
|
||||
def commit(self):
|
||||
self.connection.commit()
|
||||
|
||||
def test_connection():
|
||||
import settings
|
||||
DatabaseConnection(settings.db_host,
|
||||
settings.db_port,
|
||||
settings.db_user,
|
||||
settings.db_pw,
|
||||
settings.db_db,
|
||||
settings.db_charset)
|
185
game_manager.py
185
game_manager.py
@ -1,185 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import asyncio
|
||||
import websockets
|
||||
import json
|
||||
|
||||
player_id_queue = set()
|
||||
sockets = {}
|
||||
|
||||
player_games = {}
|
||||
|
||||
|
||||
async def register_user(id, socket):
|
||||
|
||||
if id in player_id_queue or id in player_games:
|
||||
return False
|
||||
|
||||
player_id_queue.add(id)
|
||||
sockets[id] = socket
|
||||
|
||||
await matchmaking()
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def unregister_user(id):
|
||||
if id in player_id_queue:
|
||||
player_id_queue.remove(id)
|
||||
del(sockets[id])
|
||||
|
||||
elif id in player_games:
|
||||
# we have an active game and have to end it
|
||||
await player_games[id]._on_end_game(id)
|
||||
del(player_games[id])
|
||||
|
||||
|
||||
async def process_message(id, json):
|
||||
if id in player_games:
|
||||
await player_games[id].processPlayerMessage(id, json)
|
||||
|
||||
|
||||
async def create_new_match():
|
||||
p_a = player_id_queue.pop()
|
||||
p_b = player_id_queue.pop()
|
||||
|
||||
s_a = sockets[p_a]
|
||||
s_b = sockets[p_b]
|
||||
|
||||
del(sockets[p_a])
|
||||
del(sockets[p_b])
|
||||
|
||||
new_game = GameManager(p_a, p_b, p_a, s_a, s_b)
|
||||
|
||||
player_games[p_a] = new_game
|
||||
player_games[p_b] = new_game
|
||||
|
||||
await new_game.startMatch()
|
||||
|
||||
|
||||
async def matchmaking():
|
||||
if len(player_id_queue) < 2:
|
||||
# we need at least 2 users for that
|
||||
return
|
||||
|
||||
else:
|
||||
asyncio.ensure_future(create_new_match())
|
||||
|
||||
|
||||
class GameManager(object):
|
||||
def __init__(self, player_a_id, player_b_id, start_player, socket_a, socket_b):
|
||||
self.player_a_id = player_a_id
|
||||
self.player_b_id = player_b_id
|
||||
|
||||
self.socket_a = socket_a
|
||||
self.socket_b = socket_b
|
||||
|
||||
self.current_player = start_player
|
||||
|
||||
self.game_finished = False
|
||||
|
||||
async def startMatch(self):
|
||||
|
||||
print("match starts")
|
||||
|
||||
start_msg_a = {
|
||||
'type': 'game_starts',
|
||||
'data': {
|
||||
'msg': '...',
|
||||
'opponent_name': self.player_b_id,
|
||||
'is_first_move': True
|
||||
}
|
||||
}
|
||||
|
||||
start_msg_b = {
|
||||
'type': 'game_starts',
|
||||
'data': {
|
||||
'msg': '...',
|
||||
'opponent_name': self.player_a_id,
|
||||
'is_first_move': False
|
||||
}
|
||||
}
|
||||
|
||||
await self.socket_a.send(json.dumps(start_msg_a))
|
||||
await self.socket_b.send(json.dumps(start_msg_b))
|
||||
|
||||
print("start message send to all players")
|
||||
|
||||
async def processPlayerMessage(self, player_id, json_str):
|
||||
if len(json_str) > 4096:
|
||||
# something is fishy here
|
||||
print("received strange message from client")
|
||||
|
||||
print("received message: " + json_str)
|
||||
|
||||
try:
|
||||
json_dict = json.loads(json_str)
|
||||
type = json_dict['type']
|
||||
data = json_dict['data']
|
||||
|
||||
if type == "move":
|
||||
await self._on_move(player_id, data)
|
||||
|
||||
elif type == "end_game":
|
||||
await self._on_end_game(player_id)
|
||||
|
||||
except Exception as e:
|
||||
print("" + str(e) + ": received wrong formated message")
|
||||
|
||||
async def _on_move(self, player_id, move_data):
|
||||
response = {'type': 'move_response'}
|
||||
response_data = {}
|
||||
|
||||
opponent_response = {'type': 'move'}
|
||||
opponent_response_data = {}
|
||||
|
||||
opponent_response_data['sub_x'] = move_data['sub_x']
|
||||
opponent_response_data['sub_y'] = move_data['sub_y']
|
||||
opponent_response_data['x'] = move_data['x']
|
||||
opponent_response_data['y'] = move_data['y']
|
||||
opponent_response['data'] = opponent_response_data
|
||||
|
||||
if player_id == self.current_player:
|
||||
|
||||
is_a = (self.player_a_id == player_id)
|
||||
current_socket = self.socket_a if is_a else self.socket_b
|
||||
opponent_socket = self.socket_b if is_a else self.socket_a
|
||||
|
||||
response_data['success'] = True
|
||||
response_data['msg'] = "move successful"
|
||||
|
||||
response['data'] = response_data
|
||||
|
||||
await opponent_socket.send(json.dumps(opponent_response))
|
||||
await current_socket.send(json.dumps(response))
|
||||
|
||||
# switch player
|
||||
self.current_player = self.player_b_id if is_a else self.player_a_id
|
||||
|
||||
else:
|
||||
print("received move from wrong player")
|
||||
|
||||
is_a = (self.player_a_id == player_id)
|
||||
current_socket = self.socket_a if is_a else self.socket_b
|
||||
|
||||
response_data["success"] = False
|
||||
response_data["msg"] = "not your turn!"
|
||||
|
||||
response['data'] = response_data
|
||||
|
||||
await current_socket.send(json.dumps(response))
|
||||
|
||||
async def _on_end_game(self, player_id):
|
||||
|
||||
if self.game_finished:
|
||||
return
|
||||
|
||||
is_a = (self.player_a_id == player_id)
|
||||
opponent_socket = self.socket_b if is_a else self.socket_a
|
||||
|
||||
opponent_response = {'type': 'end_game'}
|
||||
opponent_response['data'] = {'msg': 'game closed by opponent'}
|
||||
|
||||
await opponent_socket.send(json.dumps(opponent_response))
|
||||
|
||||
self.game_finished = True
|
81
main.py
81
main.py
@ -3,68 +3,77 @@ import websockets
|
||||
from settings import *
|
||||
import ssl
|
||||
|
||||
from game_manager import *
|
||||
import traceback
|
||||
|
||||
from session_manager import SessionManager
|
||||
from connection_handler import Connection, ConnectionHandler
|
||||
from match_manager import MatchManager
|
||||
from user_manager import UserManager
|
||||
from database_connection import DatabaseConnection
|
||||
import datetime
|
||||
|
||||
um = UserManager()
|
||||
sm = SessionManager(datetime.timedelta(hours=12))
|
||||
mm = MatchManager()
|
||||
ch = ConnectionHandler(sm, um, mm)
|
||||
|
||||
|
||||
DatabaseConnection(db_host,
|
||||
db_port,
|
||||
db_user,
|
||||
db_pw,
|
||||
db_db,
|
||||
db_charset)
|
||||
|
||||
|
||||
async def socket_worker(websocket, path):
|
||||
connection = None
|
||||
|
||||
registered = False
|
||||
id = None
|
||||
|
||||
print("new connection")
|
||||
|
||||
try:
|
||||
# get first message as register message
|
||||
raw_msg = await websocket.recv()
|
||||
|
||||
msg = json.loads(raw_msg)
|
||||
connection = await ch.new_connection(websocket, raw_msg)
|
||||
|
||||
if msg['type'] != 'register':
|
||||
print("got wrong registration")
|
||||
websocket.close()
|
||||
if connection is None:
|
||||
return
|
||||
|
||||
id = msg['data']['id']
|
||||
|
||||
registered = await register_user(id, websocket)
|
||||
|
||||
register_response = {
|
||||
'type': 'register_response', 'data': {
|
||||
'success': True, 'msg': '...'}}
|
||||
|
||||
if not registered:
|
||||
register_response['data']['success'] = False
|
||||
|
||||
await websocket.send(json.dumps(register_response))
|
||||
websocket.close()
|
||||
return
|
||||
|
||||
await websocket.send(json.dumps(register_response))
|
||||
|
||||
print("successful redisterd user " + id)
|
||||
print("successfull logged in user: " + connection.user_name)
|
||||
|
||||
async for m in websocket:
|
||||
await process_message(id, m)
|
||||
await ch.handle_message(connection, m)
|
||||
|
||||
except Exception as e:
|
||||
# TODO: each disconnect is an exception so far
|
||||
if id is not None:
|
||||
print("catched exception in worker for user: " + id + ": " + str(e))
|
||||
if connection is not None:
|
||||
print("catched exception in worker for user: " +
|
||||
connection.user_name + ": " + str(e))
|
||||
else:
|
||||
print("catched exception in worker for unknown user")
|
||||
print("catched exception in worker for unknown user: " + str(e))
|
||||
|
||||
print(traceback.print_exc())
|
||||
|
||||
finally:
|
||||
if registered:
|
||||
await unregister_user(id)
|
||||
id = None
|
||||
if connection:
|
||||
id = connection.user_name
|
||||
await ch.disconnect(connection)
|
||||
await connection.close()
|
||||
|
||||
if id is None:
|
||||
if connection is None:
|
||||
id = "unknown_user"
|
||||
print("close connection to user: " + id)
|
||||
|
||||
if cert_file is not None and key_file is not None:
|
||||
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
|
||||
ssl_context.load_cert_chain(cert_file, keyfile=key_file)
|
||||
|
||||
start_server = websockets.serve(socket_worker, host='', port=5555, ssl=ssl_context)
|
||||
start_server = websockets.serve(
|
||||
socket_worker, host='', port=server_port, ssl=ssl_context)
|
||||
|
||||
else:
|
||||
start_server = websockets.serve(
|
||||
socket_worker, host='', port=server_port)
|
||||
|
||||
asyncio.get_event_loop().run_until_complete(start_server)
|
||||
asyncio.get_event_loop().run_forever()
|
||||
|
192
match.py
Normal file
192
match.py
Normal file
@ -0,0 +1,192 @@
|
||||
|
||||
import numpy as np
|
||||
import json
|
||||
import base64
|
||||
|
||||
# decoder/encoder took from: https://stackoverflow.com/a/19271311
|
||||
|
||||
|
||||
def Base64Encode(ndarray):
|
||||
return json.dumps([str(ndarray.dtype), base64.b64encode(ndarray), ndarray.shape])
|
||||
|
||||
|
||||
def Base64Decode(jsonDump):
|
||||
loaded = json.loads(jsonDump)
|
||||
dtype = np.dtype(loaded[0])
|
||||
arr = np.frombuffer(base64.decodestring(loaded[1]), dtype)
|
||||
if len(loaded) > 2:
|
||||
return arr.reshape(loaded[2])
|
||||
return arr
|
||||
|
||||
|
||||
def SimpleEncode(ndarray):
|
||||
return json.dumps(ndarray.tolist())
|
||||
|
||||
|
||||
def SimpleDecode(jsonDump):
|
||||
return np.array(json.loads(jsonDump))
|
||||
|
||||
|
||||
FIELD_EMPTY = 0
|
||||
FIELD_USER_A = 1
|
||||
FIELD_USER_B = 2
|
||||
FIELD_DRAW = 3
|
||||
|
||||
|
||||
class Match(object):
|
||||
def __init__(self, n, match_id, player_a_name, player_b_name, json_state=None):
|
||||
self.n = n
|
||||
self.id = match_id
|
||||
self.complete_field = np.zeros(shape=(n*n, n*n), dtype=int)
|
||||
self.global_field = np.zeros(shape=(n, n), dtype=int)
|
||||
self.player_won = None
|
||||
self.game_over = False
|
||||
self.last_move = None
|
||||
self.is_player_a = True
|
||||
self.player_a_name = player_a_name
|
||||
self.player_b_name = player_b_name
|
||||
|
||||
if json_state is not None:
|
||||
self.from_json_state(json_state)
|
||||
|
||||
def from_json_state(self, json_state):
|
||||
match_obj = json.loads(json_state)
|
||||
self.complete_field = np.array(match_obj['complete_field'], dtype=int)
|
||||
self.global_field = np.array(match_obj['global_field'], dtype=int)
|
||||
self.player_won = match_obj['player_won']
|
||||
self.game_over = match_obj['game_over']
|
||||
self.last_move = match_obj['last_move']
|
||||
self.is_player_a = match_obj['active_player'] == self.player_a_name
|
||||
|
||||
def to_json_state(self):
|
||||
match_obj = {
|
||||
'complete_field': self.complete_field.tolist(),
|
||||
'global_field': self.global_field.tolist(),
|
||||
'last_move': self.last_move,
|
||||
'game_over': self.game_over,
|
||||
'player_won': self.player_won,
|
||||
'active_player': self.player_a_name if self.is_player_a else self.player_b_name,
|
||||
'player_a': self.player_a_name,
|
||||
'player_b': self.player_b_name
|
||||
}
|
||||
|
||||
return json.dumps(match_obj)
|
||||
|
||||
def switch_player_names(self):
|
||||
tmp = self.player_a_name
|
||||
self.player_a_name = self.player_b_name
|
||||
self.player_b_name = tmp
|
||||
|
||||
def get_current_player(self):
|
||||
return self.player_a_name if self.is_player_a else self.player_b_name
|
||||
|
||||
def is_move_valid(self, sub_x, sub_y, x, y):
|
||||
if sub_x < 0 or sub_x >= self.n:
|
||||
return False
|
||||
if sub_y < 0 or sub_y >= self.n:
|
||||
return False
|
||||
|
||||
if x < 0 or x >= self.n:
|
||||
return False
|
||||
if y < 0 or y >= self.n:
|
||||
return False
|
||||
|
||||
if (self.last_move is not None):
|
||||
last_x = self.last_move['x']
|
||||
last_y = self.last_move['y']
|
||||
last_sub_x = self.last_move['sub_x']
|
||||
last_sub_y = self.last_move['sub_y']
|
||||
|
||||
if sub_x != last_x and self.global_field[last_y, last_x] == FIELD_EMPTY:
|
||||
# user is not allowed to place everywhere! wrong move!
|
||||
return False
|
||||
|
||||
if sub_y != last_y and self.global_field[last_y, last_x] == FIELD_EMPTY:
|
||||
return False
|
||||
|
||||
if self.complete_field[sub_y * self.n + y][sub_x * self.n + x] != FIELD_EMPTY:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def is_full(self, field):
|
||||
return not field.__contains__(FIELD_EMPTY)
|
||||
|
||||
def check_win(self, field, x, y):
|
||||
is_col = True
|
||||
is_row = True
|
||||
is_main_diag = False
|
||||
is_sec_diag = False
|
||||
|
||||
val = field[y, x]
|
||||
|
||||
for i in range(self.n):
|
||||
if (field[i, x] != val):
|
||||
is_col = False
|
||||
break
|
||||
|
||||
for i in range(self.n):
|
||||
if (field[y, i] != val):
|
||||
is_row = False
|
||||
break
|
||||
|
||||
if x == y:
|
||||
is_main_diag = True
|
||||
for i in range(self.n):
|
||||
if field[i, i] != val:
|
||||
is_main_diag = False
|
||||
break
|
||||
|
||||
if x + y == self.n - 1:
|
||||
is_sec_diag = True
|
||||
for i in range(self.n):
|
||||
if field[i, self.n - i - 1] != val:
|
||||
is_sec_diag = False
|
||||
break
|
||||
|
||||
return is_col or is_row or is_main_diag or is_sec_diag
|
||||
|
||||
def move(self, move_dict):
|
||||
sub_x = int(move_dict['sub_x'])
|
||||
sub_y = int(move_dict['sub_y'])
|
||||
x = int(move_dict['x'])
|
||||
y = int(move_dict['y'])
|
||||
|
||||
abs_x = sub_x * self.n + x
|
||||
abs_y = sub_y * self.n + y
|
||||
|
||||
player_mark = FIELD_USER_A if self.is_player_a else FIELD_USER_B
|
||||
|
||||
if not self.is_move_valid(sub_x, sub_y, x, y):
|
||||
print("invalid move")
|
||||
return False
|
||||
|
||||
# else: move!
|
||||
self.complete_field[abs_y, abs_x] = player_mark
|
||||
|
||||
# encode move:
|
||||
self.last_move = {'sub_x': sub_x, 'sub_y': sub_y, 'x': x, 'y': y}
|
||||
|
||||
# check whether this indicates changes in the global field:
|
||||
if self.global_field[sub_y, sub_x] != FIELD_EMPTY:
|
||||
print("field not empty")
|
||||
return False
|
||||
|
||||
subgrid = self.complete_field[sub_y * self.n: (
|
||||
sub_y + 1) * self.n, sub_x * self.n: (sub_x + 1) * self.n]
|
||||
|
||||
if self.check_win(subgrid, x, y):
|
||||
self.global_field[sub_y, sub_x] = player_mark
|
||||
if self.check_win(self.global_field, sub_x, sub_y):
|
||||
self.game_over = True
|
||||
self.player_won = self.player_a_name if self.is_player_a else self.player_b_name
|
||||
|
||||
elif self.is_full(subgrid):
|
||||
self.global_field[sub_y, sub_x] = FIELD_DRAW
|
||||
if self.is_full(self.global_field):
|
||||
self.game_over = True
|
||||
self.player_won = None
|
||||
|
||||
self.is_player_a = not self.is_player_a
|
||||
|
||||
return True
|
64
match_manager.py
Normal file
64
match_manager.py
Normal file
@ -0,0 +1,64 @@
|
||||
from database_connection import DatabaseConnection, SQLInjectionError, get_sql_time
|
||||
from user import User
|
||||
import datetime
|
||||
import uuid
|
||||
import settings
|
||||
from match import Match
|
||||
|
||||
|
||||
class MatchManager(object):
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def get_match(self, id):
|
||||
query = f"SELECT * FROM matches WHERE id='{id}'"
|
||||
result = DatabaseConnection.global_single_query(query)
|
||||
if len(result) == 0:
|
||||
return None
|
||||
match = Match(n=settings.n, match_id=id, player_a_name=result[0]['user_a'],
|
||||
player_b_name=result[0]['user_b'], json_state=result[0]['match_state'])
|
||||
return match
|
||||
|
||||
def get_matches_for_user(self, user_name):
|
||||
query = f"SELECT * FROM matches WHERE user_a='{user_name}' OR user_b='{user_name}'"
|
||||
return DatabaseConnection.global_single_query(query)
|
||||
|
||||
def create_new_match(self, user_a, user_b):
|
||||
match_id = uuid.uuid4().hex
|
||||
# check if already existent (but should not be the case)
|
||||
if len(DatabaseConnection.global_single_query(f"SELECT id FROM matches WHERE id='{match_id}'")) > 0:
|
||||
return self.create_new_match(user_a, user_b)
|
||||
|
||||
match = Match(n=settings.n, match_id=match_id, player_a_name=user_a, player_b_name=user_b)
|
||||
now = datetime.datetime.now()
|
||||
query = f"INSERT INTO matches (id, user_a, user_b, match_state, active_user, last_active) VALUES ('{match_id}', '{user_a}', '{user_b}', '{match.to_json_state()}', '{match.get_current_player()}','{get_sql_time(now)}')"
|
||||
DatabaseConnection.global_single_execution(query)
|
||||
return match
|
||||
|
||||
def update_match(self, match_id, match):
|
||||
now = get_sql_time(datetime.datetime.now())
|
||||
query = f"UPDATE matches SET match_state='{match.to_json_state()}', active_user='{match.get_current_player()}', last_active='{now}' WHERE id='{match_id}'"
|
||||
DatabaseConnection.global_single_execution(query)
|
||||
|
||||
def apply_move(self, move_data):
|
||||
match = self.get_match(move_data['id'])
|
||||
if match is None:
|
||||
return None
|
||||
|
||||
if not match.move(move_data):
|
||||
print("error applying match move")
|
||||
return None
|
||||
|
||||
|
||||
self.update_match(move_data['id'], match)
|
||||
print("updated match")
|
||||
return match
|
||||
|
||||
def is_match(self, player_a, player_b):
|
||||
query = f"SELECT * FROM matches WHERE (user_a='{player_a}' AND user_b='{player_b}') OR (user_a='{player_b}' AND user_b='{player_a}')"
|
||||
return len(DatabaseConnection.global_single_query(query))
|
||||
|
||||
def delete_match(self, match_id):
|
||||
query = f"DELETE FROM matches WHERE id='{match_id}'"
|
||||
DatabaseConnection.global_single_execution(query)
|
||||
|
1
message_handler.py
Normal file
1
message_handler.py
Normal file
@ -0,0 +1 @@
|
||||
|
67
session_manager.py
Normal file
67
session_manager.py
Normal file
@ -0,0 +1,67 @@
|
||||
from database_connection import DatabaseConnection, SQLInjectionError, get_sql_time
|
||||
from user import User
|
||||
import datetime
|
||||
import uuid
|
||||
|
||||
|
||||
class SessionManager(object):
|
||||
def __init__(self, session_lifespan_timedelta):
|
||||
self.session_lifespan_timedelta = session_lifespan_timedelta
|
||||
|
||||
def get_session_by_id(self, session_id):
|
||||
query = f"SELECT * FROM sessions WHERE id='{session_id}'"
|
||||
return DatabaseConnection.global_single_query(query)
|
||||
|
||||
def touch_session(self, session_id):
|
||||
query = f"UPDATE sessions SET last_seen='{datetime.datetime.now()}' WHERE id='{session_id}'"
|
||||
DatabaseConnection.global_single_execution(query)
|
||||
|
||||
def get_session_by_registered_user(self, user_name):
|
||||
query = f"SELECT * FROM sessions WHERE registered_user='{user_name}'"
|
||||
return DatabaseConnection.global_single_query(query)
|
||||
|
||||
def get_session_by_temp_user(self, user_name):
|
||||
query = f"SELECT * FROM sessions WHERE temp_user='{user_name}'"
|
||||
return DatabaseConnection.global_single_query(query)
|
||||
|
||||
def create_session_for_registered_user(self, user_name):
|
||||
new_id = uuid.uuid4().hex
|
||||
# check if already existent (but should not be the case)
|
||||
if len(DatabaseConnection.global_single_query(f"SELECT id FROM sessions WHERE id='{new_id}'")) > 0:
|
||||
# okay, next try:
|
||||
return self.create_session_for_registered_user(user_name)
|
||||
|
||||
# delete other active sessions:
|
||||
query = f"DELETE FROM sessions WHERE registered_user='{user_name}'"
|
||||
DatabaseConnection.global_single_execution(query)
|
||||
|
||||
query = f"INSERT INTO sessions (id, registered_user, temp_user, last_seen) VALUES ( '{new_id}', '{user_name}', NULL, '{datetime.datetime.now()}')"
|
||||
DatabaseConnection.global_single_execution(query)
|
||||
return new_id
|
||||
|
||||
def create_session_for_temp_user(self, user_name):
|
||||
new_id = uuid.uuid4().hex
|
||||
# check if already existent (but should not be the case)
|
||||
if len(DatabaseConnection.global_single_query(f"SELECT id FROM sessions WHERE id='{new_id}'")) > 0:
|
||||
# okay, next try:
|
||||
return self.create_session_for_registered_user(user_name)
|
||||
|
||||
# delete other active sessions:
|
||||
query = f"DELETE FROM sessions WHERE temp_user='{user_name}'"
|
||||
DatabaseConnection.global_single_execution(query)
|
||||
|
||||
query = f"INSERT INTO sessions (id, registered_user, temp_user, last_seen) VALUES ( '{new_id}', NULL, '{user_name}', '{datetime.datetime.now()}')"
|
||||
DatabaseConnection.global_single_execution(query)
|
||||
return new_id
|
||||
|
||||
def delete_session(self, session_id):
|
||||
query = f"DELETE FROM sessions WHERE id='{session_id}'"
|
||||
DatabaseConnection.global_single_execution(query)
|
||||
|
||||
def revoke_inactive_sessions(self):
|
||||
revoke_time = datetime.datetime.now() - self.session_lifespan_timedelta
|
||||
query = f"SELECT * from sessions WHERE last_seen < '{get_sql_time(revoke_time)}'"
|
||||
revoked_sessions = DatabaseConnection.global_single_query(query)
|
||||
query = f"DELETE FROM sessions WHERE last_seen < '{get_sql_time(revoke_time)}'"
|
||||
DatabaseConnection.global_single_execution(query)
|
||||
return revoked_sessions
|
14
settings.py
14
settings.py
@ -1,2 +1,16 @@
|
||||
cert_file = None
|
||||
key_file = None
|
||||
|
||||
server_port = 5556
|
||||
|
||||
db_host = "127.0.0.1"
|
||||
db_port = 3306
|
||||
|
||||
db_user = None
|
||||
db_pw = None
|
||||
db_db = None
|
||||
|
||||
db_charset = 'utf8mb4'
|
||||
|
||||
# field dimension
|
||||
n = 3
|
||||
|
10
user.py
Normal file
10
user.py
Normal file
@ -0,0 +1,10 @@
|
||||
class User(object):
|
||||
def __init__(self, id, name):
|
||||
self.id = id
|
||||
self.name = name
|
||||
|
||||
def get_id(self):
|
||||
return self.id
|
||||
|
||||
def get_name(self):
|
||||
return self.name
|
75
user_manager.py
Normal file
75
user_manager.py
Normal file
@ -0,0 +1,75 @@
|
||||
from database_connection import DatabaseConnection, SQLInjectionError
|
||||
from user import User
|
||||
import datetime
|
||||
import hashlib
|
||||
import uuid
|
||||
|
||||
|
||||
class UserManager(object):
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def get_user(self, user_name):
|
||||
query = f"SELECT name, last_seen FROM users where name='{user_name}'"
|
||||
return DatabaseConnection.global_single_query(query)
|
||||
|
||||
def delete_user(self, user_name):
|
||||
query = f"DELETE FROM users where name='{user_name}'"
|
||||
DatabaseConnection.global_single_execution(query)
|
||||
|
||||
# remove from friends:
|
||||
query = f"DELETE FROM friends WHERE user='{user_name}'"
|
||||
DatabaseConnection.global_single_execution(query)
|
||||
|
||||
query = f"DELETE FROM friends WHERE friend='{user_name}'"
|
||||
DatabaseConnection.global_single_execution(query)
|
||||
|
||||
def verify_user(self, user_name, pw):
|
||||
query = f"SELECT * FROM users where name='{user_name}'"
|
||||
users = DatabaseConnection.global_single_query(query)
|
||||
if len(self.get_user(user_name)) == 0:
|
||||
return False
|
||||
|
||||
user = users[0]
|
||||
pw_salt = user['pw_salt']
|
||||
stored_pw_hash = user['pw_hash']
|
||||
|
||||
pw_hash = hashlib.sha512(pw.encode() + pw_salt.encode()).hexdigest()
|
||||
return stored_pw_hash == pw_hash
|
||||
|
||||
def create_user(self, user_name, pw):
|
||||
assert len(self.get_user(user_name)) == 0
|
||||
pw_salt = uuid.uuid4().hex
|
||||
pw_hash = hashlib.sha512(pw.encode() + pw_salt.encode()).hexdigest()
|
||||
query = f"INSERT INTO users (name, pw_hash, pw_salt, last_seen) VALUES ( '{user_name}', '{pw_hash}', '{pw_salt}', '{datetime.datetime.now()}')"
|
||||
DatabaseConnection.global_single_execution(query)
|
||||
|
||||
def touch_user(self, user_name):
|
||||
matches = self.get_user(user_name)
|
||||
assert len(matches) == 1
|
||||
query = f"UPDATE users SET last_seen='{datetime.datetime.now()}' WHERE name='{user_name}'"
|
||||
DatabaseConnection.global_single_execution(query)
|
||||
|
||||
def add_friend_to_user(self, user_name, friend_name):
|
||||
if len(self.get_user(friend_name)) > 0:
|
||||
query = f"INSERT INTO friends (user, friend) VALUES ( '{user_name}', '{friend_name}')"
|
||||
DatabaseConnection.global_single_execution(query)
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_friends_for_user(self, user_name):
|
||||
query = f"SELECT friend FROM friends WHERE user='{user_name}'"
|
||||
tmp = DatabaseConnection.global_single_query(query)
|
||||
friends = set()
|
||||
for entry in tmp:
|
||||
friends.add(entry['friend'])
|
||||
return friends
|
||||
|
||||
def remove_friend_from_user(self, user_name, friend_name):
|
||||
query = f"DELETE FROM friends WHERE user='{user_name}' AND friend='{friend_name}'"
|
||||
DatabaseConnection.global_single_execution(query)
|
||||
|
||||
def get_all_users(self):
|
||||
query = "SELECT name, last_seen FROM users"
|
||||
return DatabaseConnection.global_single_query(query)
|
||||
|
Loading…
Reference in New Issue
Block a user