From ac7633d8149a28af288ac0b850850cef9b13c151 Mon Sep 17 00:00:00 2001 From: sjtufs Date: Sun, 25 Aug 2013 15:45:55 +0800 Subject: This is alpha --- misc/server/README.rst | 1 + misc/server/client.py | 58 +++++++++++ misc/server/piztor/exc.py | 17 ++++ misc/server/piztor/import.py | 44 ++++++++ misc/server/piztor/model.py | 79 +++++++++++++++ misc/server/piztor_server.py | 234 +++++++++++++++++++++++++++++++++++++++++++ misc/server/ptp.rst | 96 ++++++++++++++++++ misc/server/rush.py | 14 +++ 8 files changed, 543 insertions(+) create mode 100644 misc/server/README.rst create mode 100644 misc/server/client.py create mode 100644 misc/server/piztor/exc.py create mode 100644 misc/server/piztor/import.py create mode 100644 misc/server/piztor/model.py create mode 100644 misc/server/piztor_server.py create mode 100644 misc/server/ptp.rst create mode 100644 misc/server/rush.py (limited to 'misc/server') diff --git a/misc/server/README.rst b/misc/server/README.rst new file mode 100644 index 0000000..e88c745 --- /dev/null +++ b/misc/server/README.rst @@ -0,0 +1 @@ +Here is the folder of server-side implementation diff --git a/misc/server/client.py b/misc/server/client.py new file mode 100644 index 0000000..15f4bbc --- /dev/null +++ b/misc/server/client.py @@ -0,0 +1,58 @@ +import socket +import sys +from struct import * +from random import random +from time import sleep + +def get_hex(data): + return "".join([hex(ord(c))[2:].zfill(2) for c in data]) + +HOST, PORT = "localhost", 9990 + +def gen_auth(username, password): + data = pack("!B", 0) + data += username + data += "\0" + data += password + return data + +def gen_update_location(token, lat, lont): + return pack("!BLdd", 2, token, lat, lont) + +def gen_request_location(token, gid): + return pack("!BLL", 3, token, gid) + +def send(data): + try: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.connect((HOST, PORT)) +# print "Client " + str(sys.argv[1]) + ": connected" + sock.sendall(data) + print get_hex(data) +# print "Client " + str(sys.argv[1]) + ": sent" +# sock.shutdown(socket.SHUT_WR) +# print "Client " + str(sys.argv[1]) + ": shutdown" + received = sock.recv(1024) + finally: + print "adf" + sock.close() + + print "Sent {}".format(get_hex(data)) + print "Received: {}".format(get_hex(data)) + return received + +#print "Client spawned:" + str(sys.argv[1]) +rec = send(gen_auth("hello", "world")) +opt, token, status = unpack("!BLB", rec) + +rec = send(gen_update_location(token, random(), random())) +opc, status = unpack("!BB", rec) + +rec = send(gen_request_location(token, 1)) +opc, length = unpack("!BL", rec[:5]) +idx = 5 +for i in xrange(length): + uid, lat, lng = unpack("!Ldd", rec[idx:idx + 20]) + print (uid, lat, lng) + idx += 20 +# sleep(60) diff --git a/misc/server/piztor/exc.py b/misc/server/piztor/exc.py new file mode 100644 index 0000000..2c53dbf --- /dev/null +++ b/misc/server/piztor/exc.py @@ -0,0 +1,17 @@ +class PiztorError(Exception): + pass + +class DBCurruptedError(PiztorError): + pass + +class ConnectionError(PiztorError): + pass + +class ReqReadError(ConnectionError): + pass + +class BadReqError(ConnectionError): + pass + +class BadTokenError(ConnectionError): + pass diff --git a/misc/server/piztor/import.py b/misc/server/piztor/import.py new file mode 100644 index 0000000..1521849 --- /dev/null +++ b/misc/server/piztor/import.py @@ -0,0 +1,44 @@ +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker +from model import * + +path = "piztor.sqlite" + +class UserData: + def __init__(self, username, password, sex): + self.username = username + self.password = password + self.sex = sex + +def create_database(): + engine = create_engine('sqlite:///' + path, echo = True) + Base.metadata.drop_all(engine) + Base.metadata.create_all(engine) + +def import_user_data(data): + engine = create_engine('sqlite:///' + path, echo = True) + Session = sessionmaker(bind = engine) + session = Session() + for user in data: + um = UserModel(username = user.username, sex = user.sex) + um.auth = UserAuth(user.password) + um.location = LocationInfo(lat = 0, lng = 0) + session.add(um) + session.commit() + +if __name__ == '__main__': + + from sys import argv, exit + if len(argv) != 2: + print "Usage: " + argv[0] + " FILE" + exit(0) + + data = list() + with open(argv[1], 'r') as f: + while True: + line = f.readline().split() + if len(line) == 0: break + data.append(UserData(line[0], line[1], line[2])) + + create_database() + import_user_data(data) diff --git a/misc/server/piztor/model.py b/misc/server/piztor/model.py new file mode 100644 index 0000000..70ca431 --- /dev/null +++ b/misc/server/piztor/model.py @@ -0,0 +1,79 @@ +from sqlalchemy import Column, Integer, String, Float, ForeignKey, LargeBinary, Boolean +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import relationship, backref + +Base = declarative_base() + +_SALT_LEN = 16 +_TOKEN_LEN = 16 + +class _TableName: # avoid typoes + UserModel = 'users' + LocationInfo = 'location_info' + UserAuth = 'user_auth' + +class UserModel(Base): + __tablename__ = _TableName.UserModel + + id = Column(Integer, primary_key = True) + username = Column(String) + sex = Column(Boolean) + location = None + auth = None + +class LocationInfo(Base): + __tablename__ = _TableName.LocationInfo + + uid = Column(Integer, ForeignKey('users.id'), primary_key = True) + lat = Column(Float(precesion = 64)) + lng = Column(Float(precesion = 64)) + user = relationship("UserModel", uselist = False, + backref = backref("location", uselist = False, + cascade = "all, delete-orphan")) + + # More: last_update + +from hashlib import sha256 +from os import urandom + +def _sprinkle_salt(uauth, passwd): + data = sha256(uauth.salt) + data.update(chr(0)) + data.update(passwd) + return data.digest() + +def _random_binary_string(length): + return urandom(length) + +class UserAuth(Base): + __tablename__ = _TableName.UserAuth + + uid = Column(Integer, ForeignKey('users.id'), primary_key = True) + password = Column(LargeBinary) + salt = Column(LargeBinary) + token = Column(LargeBinary) + + user = relationship("UserModel", uselist = False, + backref = backref("auth", uselist = False, + cascade = "all, delete-orphan")) + + def regen_token(self): + self.token = sha256(_random_binary_string(_TOKEN_LEN)).digest() + + def __init__(self, passwd): + self.set_password(passwd) + + def set_password(self, passwd): + self.salt = _random_binary_string(_SALT_LEN) + self.password = _sprinkle_salt(self, passwd) + self.regen_token() + + def check_password(self, passwd): + passwd = _sprinkle_salt(self, passwd) + return passwd == self.password + + def check_token(self, tk): + return self.token == tk + + def get_token(self): + return self.token diff --git a/misc/server/piztor_server.py b/misc/server/piztor_server.py new file mode 100644 index 0000000..514f95f --- /dev/null +++ b/misc/server/piztor_server.py @@ -0,0 +1,234 @@ +import sqlalchemy +import SocketServer, socket, select +import struct +import os + +from sqlalchemy import create_engine +from sqlalchemy import Column, Integer, String, Float +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import sessionmaker +from random import randint + +engine = create_engine('sqlite:///t.sqlite', echo = False) +Base = declarative_base() +Session = sessionmaker(bind=engine) + +def get_hex(data): + return "".join([hex(ord(c))[2:].zfill(2) for c in data]) + +class PiztorError(Exception): + def __init__(self, msg): + self.err_msg = msg + def __str__(self, msg): + return self.err_msg + +class ConnectionError(PiztorError): + pass + +class ReqReadError(ConnectionError): + def __init__(self): + super(ReqReadError, self).__init__("Error while reading request") + +class ReqInvalidError(ConnectionError): + def __init__(self): + super(ReqInvalidError, self).__init__("Invalid request") + +class TokenInvalidError(ConnectionError): + def __init__(self): + super(TokenInvalidError, self).__init__("Invalid token") + +class DataManager(object): + def __init__(self, piz_srv): + self.piz_srv = piz_srv + +class UserManager(DataManager): + + class User(Base): + __tablename__ = 'users' + id = Column(Integer, primary_key = True) + gid = Column(Integer) + username = Column(String) + password = Column(String) + token = Column(Integer) + + def get_user_by_token(self, token): + session = Session() + User = UserManager.User + entries = session.query(User).filter(User.token == token).all() + if len(entries) == 0: + raise TokenInvalidError() + return entries[0] + + def authentication_handle(self, opt_type, data): + print "Parsing User Data" + pos = -1 + for i in xrange(0, len(data)): + if data[i] == '\0': + print i + if pos != -1: + raise ReqInvalidError() + pos = i + break + if pos == -1: + raise ReqInvalidError() + username = data[0:pos] + password = data[pos + 1:] + + print "Trying to login with following info:" + print (username, password) + + session = Session() + entries = session.query(UserManager.User). \ + filter(UserManager.User.username == username).all() + if len(entries) == 0: + return struct.pack("!BLB", 0, 0, 1) + entry = entries[0] + if entry.password != password: # Auth failed + print "Login failed!" + return struct.pack("!BLB", 0, 0, 1) + else: # Succeeded + print "Logged in sucessfully!" + entry.token = randint(0, 2147483647) + session.commit() + return struct.pack("!BLB", 0, entry.token, 0) + + +class MesgManager(DataManager): + def mesg_sending_handle(self, opt_type, data): + print "Parsing Mesg Data" + try: + if len(data) < 8: + raise ReqInvalidError() + sender_token, recv_id = struct.unpack("!LL", data[:8]) + msg = data[8:] + print (sender_token, recv_id, msg) + return struct.pack("!B", 1) + except struct.error: + raise ReqInvalidError() + +class LocationManager(DataManager): + + class LocationInfo(Base): + __tablename__ = "location_info" + uid = Column(Integer, primary_key = True) + lat = Column(Float(precesion = 64)) + lng = Column(Float(precesion = 64)) + # More: last_update + + def location_update_handle(self, opt_type, data): + print "Parsing a Location Update" + try: + if len(data) < 8: + raise ReqInvalidError() + sender_token, lat, lng = struct.unpack("!Ldd", data) + print "Updating location data with following info:" + print (sender_token, lat, lng) + + user = self.piz_srv. \ + user_mgr.get_user_by_token(sender_token) + session = Session() + LInfo = LocationManager.LocationInfo + q = session.query(LInfo).filter(LInfo.uid == user.id) + entry = q.first() + entry.lat = lat + entry.lng = lng + session.commit() + print "Location update succeeded!" + return struct.pack("!BB", 2, 0) + except TokenInvalidError: + print "Location update failed!" + return struct.pack("!BB", 2, 1) + except struct.error: + raise ReqInvalidError() + + def location_request_handle(self, opt_type, data): + print "Parsing a Location Request" + try: + if len(data) != 8: + raise ReqInvalidError() + sender_token, gid = struct.unpack("!LL", data) + print "Requesting location data with following info:" + print (sender_token, gid) + session = Session() + UInfo = UserManager.User + LInfo = LocationManager.LocationInfo + user_list = session.query(UInfo).filter(UInfo.gid == gid).all() + reply = struct.pack("!BL", 3, len(user_list)) + for user in user_list: + loc = session.query(LInfo).filter(LInfo.uid == user.id).first() + reply += struct.pack("!Ldd", user.id, loc.lat, loc.lng) + print get_hex(reply) + return reply + except struct.error: + raise ReqInvalidError() + +class PiztorServer(): + + + class GenericHandler(SocketServer.StreamRequestHandler): + + def handle(self): + print self.piz_srv + sock = self.request + sock.settimeout(100) +# sock.setblocking(0) + data = "" + try: + while True: +# ready = select.select([sock], [], [], 10) +# if not ready[0]: +# raise ReqReadError() + buff = sock.recv(4096) + if len(buff) == 0: + break # terminated + else: + data += buff + sock.shutdown(socket.SHUT_RD) + + print "Got the data:" + get_hex(data) + + if len(data) < 1: + print "invalid length" + raise ReqInvalidError() + opt_id = struct.unpack("!B", data[0])[0] + print opt_id + reply = self.piz_srv.mgr_map[opt_id](opt_id, data[1:]) + sock.sendall(reply) + finally: + sock.close() + + class ForkingEchoServer(SocketServer.ForkingMixIn, SocketServer.TCPServer): + pass + + def __init__(self, host, port): + PiztorServer.GenericHandler.piz_srv = self + srv = PiztorServer.ForkingEchoServer((host, port), + PiztorServer.GenericHandler) + srv.request_queue_size = 100 +# srv.timeout = 2 + self.server = srv + + self.user_mgr = UserManager(self) + self.mesg_mgr = MesgManager(self) + self.location_mgr = LocationManager(self) + + self.mgr_map = [ self.user_mgr.authentication_handle, + self.mesg_mgr.mesg_sending_handle, + self.location_mgr.location_update_handle, + self.location_mgr.location_request_handle] + + Base.metadata.create_all(engine) + + + def run(self): + try: + self.server.serve_forever() + except KeyboardInterrupt: + print "Exiting..." + self.server.shutdown() + print "Server shutdown" + +if __name__ == "__main__": + + ps = PiztorServer("localhost", 9990) + ps.run() diff --git a/misc/server/ptp.rst b/misc/server/ptp.rst new file mode 100644 index 0000000..8aef2b7 --- /dev/null +++ b/misc/server/ptp.rst @@ -0,0 +1,96 @@ +Piztor Transmission Protocol v0.2 +--------------------------------- + +- General + + - Request + + :: + + +---4b---+---1b---+-------?b--------+ + | LENGTH | OPT ID | SPECIFIC DATA | + +--int---+-uchar--+-----------------+ + + - Response + + :: + + +---4b---+---1b---+------?b---------+ + | 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. + +- Authentication + + - Request + + :: + + +--1b---+-----?b------+-----?b-----+ + | 0x00 | USERNAME | PASSWORD | + +-uchar-+-------------+------------+ + + - Response + + :: + + +--1b---+---1b---+---4b----+----16b-----+ + | 0x00 | STATUS | USER_ID | USER_TOKEN | + +-uchar-+--uchar-+---int---+----raw-----+ + + ``STATUS`` : + + - 0x00 for success + - 0x01 for failure + +- Location Update + + - Request + + :: + + +--1b---+-----16b------+-----8b-----+------8b-----+ + | 0x02 | USER_TOKEN | LATITUDE | LONGITUDE | + +-uchar-+------raw-----+---double---+---double----+ + + - Response + + :: + + +--1b---+---1b---+ + | 0x02 | STATUS | + +-uchar-+--uchar-+ + + ``STATUS`` : + + - 0x00 for success + - 0x01 for invalid token + +- Location Information + + - Request + + :: + + +--1b---+-----16b------+------4b-----+ + | 0x03 | USER_TOKEN | GROUP_ID | + +-uchar-+-----raw------+-----int-----+ + + - Response + + :: + + +--1b---+---1b---+-----4b----+------20b-------+-----+ + | 0x03 | STATUS | ENTRY_CNT | LOCATION_ENTRY | ... | + +-uchar-+-uchar--+----int----+----------------+-----+ + + ``LOCATION_ENTRY`` : + + :: + + +---4b----+----8b----+-----8b----+ + | USER_ID | LATITUDE | LONGITUDE | + +---int---+--double--+--double---+ + diff --git a/misc/server/rush.py b/misc/server/rush.py new file mode 100644 index 0000000..f01804c --- /dev/null +++ b/misc/server/rush.py @@ -0,0 +1,14 @@ +from subprocess import Popen +procs = [] + +try: + for i in xrange(10): + p = Popen(["python", "client.py", str(i)]) + procs.append(p) + #p.wait() + print "done" + +except KeyboardInterrupt: + print "killing" + for p in procs: + p.kill() -- cgit v1.2.3-70-g09d2