From f74999631c4f83a0c8532d6b7adb348dcd5d5205 Mon Sep 17 00:00:00 2001 From: Teddy Date: Sun, 25 Aug 2013 17:54:38 +0800 Subject: ptp v0.3 and std-compliant new server! --- server/piztor/import.py | 7 +- server/piztor/model.py | 5 +- server/piztor/prob.py | 76 ++++++++++++ server/piztor/server.py | 311 ++++++++++++++++++++++++++++++++++++++++++++++++ server/piztor_server.py | 2 +- server/ptp.rst | 51 ++++---- 6 files changed, 425 insertions(+), 27 deletions(-) create mode 100644 server/piztor/prob.py create mode 100644 server/piztor/server.py diff --git a/server/piztor/import.py b/server/piztor/import.py index 1521849..84c990f 100644 --- a/server/piztor/import.py +++ b/server/piztor/import.py @@ -5,9 +5,10 @@ from model import * path = "piztor.sqlite" class UserData: - def __init__(self, username, password, sex): + def __init__(self, username, password, gid, sex): self.username = username self.password = password + self.gid = gid self.sex = sex def create_database(): @@ -20,7 +21,7 @@ def import_user_data(data): Session = sessionmaker(bind = engine) session = Session() for user in data: - um = UserModel(username = user.username, sex = user.sex) + um = UserModel(username = user.username, gid = user.gid, sex = user.sex) um.auth = UserAuth(user.password) um.location = LocationInfo(lat = 0, lng = 0) session.add(um) @@ -38,7 +39,7 @@ if __name__ == '__main__': while True: line = f.readline().split() if len(line) == 0: break - data.append(UserData(line[0], line[1], line[2])) + data.append(UserData(line[0], line[1], line[2], line[3])) create_database() import_user_data(data) diff --git a/server/piztor/model.py b/server/piztor/model.py index 70ca431..4621bbe 100644 --- a/server/piztor/model.py +++ b/server/piztor/model.py @@ -16,6 +16,7 @@ class UserModel(Base): __tablename__ = _TableName.UserModel id = Column(Integer, primary_key = True) + gid = Column(Integer) username = Column(String) sex = Column(Boolean) location = None @@ -24,7 +25,7 @@ class UserModel(Base): class LocationInfo(Base): __tablename__ = _TableName.LocationInfo - uid = Column(Integer, ForeignKey('users.id'), primary_key = True) + uid = Column(Integer, ForeignKey(_TableName.UserModel + '.id'), primary_key = True) lat = Column(Float(precesion = 64)) lng = Column(Float(precesion = 64)) user = relationship("UserModel", uselist = False, @@ -48,7 +49,7 @@ def _random_binary_string(length): class UserAuth(Base): __tablename__ = _TableName.UserAuth - uid = Column(Integer, ForeignKey('users.id'), primary_key = True) + uid = Column(Integer, ForeignKey(_TableName.UserModel + '.id'), primary_key = True) password = Column(LargeBinary) salt = Column(LargeBinary) token = Column(LargeBinary) diff --git a/server/piztor/prob.py b/server/piztor/prob.py new file mode 100644 index 0000000..1f9bdb7 --- /dev/null +++ b/server/piztor/prob.py @@ -0,0 +1,76 @@ +import socket +from struct import * +from random import random + +def get_hex(data): + return "".join([hex(ord(c))[2:].zfill(2) for c in data]) + +host = "localhost" +port = 9990 + +def gen_auth(username, password): + length = 4 + 1 + len(username) + 1 + len(password) + data = pack("!LB", length, 0x00) + data += username + data += "\0" + data += password + return data + +def gen_update_location(token, username, lat, lng): + length = 4 + 1 + 32 + 8 + 8 + len(username) + 1 + data = pack("!LB32s", length, 0x01, token) + data += username + data += chr(0) + data += pack("!dd", lat, lng) + return data + +def gen_request_location(token, username, gid): + length = 4 + 1 + 32 + 4 + len(username) + 1 + data = pack("!LB32s", length, 0x02, token) + data += username + data += chr(0) + data += pack("!L", gid) + return data + + +def send(data): + received = None + try: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.connect((host, port)) + print len(data) + sock.sendall(data) + received = sock.recv(1024) + finally: + sock.close() + return received + +username = "hello" +password = "world" +gid = 1 + +resp = send(gen_auth(username, password)) +pl, optcode, status, uid, token = unpack("!LBBL32s", resp) +print "size: " + str((pl, len(resp))) +print "opt: " + str(optcode) +print "status: " + str(status) +print "uid: " + str(uid) +print "token: " + get_hex(token) + +resp = send(gen_update_location(token, username, random(), random())) +pl, optcode, status = unpack("!LBB", resp) +print "size: " + str((pl, len(resp))) +print "opt: " + str(optcode) +print "status: " + str(status) + +resp = send(gen_request_location(token, username, gid)) +print len(resp) +pl, optcode, status, length = unpack("!LBBL", resp[:10]) +print "size: " + str((pl, len(resp))) +idx = 10 +print "length: " + str(len(resp[10:])) +for i in xrange(length): + print len(resp[idx:idx + 20]) + uid, lat, lng = unpack("!Ldd", resp[idx:idx + 20]) + idx += 20 + print (uid, lat, lng) diff --git a/server/piztor/server.py b/server/piztor/server.py new file mode 100644 index 0000000..5c3160b --- /dev/null +++ b/server/piztor/server.py @@ -0,0 +1,311 @@ +from twisted.internet.protocol import Protocol +from twisted.internet.protocol import Factory +from twisted.internet.endpoints import TCP4ServerEndpoint +from twisted.internet import reactor + +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker +from sqlalchemy.orm.exc import NoResultFound, MultipleResultsFound + +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]) + +def print_datagram(data): + print "==================================" + print "Received datagram:" + print get_hex(data) + print "==================================" + +db_path = "piztor.sqlite" +FORMAT = "%(asctime)-15s %(message)s" +logging.basicConfig(format = FORMAT) +logger = logging.getLogger('piztor_server') +logger.setLevel(logging.INFO) + + +class _SectionSize: + LENGTH = 4 + OPT_ID = 1 + STATUS = 1 + USER_ID = 4 + USER_TOKEN = 32 + GROUP_ID = 4 + ENTRY_CNT = 4 + LATITUDE = 8 + LONGITUDE = 8 + LOCATION_ENTRY = USER_ID + LATITUDE + LONGITUDE + PADDING = 1 + +class _OptCode: + user_auth = 0x00 + location_update = 0x02 + location_request= 0x03 + +class _StatusCode: + sucess = 0x00 + failure = 0x01 + +class RequestHandler(object): + def __init__(self): + self.engine = create_engine('sqlite:///' + db_path, echo = False) + self.Session = sessionmaker(bind = self.engine) + + @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("Toke and username mismatch") + return None + + return uauth + + except NoResultFound: + logger.warning("Incorrect token") + return None + + except MultipleResultsFound: + raise DBCorruptedError() + + @classmethod + def trunc_padding(cls, data): + leading = bytes() + for i in xrange(len(data)): + ch = data[i] + if ch == '\x00': + print get_hex(leading), get_hex(data[i + 1:]) + return (leading, data[i + 1:]) + else: + leading += ch + # padding not found + return (None, data) + +class UserAuthHandler(RequestHandler): + + _user_auth_response_size = \ + _SectionSize.LENGTH + \ + _SectionSize.OPT_ID + \ + _SectionSize.STATUS + \ + _SectionSize.USER_ID + \ + _SectionSize.USER_TOKEN + + def handle(self, 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:] + logger.info("Trying to login with " \ + "(username = {0}, password = {1})" \ + .format(username, password)) + + session = self.Session() + try: + user = session.query(UserModel) \ + .filter(UserModel.username == username).one() + except NoResultFound: + logger.info("No such user: {0}".format(username)) + return struct.pack("!LBBL32s", UserAuthHandler \ + ._user_auth_response_size, + _OptCode.user_auth, + _StatusCode.failure, + 0, + bytes('\x00' * 32)) + + except MultipleResultsFound: + raise DBCorruptedError() + + uauth = user.auth + if uauth is None: + raise DBCorruptedError() + if not uauth.check_password(password): + logger.info("Incorrect password: {0}".format(username)) + return struct.pack("!LBBL32s", UserAuthHandler \ + ._user_auth_response_size, + _OptCode.user_auth, + _StatusCode.failure, + 0, + bytes('\x00' * 32)) + else: + logger.info("Logged in sucessfully: {0}".format(username)) + uauth.regen_token() + session.commit() + print "new token generated: " + get_hex(uauth.token) + return struct.pack("!LBBL32s", UserAuthHandler \ + ._user_auth_response_size, + _OptCode.user_auth, + _StatusCode.sucess, + user.id, + uauth.token) + + +class LocationUpdateHandler(RequestHandler): + +# _location_update_size = \ +# _SectionSize.AUTH_HEAD + \ +# _SectionSize.LATITUDE + \ +# _SectionSize.LONGITUDE + + _location_update_response_size = \ + _SectionSize.LENGTH + \ + _SectionSize.OPT_ID + \ + _SectionSize.STATUS + + def handle(self, 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("Location update: 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)) + + session = self.Session() + uauth = RequestHandler.get_uauth(token, username, session) + # Authentication failure + if uauth is None: + logger.warning("Authentication failure") + return struct.pack("!LBB", LocationUpdateHandler \ + ._location_update_response_size, + _OptCode.location_update, + _StatusCode.failure) + + ulocation = uauth.user.location + ulocation.lat = lat + ulocation.lng = lng + session.commit() + + logger.info("Location is updated sucessfully") + return struct.pack("!LBB", LocationUpdateHandler \ + ._location_update_response_size, + _OptCode.location_update, + _StatusCode.sucess) + +class LocationRequestHandler(RequestHandler): + +# _location_request_size = \ +# _SectionSize.AUTH_HEAD + \ +# _SectionSize.GROUP_ID + + @classmethod + def _location_request_response_size(cls, item_num): + return _SectionSize.LENGTH + \ + _SectionSize.OPT_ID + \ + _SectionSize.STATUS + \ + _SectionSize.ENTRY_CNT + \ + _SectionSize.LOCATION_ENTRY * item_num + + def handle(self, tr_data): + logger.info("Reading location 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("!L", tail) + except struct.error: + raise BadReqError("Location request: Malformed request body") + + logger.info("Trying to request locatin with " \ + "(token = {0}, gid = {1})" \ + .format(get_hex(token), gid)) + + session = self.Session() + uauth = RequestHandler.get_uauth(token, username, session) + # Auth failure + if uauth is None: + logger.warning("Authentication failure") + return struct.pack("!LBBL", LocationRequestHandler \ + ._location_request_response_size(0), + _OptCode.location_request, + _StatusCode.failure, + 0) + + ulist = session.query(UserModel).filter(UserModel.gid == gid).all() + reply = struct.pack( + "!LBBL", + LocationRequestHandler._location_request_response_size(len(ulist)), + _OptCode.location_request, + _StatusCode.sucess, + len(ulist)) + + for user in ulist: + loc = user.location + reply += struct.pack("!Ldd", user.id, loc.lat, loc.lng) + + return reply + +handlers = [UserAuthHandler, + LocationUpdateHandler, + LocationRequestHandler] + +def check_header(header): + return 0 <= header < len(handlers) + +class PTP(Protocol): + + def __init__(self): + self.buff = bytes() + self.length = -1 + + def connectionMade(self): + logger.info("A new connection is made") + + def dataReceived(self, data): + self.buff += data + print len(self.buff) + if len(self.buff) > 4: + try: + self.length, self.optcode = struct.unpack("!LB", self.buff[:5]) + if not check_header(self.optcode): # invalid header + raise struct.error + except struct.error: + logger.warning("Invalid request header") + raise BadReqError("Malformed request header") + print self.length + if len(self.buff) == self.length: + h = handlers[self.optcode]() + reply = h.handle(self.buff[5:]) + logger.info("Wrote: %s", get_hex(reply)) + self.transport.write(reply) + self.transport.loseConnection() + + elif len(self.buff) > self.length: + self.transport.loseConnection() + + + def connectionLost(self, reason): + logger.info("The connection is lost") + +class PTPFactory(Factory): + def __init__(self): + pass + def buildProtocol(self, addr): + return PTP() + +endpoint = TCP4ServerEndpoint(reactor, 9990) +endpoint.listen(PTPFactory()) +reactor.run() diff --git a/server/piztor_server.py b/server/piztor_server.py index 514f95f..81805b3 100644 --- a/server/piztor_server.py +++ b/server/piztor_server.py @@ -230,5 +230,5 @@ class PiztorServer(): if __name__ == "__main__": - ps = PiztorServer("localhost", 9990) + ps = PiztorServer("localhost", 9999) ps.run() diff --git a/server/ptp.rst b/server/ptp.rst index 8aef2b7..7c40a3b 100644 --- a/server/ptp.rst +++ b/server/ptp.rst @@ -1,4 +1,4 @@ -Piztor Transmission Protocol v0.2 +Piztor Transmission Protocol v0.3 --------------------------------- - General @@ -8,7 +8,7 @@ Piztor Transmission Protocol v0.2 :: +---4b---+---1b---+-------?b--------+ - | LENGTH | OPT ID | SPECIFIC DATA | + | LENGTH | OPT_ID | SPECIFIC DATA | +--int---+-uchar--+-----------------+ - Response @@ -16,11 +16,20 @@ Piztor Transmission Protocol v0.2 :: +---4b---+---1b---+------?b---------+ - | LENGTH | OPT ID | SPECIFIC DATA | + | LENGTH | OPT_ID | SPECIFIC DATA | +--int---+-uchar--+-----------------+ - Notice that in following sections, ``LENGTH`` part is left out for clarity. - TODO: All secure requests should have username or uid provided. + Notice: + + - In following sections, ``LENGTH`` part is left out for clarity. + - ``PADDING`` has value ``0``. + - ``AUTH_HEAD`` structure: + + :: + + +----32b-----+----?b----+----1b---+ + | USER_TOKEN | USERNAME | PADDING | + +----raw-----+----------+---------+ - Authentication @@ -28,22 +37,22 @@ Piztor Transmission Protocol v0.2 :: - +--1b---+-----?b------+-----?b-----+ - | 0x00 | USERNAME | PASSWORD | - +-uchar-+-------------+------------+ + +--1b---+-----?b------+----1b----+-----?b-----+ + | 0x00 | USERNAME | PADDING | PASSWORD | + +-uchar-+-------------+----------+------------+ - Response :: - +--1b---+---1b---+---4b----+----16b-----+ + +--1b---+---1b---+---4b----+----32b-----+ | 0x00 | STATUS | USER_ID | USER_TOKEN | +-uchar-+--uchar-+---int---+----raw-----+ ``STATUS`` : - - 0x00 for success - - 0x01 for failure + - ``0x00`` for success + - ``0x01`` for failure - Location Update @@ -51,22 +60,22 @@ Piztor Transmission Protocol v0.2 :: - +--1b---+-----16b------+-----8b-----+------8b-----+ - | 0x02 | USER_TOKEN | LATITUDE | LONGITUDE | - +-uchar-+------raw-----+---double---+---double----+ + +--1b---+-----?b------+----8b------+------8b-----+ + | 0x01 | AUTH_HEAD | LATITUDE | LONGITUDE | + +-uchar-+-------------+---double---+---double----+ - Response :: +--1b---+---1b---+ - | 0x02 | STATUS | + | 0x01 | STATUS | +-uchar-+--uchar-+ ``STATUS`` : - - 0x00 for success - - 0x01 for invalid token + - ``0x00`` for success + - ``0x01`` for invalid token - Location Information @@ -74,16 +83,16 @@ Piztor Transmission Protocol v0.2 :: - +--1b---+-----16b------+------4b-----+ - | 0x03 | USER_TOKEN | GROUP_ID | - +-uchar-+-----raw------+-----int-----+ + +--1b---+------?b------+------4b-----+ + | 0x02 | AUTH_HEAD | GROUP_ID | + +-uchar-+--------------+-----int-----+ - Response :: +--1b---+---1b---+-----4b----+------20b-------+-----+ - | 0x03 | STATUS | ENTRY_CNT | LOCATION_ENTRY | ... | + | 0x02 | STATUS | ENTRY_CNT | LOCATION_ENTRY | ... | +-uchar-+-uchar--+----int----+----------------+-----+ ``LOCATION_ENTRY`` : -- cgit v1.2.3