summaryrefslogtreecommitdiff
path: root/server/piztor/game_server.py
diff options
context:
space:
mode:
Diffstat (limited to 'server/piztor/game_server.py')
-rw-r--r--server/piztor/game_server.py1006
1 files changed, 1006 insertions, 0 deletions
diff --git a/server/piztor/game_server.py b/server/piztor/game_server.py
new file mode 100644
index 0000000..cc1bd89
--- /dev/null
+++ b/server/piztor/game_server.py
@@ -0,0 +1,1006 @@
+from twisted.internet.protocol import Protocol
+from twisted.internet.protocol import Factory
+from twisted.internet.endpoints import TCP4ServerEndpoint
+from twisted.protocols.policies import TimeoutMixin
+
+from sqlalchemy import create_engine, and_
+from sqlalchemy.orm import sessionmaker
+from sqlalchemy.orm.exc import NoResultFound, MultipleResultsFound
+
+from collections import deque
+from time import time
+
+import struct
+import os
+import logging
+
+from exc import *
+from model import *
+
+def get_hex(data):
+ return "".join([hex(ord(c))[2:].zfill(2) for c in data])
+
+db_path = "root:helloworld@localhost/piztor2"
+#db_path = "piztor.sqlite"
+FORMAT = "%(asctime)-15s %(message)s"
+logging.basicConfig(format = FORMAT)
+logger = logging.getLogger('piztor_server')
+logger.setLevel(logging.INFO)
+engine = create_engine('mysql://' + db_path, echo = False, pool_size = 1024)
+
+class _PermCode:
+ normal = 0x00
+ section = 0x01
+ company = 0x02
+
+class _SectionSize:
+ LENGTH = 4
+ OPT_ID = 1
+ STATUS = 1
+ USER_ID = 4
+ USER_TOKEN = 32
+ GROUP_ID = 2
+ LATITUDE = 8
+ LONGITUDE = 8
+ PADDING = 1
+ DEADLINE = 4
+ MARKER_ID = 1
+
+_MAX_AUTH_HEAD_SIZE = _SectionSize.USER_TOKEN + \
+ MAX_USERNAME_SIZE + \
+ _SectionSize.PADDING
+_HEADER_SIZE = _SectionSize.LENGTH + \
+ _SectionSize.OPT_ID
+
+_MAX_TEXT_MESG_SIZE = 1024
+_MAX_SUB_LIST_SIZE = 10
+_MAX_PENDING_PUSH = 10
+_INIT_MARKER_NUM = 5
+
+class _OptCode:
+ user_auth = 0x00
+ update_location = 0x01
+ user_info = 0x02
+ update_subscription = 0x03
+ user_logout = 0x04
+ open_push_tunnel = 0x05
+ send_text_mesg = 0x06
+ set_marker = 0x07
+ change_password = 0x08
+ check_in = 0x09
+ game_start = 0x0A
+
+class _StatusCode:
+ sucess = 0x00
+ auth_fail = 0x01
+ insuf_lvl = 0x02
+ wrong_pass = 0x03
+ grp_not_found = 0x04
+ checkin_fail = 0x05
+
+class PushData(object):
+ from hashlib import sha256
+ def pack(self, optcode, data):
+ self.finger_print = sha256(data + struct.pack("!d", time())).digest()
+ buff = struct.pack("!B32s", optcode, self.finger_print)
+ buff += data
+ buff = struct.pack("!L", _SectionSize.LENGTH + len(buff)) + buff
+ self.data = buff
+
+class PushTextMesgData(PushData):
+ def __init__(self, mesg):
+ self.pack(0x00, mesg + chr(0))
+
+class PushLocationData(PushData):
+ def __init__(self, uid, lat, lng):
+ self.pack(0x01, struct.pack("!Ldd", uid, lat, lng))
+
+class PushMarkerData(PushData):
+ def __init__(self, perm, lat, lng, deadline, mid, score):
+ self.pack(0x02, struct.pack("!BddlBL", perm, lat, lng, deadline,
+ mid, score))
+class PushMarkerRemovalData(PushData):
+ def __init__(self, mid):
+ self.pack(0x03, struct.pack("!B", mid))
+
+class PushScoreData(PushData):
+ def __init__(self, score1, score2):
+ self.pack(0x04, struct.pack("!LL", score1, score2))
+
+class PushTunnel(object):
+ def __init__(self, uid):
+ self.uid = uid
+ self.pending = deque()
+ self.conn = None
+ self.blocked = False
+
+ def close(self):
+ print "closing TUNNEL"
+ if self.conn:
+ print "loseConn called"
+ self.conn.transport.loseConnection()
+
+ def add(self, pdata):
+ #logger.info("-- Push data enqued --")
+ self.pending.append(pdata)
+ if not self.blocked and len(self.pending) > _MAX_PENDING_PUSH:
+ #logger.info("-- Push queue is full, discarded an obsolete push --")
+ self.pending.popleft() # discard old push
+
+ def on_receive(self, data):
+ front = self.pending.popleft()
+ length, optcode, fingerprint = struct.unpack("!LB32s", data)
+ if front.finger_print != fingerprint:
+ raise PiztorError
+ #logger.info("-- Push data confirmed by client %s --", str(self.uid))
+ self.blocked = False
+ self.push()
+
+ def push(self):
+ if self.blocked:
+ return
+ print "Pushing via " + str(self.uid)
+ print "Pending size: " + str(len(self.pending))
+ #logger.info("Pushing...")
+ if (self.conn is None) or len(self.pending) == 0:
+ return
+ front = self.pending.popleft()
+ self.pending.appendleft(front)
+ self.conn.transport.write(front.data)
+ #logger.info("-- Wrote push: %s --", get_hex(front.data))
+ self.blocked = True
+
+ def clear(self):
+ self.pending.clear()
+
+ def connect(self, conn):
+ self.clear()
+ conn.tunnel = self
+ if self.conn: # only one long-connection per user
+ self.conn.transport.loseConnection()
+ self.conn = conn
+
+ def on_connection_lost(self, conn):
+ print "TUNNEL closed"
+ if conn == self.conn:
+ self.conn = None
+
+def pack_uid(user):
+ return struct.pack("!L", user.id)
+
+def pack_username(user):
+ buff = user.username
+ buff += chr(0)
+ return buff
+
+def pack_nickname(user):
+ buff = user.nickname
+ buff += chr(0)
+ return buff
+
+def pack_sex(user):
+ return struct.pack("!B", 0x01 if user.sex else 0x00)
+
+def pack_gid(user):
+ return struct.pack("!H", user.sec_id)
+
+def pack_lat(user):
+ return struct.pack("!d", user.location.lat)
+
+def pack_lng(user):
+ return struct.pack("!d", user.location.lng)
+
+def pack_perm(user):
+ return struct.pack("!B", user.perm)
+
+class RequestHandler(object):
+ push_tunnels = dict()
+ def __init__(self):
+ Session = sessionmaker(bind = engine)
+ self.session = Session()
+
+ def __del__(self):
+ self.session.close()
+
+ def check_size(self, tr_data):
+ if len(tr_data) > self._max_tr_data_size:
+ raise BadReqError("Authentication: Request size is too large")
+
+ @classmethod
+ def get_uauth(cls, token, username, session):
+ try:
+ uauth = session.query(UserAuth) \
+ .filter(UserAuth.token == token).one()
+
+ if uauth.user.username != username:
+ logger.warning("Token and username mismatch")
+ return None
+ uid = uauth.uid
+ if not cls.push_tunnels.has_key(uid):
+ cls.push_tunnels[uid] = PushTunnel(uid)
+ return uauth
+
+ except NoResultFound:
+ logger.warning("Incorrect token")
+ return None
+
+ except MultipleResultsFound:
+ raise DBCorruptionError()
+
+ @classmethod
+ def trunc_padding(cls, data):
+ leading = bytes()
+ for i in xrange(len(data)):
+ ch = data[i]
+ if ch == '\x00':
+ return (leading, data[i + 1:])
+ else:
+ leading += ch
+ # padding not found
+ return (None, data)
+
+ def pack(self, data):
+ return struct.pack("!LB",
+ _SectionSize.LENGTH + \
+ _SectionSize.OPT_ID + \
+ len(data), self._optcode) + data
+
+ _code_map = { 0x01 : pack_uid,
+ 0x02 : pack_username,
+ 0x03 : pack_nickname,
+ 0x04 : pack_sex,
+ 0x05 : pack_gid,
+ 0x06 : pack_lat,
+ 0x07 : pack_lng,
+ 0x08 : pack_perm}
+
+ @classmethod
+ def pack_info_entry(cls, user, entry_code):
+ pack_method = cls._code_map[entry_code]
+ info_key = entry_code
+ return struct.pack("!B", info_key) + pack_method(user)
+
+ @classmethod
+ def pack_user_entry(cls, user):
+ buff = bytes()
+ for entry_code in cls._code_map:
+ buff += cls.pack_info_entry(user, entry_code)
+ buff += chr(0)
+ return buff
+
+ @classmethod
+ def pack_sub_list(cls, user):
+ buff = bytes()
+ for grp in user.sub:
+ buff += struct.pack("!H", grp.id)
+ buff += chr(0)
+ return buff
+
+ @classmethod
+ def unpack_sub_list(cls, data):
+ res = list()
+ idx = 0
+ end = len(data) - 1
+ while idx < end:
+ res.append(struct.unpack("!H",
+ data[idx:idx + _SectionSize.GROUP_ID])[0])
+ idx += _SectionSize.GROUP_ID
+ return res
+
+
+class UserAuthHandler(RequestHandler):
+
+ _optcode = _OptCode.user_auth
+ _max_tr_data_size = MAX_USERNAME_SIZE + \
+ _SectionSize.PADDING + \
+ MAX_PASSWORD_SIZE + \
+ _SectionSize.PADDING
+
+ _failed_response = \
+ lambda self: self.pack(struct.pack("!B", _StatusCode.auth_fail))
+
+
+ def handle(self, tr_data, conn):
+ self.check_size(tr_data)
+ logger.info("Reading auth data...")
+ pos = -1
+ for i in xrange(0, len(tr_data)):
+ if tr_data[i] == '\x00':
+ pos = i
+ break
+ if pos == -1:
+ raise BadReqError("Authentication: Malformed request body")
+
+ username = tr_data[0:pos]
+ password = tr_data[pos + 1:-1]
+ logger.info("Trying to login with " \
+ "(username = {0}, password = {1})" \
+ .format(username, password))
+
+ try:
+ user = self.session.query(UserModel) \
+ .filter(UserModel.username == username).one()
+ except NoResultFound:
+ logger.info("No such user: {0}".format(username))
+ return self._failed_response()
+
+ except MultipleResultsFound:
+ raise DBCorruptionError()
+
+ uauth = user.auth
+ if uauth is None:
+ raise DBCorruptionError()
+ if not uauth.check_password(password):
+ logger.info("Incorrect password: {0}".format(password))
+ return self._failed_response()
+ else:
+ logger.info("Logged in sucessfully: {0}".format(username))
+ pt = RequestHandler.push_tunnels
+ uid = uauth.uid
+ if pt.has_key(uid): # close the old push tunnel
+ pt[uid].close()
+ del pt[uid]
+ uauth.regen_token()
+ #logger.info("New token generated: " + get_hex(uauth.token))
+ self.session.commit()
+ # STATUS | USER_TOKEN
+ reply = struct.pack("!B32s", _StatusCode.sucess, uauth.token)
+ # USER_ENTRY
+ reply += RequestHandler.pack_user_entry(user)
+ reply += RequestHandler.pack_sub_list(user)
+ score1 = self.session.query(GroupInfo) \
+ .filter(GroupInfo.id == 5889).one().score
+ score2 = self.session.query(GroupInfo) \
+ .filter(GroupInfo.id == 5890).one().score
+
+ for mrk in self.session.query(MarkerInfo).all():
+ if mrk.status != MARKER_DISPLAYED:
+ continue
+ reply += struct.pack("!BddlBL", 2, mrk.lat, mrk.lng, 0x7fffffff,
+ mrk.id, mrk.score)
+ reply += struct.pack("!LL", score1, score2)
+ return self.pack(reply)
+
+
+class UpdateLocationHandler(RequestHandler):
+
+ _optcode = _OptCode.update_location
+ _max_tr_data_size = _MAX_AUTH_HEAD_SIZE + \
+ _SectionSize.LATITUDE + \
+ _SectionSize.LONGITUDE
+
+ def handle(self, tr_data, conn):
+ self.check_size(tr_data)
+ logger.info("Reading location update data...")
+ try:
+ token, = struct.unpack("!32s", tr_data[:32])
+ username, tail = RequestHandler.trunc_padding(tr_data[32:])
+ if username is None:
+ raise struct.error
+ lat, lng = struct.unpack("!dd", tail)
+ except struct.error:
+ raise BadReqError("Update location: Malformed request body")
+
+ logger.info("Trying to update location with "
+ "(token = {0}, username = {1}, lat = {2}, lng = {3})"\
+ .format(get_hex(token), username, lat, lng))
+
+ uauth = RequestHandler.get_uauth(token, username, self.session)
+ # Authentication failure
+ if uauth is None:
+ logger.warning("Authentication failure")
+ return self.pack(struct.pack("!B", _StatusCode.auth_fail))
+
+ loc = uauth.user.location
+ loc.lat = lat
+ loc.lng = lng
+
+ self.session.commit()
+ logger.info("Location is updated sucessfully")
+
+ pt = RequestHandler.push_tunnels
+ u = uauth.user
+ comp = self.session.query(GroupInfo) \
+ .filter(GroupInfo.id == u.comp_id).one()
+ sec = self.session.query(GroupInfo) \
+ .filter(GroupInfo.id == u.sec_id).one()
+
+ pdata = PushLocationData(u.id, lat, lng)
+ for user in comp.subscribers:
+ uid = user.id
+ if pt.has_key(uid):
+ tunnel = pt[uid]
+ tunnel.add(pdata)
+ tunnel.push()
+
+ return self.pack(struct.pack("!B", _StatusCode.sucess))
+
+
+class UserInfoHandler(RequestHandler):
+
+ _optcode = _OptCode.user_info
+ _max_tr_data_size = _MAX_AUTH_HEAD_SIZE + \
+ _SectionSize.GROUP_ID
+
+ _failed_response = \
+ lambda self : self.pack(struct.pack("!B", _StatusCode.auth_fail))
+
+
+ def handle(self, tr_data, conn):
+ self.check_size(tr_data)
+ logger.info("Reading user info request data...")
+ try:
+ token, = struct.unpack("!32s", tr_data[:32])
+ username, tail = RequestHandler.trunc_padding(tr_data[32:])
+ if username is None:
+ raise struct.error
+ gid, = struct.unpack("!H", tail)
+ except struct.error:
+ raise BadReqError("User info request: Malformed request body")
+
+# logger.info("Trying to get user info with " \
+# "(token = {0}, gid = {1})" \
+# .format(get_hex(token), gid))
+
+ uauth = RequestHandler.get_uauth(token, username, self.session)
+ # Auth failure
+ if uauth is None:
+ logger.warning("Authentication failure")
+ return self._failed_response()
+ # TODO: check the relationship between user and quser
+ u = uauth.user
+
+ grp = self.session.query(UserModel) \
+ .filter(UserModel.sec_id == gid).all()
+ grp += self.session.query(UserModel) \
+ .filter(UserModel.comp_id == gid).all()
+
+ reply = struct.pack("!B", _StatusCode.sucess)
+ for user in grp:
+ reply += RequestHandler.pack_user_entry(user)
+ return self.pack(reply)
+
+class UpdateSubscription(RequestHandler):
+
+ _optcode = _OptCode.update_subscription
+ _max_tr_data_size = _MAX_AUTH_HEAD_SIZE + \
+ _SectionSize.GROUP_ID * _MAX_SUB_LIST_SIZE + \
+ _SectionSize.PADDING
+
+ def _find_group(self, gid):
+ q = self.session.query(GroupInfo).filter(GroupInfo.id == gid)
+ entry = q.first()
+ if not entry:
+ raise BadReqError("Group not found")
+ self.session.commit()
+ return entry
+
+ def handle(self, tr_data, conn):
+
+ self.check_size(tr_data)
+ logger.info("Reading update subscription data...")
+ try:
+ token, = struct.unpack("!32s", tr_data[:32])
+ username, tail = RequestHandler.trunc_padding(tr_data[32:])
+ if username is None:
+ raise struct.error
+ sub_list = RequestHandler.unpack_sub_list(tail)
+ except struct.error:
+ raise BadReqError("Update Subscription: Malformed request body")
+
+# logger.info("Trying to update subscription with "
+# "(token = {0}, username = {1}, grps = {2})"\
+# .format(get_hex(token), username, str(sub_list)))
+
+ uauth = RequestHandler.get_uauth(token, username, self.session)
+ # Authentication failure
+ if uauth is None:
+ logger.warning("Authentication failure")
+ return self.pack(struct.pack("!B", _StatusCode.auth_fail))
+
+ try:
+ uauth.user.sub = map(self._find_group, sub_list)
+ except BadReqError:
+ return self.pack(struct.pack("!B", _StatusCode.grp_not_found))
+ self.session.commit()
+ logger.info("Subscription is updated sucessfully")
+
+ return self.pack(struct.pack("!B", _StatusCode.sucess))
+
+class UserLogoutHandler(RequestHandler):
+
+ _optcode = _OptCode.user_logout
+ _max_tr_data_size = _MAX_AUTH_HEAD_SIZE
+
+ def handle(self, tr_data, conn):
+ self.check_size(tr_data)
+ logger.info("Reading user logout data...")
+ try:
+ token, = struct.unpack("!32s", tr_data[:32])
+ username, tail = RequestHandler.trunc_padding(tr_data[32:])
+ if username is None:
+ raise struct.error
+ except struct.error:
+ raise BadReqError("User logout: Malformed request body")
+
+# logger.info("Trying to logout with "
+# "(token = {0}, username = {1})"\
+# .format(get_hex(token), username))
+
+ uauth = RequestHandler.get_uauth(token, username, self.session)
+ # Authentication failure
+ if uauth is None:
+ logger.warning("Authentication failure")
+ return self.pack(struct.pack("!B", _StatusCode.auth_fail))
+
+ pt = RequestHandler.push_tunnels
+ u = uauth.user
+ loc = u.location
+ loc.lat = NOT_A_LAT
+ loc.lng = NOT_A_LNG
+
+ comp = self.session.query(GroupInfo) \
+ .filter(GroupInfo.id == u.comp_id).one()
+ sec = self.session.query(GroupInfo) \
+ .filter(GroupInfo.id == u.sec_id).one()
+
+ pdata = PushLocationData(u.id, loc.lat, loc.lng)
+ for user in comp.subscribers:
+ uid = user.id
+ if uid == uauth.uid: continue
+ if pt.has_key(uid):
+ tunnel = pt[uid]
+ tunnel.add(pdata)
+ tunnel.push()
+
+ for user in sec.subscribers:
+ uid = user.id
+ if uid == uauth.uid: continue
+ if pt.has_key(uid):
+ tunnel = pt[uid]
+ tunnel.add(pdata)
+ tunnel.push()
+
+ pt = RequestHandler.push_tunnels
+ uid = uauth.uid
+ pt[uid].close()
+ del pt[uid]
+ uauth.regen_token()
+ logger.info("User Logged out successfully!")
+ self.session.commit()
+ return self.pack(struct.pack("!B", _StatusCode.sucess))
+
+class OpenPushTunnelHandler(RequestHandler):
+
+ _optcode = _OptCode.open_push_tunnel
+ _max_tr_data_size = _MAX_AUTH_HEAD_SIZE
+
+ def handle(self, tr_data, conn):
+ self.check_size(tr_data)
+ logger.info("Reading open push tunnel data...")
+ try:
+ token, = struct.unpack("!32s", tr_data[:32])
+ username, tail = RequestHandler.trunc_padding(tr_data[32:])
+ if username is None:
+ raise struct.error
+ except struct.error:
+ raise BadReqError("Open push tunnel: Malformed request body")
+
+# logger.info("Trying to open push tunnel with "
+# "(token = {0}, username = {1})"\
+# .format(get_hex(token), username))
+
+ uauth = RequestHandler.get_uauth(token, username, self.session)
+ # Authentication failure
+ if uauth is None:
+ logger.warning("Authentication failure")
+ return self.pack(struct.pack("!B", _StatusCode.auth_fail))
+
+ tunnel = RequestHandler.push_tunnels[uauth.uid]
+ tunnel.connect(conn)
+
+ logger.info("Push tunnel opened successfully!")
+ return self.pack(struct.pack("!B", _StatusCode.sucess))
+
+class SendTextMessageHandler(RequestHandler):
+
+ _optcode = _OptCode.send_text_mesg
+ _max_tr_data_size = _MAX_AUTH_HEAD_SIZE + \
+ _MAX_TEXT_MESG_SIZE + \
+ _SectionSize.PADDING
+
+ def handle(self, tr_data, conn):
+ self.check_size(tr_data)
+ logger.info("Reading send text mesg data...")
+ try:
+ token, = struct.unpack("!32s", tr_data[:32])
+ username, tail = RequestHandler.trunc_padding(tr_data[32:])
+ mesg = tail[:-1]
+ if username is None:
+ raise struct.error
+ except struct.error:
+ raise BadReqError("Send text mesg: Malformed request body")
+
+# logger.info("Trying to send text mesg with "
+# "(token = {0}, username = {1})"\
+# .format(get_hex(token), username))
+
+ uauth = RequestHandler.get_uauth(token, username, self.session)
+ # Authentication failure
+ if uauth is None:
+ logger.warning("Authentication failure")
+ return self.pack(struct.pack("!B", _StatusCode.auth_fail))
+
+ pt = RequestHandler.push_tunnels
+ u = uauth.user
+ ulist = self.session.query(UserModel) \
+ .filter(UserModel.sec_id == u.sec_id).all()
+
+ for user in ulist:
+ uid = user.id
+ if uid == uauth.uid: continue
+ if pt.has_key(uid):
+ tunnel = pt[uid]
+ tunnel.add(PushTextMesgData(mesg))
+ tunnel.push()
+ logger.info("Sent text mesg successfully!")
+ return self.pack(struct.pack("!B", _StatusCode.sucess))
+
+class SetMarkerHandler(RequestHandler):
+
+ _optcode = _OptCode.set_marker
+ _max_tr_data_size = _MAX_AUTH_HEAD_SIZE + \
+ _SectionSize.LATITUDE + \
+ _SectionSize.LONGITUDE + \
+ _SectionSize.DEADLINE
+ @classmethod
+ def push_new_marker(cls, u, session):
+ left = session.query(MarkerInfo) \
+ .filter(MarkerInfo.status == MARKER_FRESH).all()
+ if len(left) == 0:
+ logger.warn("All markers have been used!")
+ return
+ marker = left[0]
+ marker.status = MARKER_DISPLAYED
+ session.commit()
+ pdata = PushMarkerData(perm = 2, lat = marker.lat, \
+ lng = marker.lng, \
+ deadline = 0x7fffffff, \
+ mid = marker.id, \
+ score = marker.score)
+ ulist = session.query(UserModel) \
+ .filter(UserModel.comp_id == u.comp_id).all()
+
+ pt = RequestHandler.push_tunnels
+ for user in ulist:
+ uid = user.id
+ if pt.has_key(uid):
+ tunnel = pt[uid]
+ tunnel.add(pdata)
+ tunnel.push()
+
+
+ def handle(self, tr_data, conn):
+ self.check_size(tr_data)
+ logger.info("Reading set marker data...")
+ try:
+ token, = struct.unpack("!32s", tr_data[:32])
+ username, tail = RequestHandler.trunc_padding(tr_data[32:])
+ lat, lng, deadline, mid, score = struct.unpack("!ddlBL", tail)
+ if username is None:
+ raise struct.error
+ except struct.error:
+ raise BadReqError("Set marker: Malformed request body")
+
+ logger.info("Trying to set marker with "
+ "(token = {0}, username = {1})"\
+ .format(get_hex(token), username))
+
+ uauth = RequestHandler.get_uauth(token, username, self.session)
+ # Authentication failure
+ if uauth is None:
+ logger.warning("Authentication failure")
+ return self.pack(struct.pack("!B", _StatusCode.auth_fail))
+
+ SetMarkerHandler.push_new_marker(uauth.user, self.session)
+
+ logger.info("Set marker successfully!")
+ return self.pack(struct.pack("!B", _StatusCode.sucess))
+
+class ChangePasswordHandler(RequestHandler):
+
+ _optcode = _OptCode.change_password
+ _max_tr_data_size = _MAX_AUTH_HEAD_SIZE + \
+ MAX_PASSWORD_SIZE + \
+ _SectionSize.PADDING + \
+ MAX_PASSWORD_SIZE + \
+ _SectionSize.PADDING
+
+ def handle(self, tr_data, conn):
+ self.check_size(tr_data)
+ logger.info("Reading change password data...")
+ try:
+ token, = struct.unpack("!32s", tr_data[:32])
+ username, tail = RequestHandler.trunc_padding(tr_data[32:])
+ if username is None:
+ raise struct.error
+ old_pass, tail = RequestHandler.trunc_padding(tail)
+ new_pass = tail[:-1]
+ except struct.error:
+ raise BadReqError("User logout: Malformed request body")
+
+# logger.info("Trying to change password with "
+# "(token = {0}, username = {1}, old_pass = {2}, new_pass = {3})"\
+# .format(get_hex(token), username, old_pass, new_pass))
+
+ uauth = RequestHandler.get_uauth(token, username, self.session)
+ # Authentication failure
+ if uauth is None:
+ logger.warning("Authentication failure")
+ return self.pack(struct.pack("!B", _StatusCode.auth_fail))
+ if not uauth.check_password(old_pass):
+ return self.pack(struct.pack("!B", _StatusCode.wrong_pass))
+ uauth.set_password(new_pass)
+ self.session.commit()
+ logger.info("Password changed successfully!")
+
+ pt = RequestHandler.push_tunnels
+ uid = uauth.uid
+ pt[uid].close()
+ del pt[uid]
+ uauth.regen_token()
+
+ return self.pack(struct.pack("!B", _StatusCode.sucess))
+
+class CheckInHandler(RequestHandler):
+
+ _optcode = _OptCode.check_in
+ _max_tr_data_size = _MAX_AUTH_HEAD_SIZE + \
+ _SectionSize.MARKER_ID
+
+ def handle(self, tr_data, conn):
+ self.check_size(tr_data)
+ logger.info("Reading check-in data...")
+ try:
+ token, = struct.unpack("!32s", tr_data[:32])
+ username, tail = RequestHandler.trunc_padding(tr_data[32:])
+ if username is None:
+ raise struct.error
+ mid, = struct.unpack("!B", tail)
+ except struct.error:
+ raise BadReqError("Check-in: Malformed request body")
+
+ logger.info("Trying to check-in with "
+ "(token = {0}, username = {1}, mid = {2})"\
+ .format(get_hex(token), username, str(mid)))
+
+ uauth = RequestHandler.get_uauth(token, username, self.session)
+ # Authentication failure
+ if uauth is None:
+ logger.warning("Authentication failure")
+ return self.pack(struct.pack("!B", _StatusCode.auth_fail))
+
+ try:
+ marker = self.session.query(MarkerInfo) \
+ .filter(MarkerInfo.id == mid).one()
+ except NoResultFound:
+ raise BadReqError("Check-in: no such marker")
+
+ if marker.status != MARKER_DISPLAYED:
+ return self.pack(struct.pack("!B", _StatusCode.checkin_fail))
+ marker.status = MARKER_CHECKED
+
+ pt = RequestHandler.push_tunnels
+ u = uauth.user
+
+ comp = self.session.query(GroupInfo) \
+ .filter(GroupInfo.id == u.comp_id).one()
+ sec = self.session.query(GroupInfo) \
+ .filter(GroupInfo.id == u.sec_id).one()
+
+ sec.score += marker.score
+ logger.info("Player {0} got score: {1}".format(username, str(marker.score)))
+
+ score1 = self.session.query(GroupInfo) \
+ .filter(GroupInfo.id == 5889).one().score
+ score2 = self.session.query(GroupInfo) \
+ .filter(GroupInfo.id == 5890).one().score
+
+ pdata = PushMarkerRemovalData(mid)
+ for user in comp.subscribers:
+ uid = user.id
+ if pt.has_key(uid):
+ tunnel = pt[uid]
+ tunnel.add(pdata)
+ tunnel.add(PushScoreData(score1, score2))
+ tunnel.push()
+
+ SetMarkerHandler.push_new_marker(u, self.session)
+
+ logger.info("User checked in successfully!")
+ self.session.commit()
+ return self.pack(struct.pack("!B", _StatusCode.sucess))
+
+class GameStartHandler(RequestHandler):
+
+ _optcode = _OptCode.game_start
+ _max_tr_data_size = _MAX_AUTH_HEAD_SIZE
+
+ def handle(self, tr_data, conn):
+ self.check_size(tr_data)
+ logger.info("Reading game-start data...")
+ try:
+ token, = struct.unpack("!32s", tr_data[:32])
+ username, tail = RequestHandler.trunc_padding(tr_data[32:])
+ if username is None:
+ raise struct.error
+ except struct.error:
+ raise BadReqError("Game-start: Malformed request body")
+
+ logger.info("Trying to game-start with "
+ "(token = {0}, username = {1})"\
+ .format(get_hex(token), username))
+
+ uauth = RequestHandler.get_uauth(token, username, self.session)
+ # Authentication failure
+ if uauth is None:
+ logger.warning("Authentication failure")
+ return self.pack(struct.pack("!B", _StatusCode.auth_fail))
+
+
+ pt = RequestHandler.push_tunnels
+ u = uauth.user
+
+ if u.perm != 2:
+ logger.warning("Insufficient privilege")
+ return self.pack(struct.pack("!B", _StatusCode.insuf_lvl))
+
+ team1 = self.session.query(GroupInfo) \
+ .filter(GroupInfo.id == 5889).one()
+ team2 = self.session.query(GroupInfo) \
+ .filter(GroupInfo.id == 5890).one()
+
+ team1.score = 0
+ team2.score = 0
+
+ for mrk in self.session.query(MarkerInfo).all():
+ mrk.status = MARKER_FRESH
+
+ users1 = self.session.query(UserModel) \
+ .filter(UserModel.sec_id == team1.id)
+
+ users2 = self.session.query(UserModel) \
+ .filter(UserModel.sec_id == team2.id)
+
+ for i in xrange(_INIT_MARKER_NUM):
+ SetMarkerHandler.push_new_marker(u, self.session)
+
+ comp = self.session.query(GroupInfo) \
+ .filter(GroupInfo.id == u.comp_id).one()
+
+ for user in comp.subscribers:
+ uid = user.id
+ if pt.has_key(uid):
+ tunnel = pt[uid]
+ tunnel.add(PushScoreData(team1.score, team2.score))
+ tunnel.push()
+
+ logger.info("GAME START!")
+ self.session.commit()
+ return self.pack(struct.pack("!B", _StatusCode.sucess))
+
+
+class PTP(Protocol, TimeoutMixin):
+
+ handlers = [UserAuthHandler,
+ UpdateLocationHandler,
+ UserInfoHandler,
+ UpdateSubscription,
+ UserLogoutHandler,
+ OpenPushTunnelHandler,
+ SendTextMessageHandler,
+ SetMarkerHandler,
+ ChangePasswordHandler,
+ CheckInHandler,
+ GameStartHandler]
+
+ handler_num = len(handlers)
+
+ _MAX_REQUEST_SIZE = _HEADER_SIZE + \
+ max([h._max_tr_data_size for h in handlers])
+
+ @classmethod
+ def check_header(cls, header):
+ return 0 <= header < cls.handler_num
+
+ def __init__(self, factory):
+ self.buff = bytes()
+ self.length = -1
+ self.factory = factory
+ self.tunnel = None
+
+ def timeoutConnection(self):
+ logger.info("The connection times out")
+ self.transport.loseConnection()
+
+ def connectionMade(self):
+ logger.info("A new connection is made")
+ self.setTimeout(self.factory.timeout)
+
+ def response(self, buff):
+ try:
+ h = PTP.handlers[self.optcode]()
+ reply = h.handle(buff[5:], self)
+# logger.info("Wrote: %s", get_hex(reply))
+ self.transport.write(reply)
+ if self.tunnel:
+ logger.info("Blocking the client...")
+ self.tunnel.push()
+ self.length = -1
+ return
+ self.transport.loseConnection()
+ except BadReqError as e:
+ logger.warn("Rejected a bad request: %s", str(e))
+ self.transport.loseConnection()
+ except DBCorruptionError:
+ logger.error("*** Database corruption ***")
+
+
+ def dataReceived(self, data):
+ self.buff += data
+ self.resetTimeout()
+ logger.info("Buffer length is now: %d", len(self.buff))
+ if len(self.buff) <= 4:
+ return
+ try:
+ if self.length == -1:
+ try:
+ self.length, self.optcode = struct.unpack("!LB", self.buff[:5])
+ if not PTP.check_header(self.optcode): # invalid header
+ raise struct.error
+ except struct.error:
+ raise BadReqError("Malformed request header")
+ if self.length > PTP._MAX_REQUEST_SIZE:
+ print self.length, PTP._MAX_REQUEST_SIZE
+ raise BadReqError("The size of remaining part is too big")
+
+ if len(self.buff) >= self.length:
+ buff = self.buff[:self.length]
+ self.buff = self.buff[self.length:]
+ if self.tunnel: # received push response
+ self.tunnel.on_receive(buff)
+ self.length = -1
+ return
+ self.setTimeout(None)
+ reactor.callFromThread(self.response, buff)
+ #self.response(buff)
+ except BadReqError as e:
+ logger.warn("Rejected a bad request: %s", str(e))
+ self.transport.loseConnection()
+
+ def connectionLost(self, reason):
+ if self.tunnel:
+ self.tunnel.on_connection_lost(self)
+ logger.info("The connection is lost")
+ self.setTimeout(None)
+
+class PTPFactory(Factory):
+ def __init__(self, timeout = 10):
+ self.timeout = timeout
+ def buildProtocol(self, addr):
+ return PTP(self)
+
+if os.name!='nt':
+ from twisted.internet import epollreactor
+ epollreactor.install()
+else:
+ from twisted.internet import iocpreactor
+ iocpreactor.install()
+
+from twisted.internet import reactor
+
+f = PTPFactory()
+f.protocol = PTP
+reactor.listenTCP(2224, f)
+logger.warning("The server is lanuched")
+reactor.run()