From 8f06e942f25b424661201c1eb40955e092ac0560 Mon Sep 17 00:00:00 2001 From: Gabriele Morello Date: Mon, 15 Nov 2021 14:28:19 +0100 Subject: [PATCH] big update changed the whole structure, src contains all the interfaces and core that contains modules that shouldn't be executed, removed every config.json read, every parameter is either and argument or a constant if the user shouldn't modify it, added a lot of options, when running a single channel, added a subparser for Server.py when running the channel locally, updated README.md in src, fixed an issue in channel related to noise and eve, removed some ugly path insertions please note: some changes are made automatically while refactoring FTP is not compatible with the new structure, no plans to adapt it for now, --- QKDSimkit/FTP/FTP_Server.py | 2 +- QKDSimkit/QKDSimChannels/QKD_Channel.py | 29 -- QKDSimkit/QKDSimClients/__init__.py | 0 QKDSimkit/QKDSimClients/models.py | 65 ----- QKDSimkit/QKDSimClients/qexceptions.py | 5 - QKDSimkit/Server_Client_wrapper/__init__.py | 0 QKDSimkit/{ => data}/config.json | 0 QKDSimkit/src/Channel.py | 34 +++ .../{Server_Client_wrapper => src}/Client.py | 104 ++++--- .../{Server_Client_wrapper => src}/README.md | 26 +- .../{Server_Client_wrapper => src}/Server.py | 274 +++++++++--------- QKDSimkit/{QKDSimChannels => src}/__init__.py | 0 QKDSimkit/src/core/__init__.py | 8 + .../QKD_Alice.py => src/core/alice.py} | 7 +- .../QKD_Bob.py => src/core/bob.py} | 6 +- .../{QKDSimChannels => src/core}/channel.py | 29 +- .../core}/channel_features.py | 19 +- .../{QKDSimChannels => src/core}/models.py | 0 QKDSimkit/{QKDSimClients => src/core}/node.py | 121 ++++---- .../core}/qexceptions.py | 0 .../{QKDSimClients => src/core}/receiver.py | 13 +- .../{QKDSimClients => src/core}/sender.py | 12 +- .../{QKDSimClients => src/core}/utils.py | 1 - .../{QKDSimClients => src}/p2p_servers.py | 30 +- 24 files changed, 377 insertions(+), 408 deletions(-) delete mode 100644 QKDSimkit/QKDSimChannels/QKD_Channel.py delete mode 100644 QKDSimkit/QKDSimClients/__init__.py delete mode 100644 QKDSimkit/QKDSimClients/models.py delete mode 100644 QKDSimkit/QKDSimClients/qexceptions.py delete mode 100644 QKDSimkit/Server_Client_wrapper/__init__.py rename QKDSimkit/{ => data}/config.json (100%) create mode 100644 QKDSimkit/src/Channel.py rename QKDSimkit/{Server_Client_wrapper => src}/Client.py (76%) rename QKDSimkit/{Server_Client_wrapper => src}/README.md (78%) rename QKDSimkit/{Server_Client_wrapper => src}/Server.py (64%) rename QKDSimkit/{QKDSimChannels => src}/__init__.py (100%) create mode 100644 QKDSimkit/src/core/__init__.py rename QKDSimkit/{QKDSimClients/QKD_Alice.py => src/core/alice.py} (96%) rename QKDSimkit/{QKDSimClients/QKD_Bob.py => src/core/bob.py} (96%) rename QKDSimkit/{QKDSimChannels => src/core}/channel.py (76%) rename QKDSimkit/{QKDSimChannels => src/core}/channel_features.py (75%) rename QKDSimkit/{QKDSimChannels => src/core}/models.py (100%) rename QKDSimkit/{QKDSimClients => src/core}/node.py (70%) rename QKDSimkit/{QKDSimChannels => src/core}/qexceptions.py (100%) rename QKDSimkit/{QKDSimClients => src/core}/receiver.py (95%) rename QKDSimkit/{QKDSimClients => src/core}/sender.py (92%) rename QKDSimkit/{QKDSimClients => src/core}/utils.py (99%) rename QKDSimkit/{QKDSimClients => src}/p2p_servers.py (71%) diff --git a/QKDSimkit/FTP/FTP_Server.py b/QKDSimkit/FTP/FTP_Server.py index 3a5a92d..903b17c 100644 --- a/QKDSimkit/FTP/FTP_Server.py +++ b/QKDSimkit/FTP/FTP_Server.py @@ -67,7 +67,7 @@ def startServer(self): sys.exit() try: - s = json.load(open('../config.json', ))['Server'] + s = json.load(open('../data/config.json', ))['Server'] handler = MyHandler # select the created custom FTP handler handler.authorizer = authorizer # assign the authorizer to the handler handler.banner = "Server Ready.." # server banner is returned when the client calls a getWelcomeMessage() call diff --git a/QKDSimkit/QKDSimChannels/QKD_Channel.py b/QKDSimkit/QKDSimChannels/QKD_Channel.py deleted file mode 100644 index fec5a48..0000000 --- a/QKDSimkit/QKDSimChannels/QKD_Channel.py +++ /dev/null @@ -1,29 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Wed May 12 21:31:42 2021 - -@author: Alberto Di Meglio -""" - -import os -import sys - -this_file_dir = os.path.dirname(os.path.realpath(__file__)) -sys.path.insert(0, this_file_dir + "/../..") - -from QKDSimkit.QKDSimChannels.channel import public_channel - - -def run_channel(): - # clean up - _ = os.system('clear') - - # instantiate a receiver channel - theChannel = public_channel() - - # initiate the channel and listen for connections - theChannel.initiate_channel() - - -if __name__ == '__main__': - run_channel() diff --git a/QKDSimkit/QKDSimClients/__init__.py b/QKDSimkit/QKDSimClients/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/QKDSimkit/QKDSimClients/models.py b/QKDSimkit/QKDSimClients/models.py deleted file mode 100644 index 5339480..0000000 --- a/QKDSimkit/QKDSimClients/models.py +++ /dev/null @@ -1,65 +0,0 @@ -import random - - -class Photon(object): - """Photon, it has bit, basis and polarization and some methods to manipulate them""" - def __init__(self): - self.bit = self.create_random_bit() - self.basis = self.select_random_basis() - self.polarization = self.set_polarization() - - def create_random_bit(self) -> int: - """Assign a random bit to the photon""" - return random.randint(0, 1) - - def select_random_basis(self): - """Assign a random basis to the photon""" - return random.choice(["RL", "DG"]) # RL = Rectilinear basis; DG = Diagonal basis; - - def set_polarization(self): # Set polarization according to settings of bits and basis. - """Assign the right polarization to the photon according to basis and bit""" - if (self.basis == "RL" and self.bit == 0): - return 0 - elif (self.basis == "RL" and self.bit == 1): - return 90 - elif (self.basis == "DG" and self.bit == 0): - return 45 - elif (self.basis == "DG" and self.bit == 1): - return 135 - - return self.polarization - - def measure(self, polarization: int) -> int: - """Give back a polarization according to the basis of the photon - Args: - polarization (int): polarization - Returns: - polarization - """ - if (self.basis == "RL"): - if polarization in [0, 90]: - return polarization - else: - return random.choice([0, 90]) - elif (self.basis == "DG"): - if polarization in [45, 135]: - return polarization - else: - return random.choice([45, 135]) - - def set_bit_from_measurement(self) -> int: - """Set bits according to settings of polarization and basis. - Returns: - bit: return a bit for each case - """ - if (self.basis == "RL" and self.polarization == 0): - return 0 - elif (self.basis == "RL" and self.polarization == 90): - return 1 - elif (self.basis == "DG" and self.polarization == 45): - return 0 - elif (self.basis == "DG" and self.polarization == 135): - return 1 - - return self.bit - diff --git a/QKDSimkit/QKDSimClients/qexceptions.py b/QKDSimkit/QKDSimClients/qexceptions.py deleted file mode 100644 index f8bb996..0000000 --- a/QKDSimkit/QKDSimClients/qexceptions.py +++ /dev/null @@ -1,5 +0,0 @@ -class qsocketerror(Exception): - pass - -class qobjecterror(Exception): - pass diff --git a/QKDSimkit/Server_Client_wrapper/__init__.py b/QKDSimkit/Server_Client_wrapper/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/QKDSimkit/config.json b/QKDSimkit/data/config.json similarity index 100% rename from QKDSimkit/config.json rename to QKDSimkit/data/config.json diff --git a/QKDSimkit/src/Channel.py b/QKDSimkit/src/Channel.py new file mode 100644 index 0000000..486c944 --- /dev/null +++ b/QKDSimkit/src/Channel.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +""" +Created on Wed May 12 21:31:42 2021 + +@author: Alberto Di Meglio +""" + +import os +import argparse +import core + + +def run_channel(host: str = '', port: str = '5000', noise: float = 0.0, eve: bool = False): + # clean up + _ = os.system('clear') + + # instantiate a receiver channel + theChannel = core.channel.public_channel(host, port, noise, eve) + + # initiate the channel and listen for connections + theChannel.initiate_channel() + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description='Channel for Quantumacy') + parser.add_argument('--host', default='', type=str, + help='Bind socket to this host (default: %(default)s)') + parser.add_argument('--port', default='5000', type=str, + help='Bind socket to this port (default: %(default)s)') + parser.add_argument('-n', '--noise', default=0.0, type=float, + help='Set a noise value for channel, type a float number in [0,1] (default: %(default)s)') + parser.add_argument('-e', '--eve', action='store_true', help='Add an eavesdropper to the channel') + args = parser.parse_args() + run_channel(args.host, args.port, args.noise, args.eve) diff --git a/QKDSimkit/Server_Client_wrapper/Client.py b/QKDSimkit/src/Client.py similarity index 76% rename from QKDSimkit/Server_Client_wrapper/Client.py rename to QKDSimkit/src/Client.py index 45896fe..3ad8025 100644 --- a/QKDSimkit/Server_Client_wrapper/Client.py +++ b/QKDSimkit/src/Client.py @@ -1,54 +1,50 @@ -import http.client -import urllib.parse -import sys -import argparse -import os - -this_file_dir = os.path.dirname(os.path.realpath(__file__)) -sys.path.insert(0, this_file_dir + "/../..") - -from QKDSimkit.QKDSimClients.utils import hash_token, decrypt -from QKDSimkit.QKDSimClients.QKD_Bob import import_key - - -def get_key(alice_address, channel_address, token, number, size): - hashed = hash_token(token) - params = urllib.parse.urlencode({'hashed': hashed}) - conn = http.client.HTTPConnection(f"{alice_address}") - conn.request("GET", f"/hello?{params}") - r = conn.getresponse() - if r.status != 200: - return r.status - data = r.read().decode() - proof = decrypt(token, data) - conn.close() - hash_proof = hash_token(proof) - params = urllib.parse.urlencode({'number': number, 'size': size, 'hashed': hashed, 'hash_proof': hash_proof}) - conn.close() - conn1 = http.client.HTTPConnection(f"{alice_address}") - conn1.request("GET", f"/proof?{params}") - r = conn1.getresponse() - if r.status == 200: - key_list = [] - for n in range(number): - key_list.append(import_key(channel_address=channel_address, ID=hashed, size=size).decode()) - return key_list - else: - return r.status - - -def manage_args(): - parser = argparse.ArgumentParser(description='Client for Quantumacy') - parser.add_argument('alice_address', type=str, help='Address of server/Alice [host:port]') - parser.add_argument('channel_address', type=str, help='Address of channel [host:port]') - parser.add_argument('-t', '--token', default='7KHuKtJ1ZsV21DknPbcsOZIXfmH1_MnKdOIGymsQ5aA=', type=str, - help='Auth token, for development purposes a default token is provided') - parser.add_argument('-n', '--number', default=1, type=int, help="Number of keys (default: %(default)s)") - parser.add_argument('-s', '--size', default=256, type=int, help="Size of keys (default: %(default)s)") - return parser.parse_args() - - -if __name__ == '__main__': - args = manage_args() - l = get_key(args.alice_address, args.channel_address, args.token, args.number, args.size) - print(l) +import http.client +import urllib.parse +import argparse +import core + + +def get_key(alice_address, channel_address, token, number, size): + hashed = core.utils.hash_token(token) + params = urllib.parse.urlencode({'hashed': hashed}) + conn = http.client.HTTPConnection(f"{alice_address}") + conn.request("GET", f"/hello?{params}") + r = conn.getresponse() + if r.status != 200: + return r.status + data = r.read().decode() + proof = core.utils.decrypt(token, data) + conn.close() + hash_proof = core.utils.hash_token(proof) + params = urllib.parse.urlencode({'number': number, 'size': size, 'hashed': hashed, 'hash_proof': hash_proof}) + conn.close() + conn1 = http.client.HTTPConnection(f"{alice_address}") + conn1.request("GET", f"/proof?{params}") + r = conn1.getresponse() + if r.status == 200: + key_list = [] + for n in range(number): + res = core.bob.import_key(channel_address=channel_address, ID=hashed, size=size) + if res == -1: + print("Can't exchange a safe key") + if isinstance(res, bytearray): + key_list.append(res.decode()) + return key_list + else: + return r.status + + +def manage_args(): + parser = argparse.ArgumentParser(description='Client for Quantumacy') + parser.add_argument('alice_address', type=str, help='Address of server/Alice [host:port]') + parser.add_argument('channel_address', type=str, help='Address of channel [host:port]') + parser.add_argument('-t', '--token', default='7KHuKtJ1ZsV21DknPbcsOZIXfmH1_MnKdOIGymsQ5aA=', type=str, + help='Auth token, for development purposes a default token is provided') + parser.add_argument('-n', '--number', default=1, type=int, help="Number of keys (default: %(default)s)") + parser.add_argument('-s', '--size', default=256, type=int, help="Size of keys (default: %(default)s)") + return parser.parse_args() + + +if __name__ == '__main__': + args = manage_args() + l = get_key(args.alice_address, args.channel_address, args.token, args.number, args.size) diff --git a/QKDSimkit/Server_Client_wrapper/README.md b/QKDSimkit/src/README.md similarity index 78% rename from QKDSimkit/Server_Client_wrapper/README.md rename to QKDSimkit/src/README.md index 7ddf334..56c7665 100644 --- a/QKDSimkit/Server_Client_wrapper/README.md +++ b/QKDSimkit/src/README.md @@ -18,15 +18,29 @@ pip install -r ../requirements.txt ### Executing program -* Run with the most basic configuration: channel will run on the same machine as server +* Run with the most basic configuration: channel will run on the same machine as the server ``` -python Server.py -l +python Server.py l ``` -* Run just server and use an external channel + +* Run with personalized channel settings (noise, eavesdropper) +``` +python Server.py l -n 0.5 -e True +``` + +* Run server and use an external channel ``` -python Server.py -ca [host:port] --host [host] --port [port] +python Server.py --host [host] --port [port] ca [host:port] ``` +### Help + +For more options please check +``` +python Server.py -h +python Server.py l -h +python Server.py ca -h +``` ##Client ### Dependencies @@ -54,9 +68,9 @@ python Client.py [server_host:port] [channel_host:port] python Client.py [server_host:port] [channel_host:port] -n [num_keys] -s [size] ``` -## Help +### Help -Any advise for common problems or issues. +For more options please check ``` python Client.py -h ``` diff --git a/QKDSimkit/Server_Client_wrapper/Server.py b/QKDSimkit/src/Server.py similarity index 64% rename from QKDSimkit/Server_Client_wrapper/Server.py rename to QKDSimkit/src/Server.py index 7df8fc3..c32b8e8 100644 --- a/QKDSimkit/Server_Client_wrapper/Server.py +++ b/QKDSimkit/src/Server.py @@ -1,132 +1,142 @@ -import uvicorn -import os -import asyncio -import random -import string -import sys -import json -import argparse - -this_file_dir = os.path.dirname(os.path.realpath(__file__)) -sys.path.insert(0, this_file_dir + "/../..") -data_directory = os.path.dirname(os.path.realpath(__file__)) + '/server_data/' - -from QKDSimkit.QKDSimClients.utils import hash_token, encrypt -from QKDSimkit.QKDSimClients.QKD_Alice import import_key -from QKDSimkit.QKDSimChannels.QKD_Channel import run_channel -from threading import Thread -from fastapi import FastAPI, Request, HTTPException, BackgroundTasks -from fastapi.middleware.cors import CORSMiddleware -from fastapi.responses import Response -from aiocache import Cache - - -app = FastAPI() - -cache = Cache(Cache.REDIS, endpoint="localhost", port=6379, namespace="main") - -origins = [ - "*" -] - -app.add_middleware( - CORSMiddleware, - allow_origins=origins, - allow_credentials=True, - allow_methods=["*"], - allow_headers=["*"], -) - - -async def add_user(token): - users = {} - hashed = hash_token(token) - users[hashed] = token - await cache.set('users', users) - - -async def add_channel(address): - await cache.set('address', address) - - -async def start_alice(number: int, size: int, ID: str): - id_keys_map = await cache.get('id_keys') - if not id_keys_map: - id_keys_map = {} - address = await cache.get('address') - if not address: - raise Exception - key_list = [] - s = json.load(open('../config.json', ))['channel'] - address = '{}:{}'.format(s['host'], s['port']) - for i in range(number): - key_list.append(import_key(channel_address=address, ID=ID, size=size).decode()) - id_keys_map[ID] = key_list - await cache.set('id_keys', id_keys_map) - - -@app.get("/hello") -async def root(hashed: str): - users = await cache.get('users') - if hashed not in users.keys(): - return Response(status_code=404, content='Provided ID does not match any user') - r = ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(512)) - map_hash_r = await cache.get('proof') - if not map_hash_r: - map_hash_r = {} - map_hash_r[hashed] = hash_token(r) - await cache.set('proof', map_hash_r) - return encrypt(users[hashed], r) - - -@app.get("/proof") -async def root(number: int, size: int, hashed: str, hash_proof: str, background_tasks: BackgroundTasks): - users = await cache.get('users') - map_hash_r = await cache.get('proof') - if hashed in users.keys() and hash_proof == map_hash_r[hashed]: - background_tasks.add_task(start_alice, number, size, hashed) - return "Verified!" - return Response(status_code=404, content='Provided ID does not match any user') - - -@app.get("/get_key") -async def root(ID: str = 'id'): - id_keys_map = await cache.get('id_keys') - if ID not in id_keys_map.keys(): - raise HTTPException(status_code=404, detail="No keys for the given ID") - return id_keys_map[ID] - - -@app.middleware("http") -async def filter_get_key(request: Request, call_next): - if request.client.host != '127.0.0.1' and request.url.path == '/get_key': - response = Response(status_code=403, content='Forbidden') - else: - response = await call_next(request) - return response - - -def manage_args(): - parser = argparse.ArgumentParser(description='Server for Quantumacy') - group = parser.add_mutually_exclusive_group(required=True) - group.add_argument('-ca', '--channel_address', type=str, help='Address of channel [host:port]') - group.add_argument('-l', '--local', action='store_true', help='Run the channel at :5000') - parser.add_argument('--host', default='127.0.0.1', type=str, - help='Bind socket to this host (default: %(default)s)') - parser.add_argument('--port', default='5002', type=str, - help='Bind socket to this port (default: %(default)s)') - parser.add_argument('-t', '--token', default='7KHuKtJ1ZsV21DknPbcsOZIXfmH1_MnKdOIGymsQ5aA=', type=str, - help='Auth token, for development purposes a default token is provided') - return parser.parse_args() - - -if __name__ == "__main__": - args = manage_args() - asyncio.run(add_channel(args.channel_address)) - asyncio.run(add_user(args.token)) - if args.local: - asyncio.run(add_channel(':5000')) - _thread = Thread(target=run_channel) - _thread.daemon = True - _thread.start() - uvicorn.run('Server:app', host=args.host, port=int(args.port)) +import uvicorn +import os +import asyncio +import random +import string +import json +import argparse +import core +import Channel +from threading import Thread +from fastapi import FastAPI, Request, HTTPException, BackgroundTasks +from fastapi.middleware.cors import CORSMiddleware +from fastapi.responses import Response +from aiocache import Cache + +data_directory = os.path.dirname(os.path.realpath(__file__)) + '/../data/' + +app = FastAPI() + +cache = Cache(Cache.REDIS, endpoint="localhost", port=6379, namespace="main") + +origins = [ + "*" +] +app.add_middleware( + CORSMiddleware, + allow_origins=origins, + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + + +async def add_user(token): + users = {} + hashed = core.utils.hash_token(token) + users[hashed] = token + await cache.set('users', users) + + +async def add_channel(address): + await cache.set('address', address) + + +async def start_alice(number: int, size: int, ID: str): + id_keys_map = await cache.get('id_keys') + if not id_keys_map: + id_keys_map = {} + address = await cache.get('address') + if not address: + raise Exception + key_list = [] + s = json.load(open('../data/config.json', ))['channel'] + address = '{}:{}'.format(s['host'], s['port']) + for i in range(number): + res = core.alice.import_key(channel_address=address, ID=ID, size=size) + if res == -1: + print("Can't exchange a safe key") + if isinstance(res, bytearray): + key_list.append(res.decode()) + id_keys_map[ID] = key_list + await cache.set('id_keys', id_keys_map) + + +@app.get("/hello") +async def root(hashed: str): + users = await cache.get('users') + if hashed not in users.keys(): + return Response(status_code=404, content='Provided ID does not match any user') + r = ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(512)) + map_hash_r = await cache.get('proof') + if not map_hash_r: + map_hash_r = {} + map_hash_r[hashed] = core.utils.hash_token(r) + await cache.set('proof', map_hash_r) + return core.utils.encrypt(users[hashed], r) + + +@app.get("/proof") +async def root(number: int, size: int, hashed: str, hash_proof: str, background_tasks: BackgroundTasks): + users = await cache.get('users') + map_hash_r = await cache.get('proof') + if hashed in users.keys() and hash_proof == map_hash_r[hashed]: + background_tasks.add_task(start_alice, number, size, hashed) + return "Verified!" + return Response(status_code=404, content='Provided ID does not match any user') + + +@app.get("/get_key") +async def root(ID: str = 'id'): + id_keys_map = await cache.get('id_keys') + if ID not in id_keys_map.keys(): + raise HTTPException(status_code=404, detail="No keys for the given ID") + return id_keys_map[ID] + + +@app.middleware("http") +async def filter_get_key(request: Request, call_next): + if request.client.host != '127.0.0.1' and request.url.path == '/get_key': + response = Response(status_code=403, content='Forbidden') + else: + response = await call_next(request) + return response + + +def manage_args(): + parser = argparse.ArgumentParser(description='Server for Quantumacy') + parser.add_argument('--host', default='127.0.0.1', type=str, + help='Bind socket to this host (default: %(default)s)') + parser.add_argument('--port', default='5002', type=str, + help='Bind socket to this port (default: %(default)s)') + parser.add_argument('-t', '--token', default='7KHuKtJ1ZsV21DknPbcsOZIXfmH1_MnKdOIGymsQ5aA=', type=str, + help='Auth token, for development purposes a default token is provided') + channels = parser.add_subparsers(title='Channel type', dest='channel_type', required=True, + help='Specify where the channel is (required)') + parser_l = channels.add_parser('l', help='Run the channel on this machine') + parser_l.add_argument('-ch', '--channel_host', default='', type=str, + help='Bind socket to this host (default: %(default)s)') + parser_l.add_argument('-cp', '--channel_port', default='5000', type=str, + help='Bind socket to this port (default: %(default)s)') + parser_l.add_argument('-n', '--noise', default=0.0, type=float, + help='Set a noise value for channel, type a float number in [0,1] (default: %(default)s)') + parser_l.add_argument('-e', '--eve', action='store_true', + help='Add an eavesdropper to the channel') + parser_e = channels.add_parser('e', help='Connect to an external channel') + parser_e.add_argument('-ca', '--channel_address', type=str, required=True, help='Address of channel [host:port]') + return parser.parse_args() + + +if __name__ == "__main__": + args = manage_args() + asyncio.run(add_user(args.token)) + if args.channel_type == 'l': + address = '{h}:{p}'.format(h=args.channel_host, p=args.channel_port) + asyncio.run(add_channel(address)) + _thread = Thread(target=Channel.run_channel, args=(args.channel_host, args.channel_port, args.noise, + args.eve)) + _thread.daemon = True + _thread.start() + else: + asyncio.run(add_channel(args.channel_address)) + uvicorn.run('Server:app', host=args.host, port=int(args.port)) diff --git a/QKDSimkit/QKDSimChannels/__init__.py b/QKDSimkit/src/__init__.py similarity index 100% rename from QKDSimkit/QKDSimChannels/__init__.py rename to QKDSimkit/src/__init__.py diff --git a/QKDSimkit/src/core/__init__.py b/QKDSimkit/src/core/__init__.py new file mode 100644 index 0000000..0ad9511 --- /dev/null +++ b/QKDSimkit/src/core/__init__.py @@ -0,0 +1,8 @@ +from .alice import * +from .bob import * +from .node import Node +from .sender import Sender +from .receiver import Receiver +from .utils import * +from .channel import * + diff --git a/QKDSimkit/QKDSimClients/QKD_Alice.py b/QKDSimkit/src/core/alice.py similarity index 96% rename from QKDSimkit/QKDSimClients/QKD_Alice.py rename to QKDSimkit/src/core/alice.py index b522b0c..10ac7e2 100644 --- a/QKDSimkit/QKDSimClients/QKD_Alice.py +++ b/QKDSimkit/src/core/alice.py @@ -9,9 +9,8 @@ import sys import logging from base64 import urlsafe_b64encode -from QKDSimkit.QKDSimClients.sender import sender -from QKDSimkit.QKDSimChannels.qexceptions import qsocketerror, qobjecterror - +from .sender import Sender +from .qexceptions import qsocketerror def import_key(channel_address: str, ID: str, size: int = 256): @@ -23,7 +22,7 @@ def import_key(channel_address: str, ID: str, size: int = 256): _ = os.system('clear') for count in range(0, 1000): - alice = sender(ID, size) + alice = Sender(ID, size) try: # connect to quantum channel alice.connect_to_channel(channelIP, channelPort) diff --git a/QKDSimkit/QKDSimClients/QKD_Bob.py b/QKDSimkit/src/core/bob.py similarity index 96% rename from QKDSimkit/QKDSimClients/QKD_Bob.py rename to QKDSimkit/src/core/bob.py index c7eac72..36bde82 100644 --- a/QKDSimkit/QKDSimClients/QKD_Bob.py +++ b/QKDSimkit/src/core/bob.py @@ -9,8 +9,8 @@ import sys import logging from base64 import urlsafe_b64encode -from QKDSimkit.QKDSimClients.receiver import receiver -from QKDSimkit.QKDSimChannels.qexceptions import qsocketerror +from .receiver import Receiver +from .qexceptions import qsocketerror def import_key(channel_address: str, ID: str, size: int = 256): @@ -22,7 +22,7 @@ def import_key(channel_address: str, ID: str, size: int = 256): _ = os.system('clear') for count in range(0, 1000): - bob = receiver(ID, size) + bob = Receiver(ID, size) try: # connect to channel diff --git a/QKDSimkit/QKDSimChannels/channel.py b/QKDSimkit/src/core/channel.py similarity index 76% rename from QKDSimkit/QKDSimChannels/channel.py rename to QKDSimkit/src/core/channel.py index ade70aa..e2b1704 100644 --- a/QKDSimkit/QKDSimChannels/channel.py +++ b/QKDSimkit/src/core/channel.py @@ -1,17 +1,19 @@ import socket import logging -import json -import os from threading import Thread -from QKDSimkit.QKDSimChannels.qexceptions import qsocketerror -from QKDSimkit.QKDSimChannels.channel_features import eavesdropper, random_errors +from .qexceptions import qsocketerror +from .channel_features import eavesdropper, random_errors logging.basicConfig(level=logging.DEBUG) -config_directory = os.path.dirname(os.path.realpath(__file__)) + '/..' + class public_channel(object): # insecure public classical/quantum channel - def __init__(self): - self.__dict__ = json.load(open(config_directory + '/config.json',))['channel'] + def __init__(self, host: str, port: str, noise: float, eve: bool): + self.host = host + self.port = port + self.noise = noise + self.eve = eve + self.buffer_size = 8192 self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # reusable socket self.ip_list = [] # blacklist already created connections @@ -22,6 +24,8 @@ def initiate_channel(self, *port): """Start channel""" if len(port) > 0: self.port = str(port[0]) + if isinstance(self.port, str): + self.port = int(self.port) try: self.socket.bind((self.host, self.port)) @@ -50,11 +54,12 @@ def initiate_connection(self, conn, addr): try: data = conn.recv(self.buffer_size) message = data.decode() - if message.split(':')[0] == 'qpulse' and message.split(':')[1] != 'ack': - if self.eve: - message = eavesdropper(str(message).split(':', 1)[1]) - if self.noise > 0: - message = random_errors(str(message).split(':', 1)[1], self.noise) + if len(message.split(':')) > 1: + if message.split(':')[1] == 'qpulse' and message.split(':')[2] != 'ack': + if self.eve: + message = eavesdropper(message.split(':', 2)) + if self.noise > 0: + message = random_errors(message.split(':', 2), self.noise) except ConnectionResetError: break except ConnectionAbortedError: diff --git a/QKDSimkit/QKDSimChannels/channel_features.py b/QKDSimkit/src/core/channel_features.py similarity index 75% rename from QKDSimkit/QKDSimChannels/channel_features.py rename to QKDSimkit/src/core/channel_features.py index 5cd0d0c..d20a923 100644 --- a/QKDSimkit/QKDSimChannels/channel_features.py +++ b/QKDSimkit/src/core/channel_features.py @@ -1,10 +1,10 @@ import random import sys import logging -from QKDSimkit.QKDSimChannels.models import Photon +from .models import Photon -def eavesdropper(photon_stream: str) -> str: +def eavesdropper(photon_stream) -> str: """Method to simulate an eavesdropper in a quantum channel Args: photon_stream (str): message containing polarizations @@ -13,19 +13,20 @@ def eavesdropper(photon_stream: str) -> str: """ try: photon_pulse = [] - new_message = 'qpulse:' - polarization_vector = photon_stream.split("~")[:-1] + new_message = '' + photon_stream[0] + ':' + photon_stream[1] + ':' + polarization_vector = photon_stream[2].split("~")[:-1] for p in range(len(polarization_vector)): photon_pulse.append(Photon()) photon_pulse[p].polarization = photon_pulse[p].measure(int(polarization_vector[p])) photon_pulse[p].bit = photon_pulse[p].set_bit_from_measurement() new_message += str(photon_pulse[p].polarization) + '~' - return new_message + return new_message + ':' except Exception as e: logging.error('Eavesdropper error:\n' + str(e)) sys.exit() -def random_errors(photon_stream: str, rate: float) -> str: + +def random_errors(photon_stream, rate: float) -> str: """Method to simulate random errors in a quantum channel Args: photon_stream (str): message containing polarizations @@ -34,8 +35,8 @@ def random_errors(photon_stream: str, rate: float) -> str: message with errors """ try: - polarization_vector = photon_stream.split("~")[:-1] - new_message = 'qpulse:' + polarization_vector = photon_stream[2].split("~")[:-1] + new_message = '' + photon_stream[0] + ':' + photon_stream[1] + ':' count = 0 for p in polarization_vector: if random.randint(1, 100) <= rate*100: @@ -45,7 +46,7 @@ def random_errors(photon_stream: str, rate: float) -> str: else: new_message += str(p) + '~' logging.info('Errors: ' + str(count)) - return new_message + return new_message + ':' except Exception as e: logging.error("Failed to add errors in photon stream:\n" + str(e)) sys.exit() diff --git a/QKDSimkit/QKDSimChannels/models.py b/QKDSimkit/src/core/models.py similarity index 100% rename from QKDSimkit/QKDSimChannels/models.py rename to QKDSimkit/src/core/models.py diff --git a/QKDSimkit/QKDSimClients/node.py b/QKDSimkit/src/core/node.py similarity index 70% rename from QKDSimkit/QKDSimClients/node.py rename to QKDSimkit/src/core/node.py index 9b2e255..f9cc5ae 100644 --- a/QKDSimkit/QKDSimClients/node.py +++ b/QKDSimkit/src/core/node.py @@ -1,23 +1,31 @@ import socket import ast import logging -import json import select import re import abc -import os -from cryptography.fernet import Fernet -from QKDSimkit.QKDSimClients.utils import validate -from QKDSimkit.QKDSimChannels.qexceptions import qsocketerror +from .utils import validate +from .qexceptions import qsocketerror + +MIN_SHARED = 20 +BUFFER_SIZE = 8192 +TIMEOUT_IN_SECONDS = 1 +CONNECTION_ATTEMPTS = 50 +MAX_REPETITIONS = 1000 +MIN_SHARED_PERCENT = 0.89 -config_directory = os.path.dirname(os.path.realpath(__file__)) + '/..' class Node(object): """Father class for receiver and sender """ def __init__(self, ID, size): - self.__dict__ = json.load(open(config_directory + '/config.json', ))['node'] + self.min_shared = MIN_SHARED + self.buffer_size = BUFFER_SIZE + self.timeout_in_seconds = TIMEOUT_IN_SECONDS + self.connection_attempts = CONNECTION_ATTEMPTS + self.max_repetitions = MAX_REPETITIONS + self.min_shared_percent = MIN_SHARED_PERCENT self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.ID = ID self.photon_pulse = [] @@ -34,7 +42,6 @@ def __init__(self, ID, size): self.fragments = [] self.regex = r'\((.)*\):' self.photon_pulse_size = size * 5 - # self.token = self.get_key_from_password(password) def connect_to_channel(self, address: str, port: int): """It starts the connection with the channel @@ -115,8 +122,7 @@ def validate(self) -> int: Returns: 1: keys are equals 0: error rate is below a given percent - -1: error rate too high - """ + -1: error rate too high""" percent = validate(self.sub_shared_key, self.other_sub_key) logging.info('Correct bits percentage: ' + str(percent)) if percent == 1: @@ -126,33 +132,33 @@ def validate(self) -> int: if percent < self.min_shared_percent: return -1 - # def encrypt_not_qpulse(self, header: str, message: str): - # """encrypt payload - # encrypt the payload of a message if the header is not 'qpulse' - # - # Returns: - # encrypted message (str): string made by header and encrypted payload to split parts there are ':' - # """ - # if header != 'qpulse': - # cipher = Fernet(self.token) - # enc_message = cipher.encrypt(message.encode()) - # return header + ':' + enc_message.decode() + ':' - # else: - # return header + ':' + message + ':' - - # def decrypt_not_qpulse(self, header: str, message: str): - # """ decrypt payload - # decrypt the payload of a message if the header is not 'qpulse' - # - # Returns: - # decrypted message (str): string with decrypted payload, note that the header is not included - # """ - # if header != 'qpulse': - # cipher = Fernet(self.token) - # message = cipher.decrypt(bytes(message.encode())) - # return message.decode() - # else: - # return message + '''def encrypt_not_qpulse(self, header: str, message: str): + """encrypt payload + encrypt the payload of a message if the header is not 'qpulse' + + Returns: + encrypted message (str): string made by header and encrypted payload to split parts there are ':' + """ + if header != 'qpulse': + cipher = Fernet(self.token) + enc_message = cipher.encrypt(message.encode()) + return header + ':' + enc_message.decode() + ':' + else: + return header + ':' + message + ':' + + def decrypt_not_qpulse(self, header: str, message: str): + """ decrypt payload + decrypt the payload of a message if the header is not 'qpulse' + + Returns: + decrypted message (str): string with decrypted payload, note that the header is not included + """ + if header != 'qpulse': + cipher = Fernet(self.token) + message = cipher.decrypt(bytes(message.encode())) + return message.decode() + else: + return message''' def recv_all(self) -> list: """receive a message @@ -178,31 +184,32 @@ def recv_all(self) -> list: return fragments[1:] # we don't need to return ID because we already checked it is correct @abc.abstractmethod - def send(self): + def send(self, header: str, message: str): """abstract method""" print("send(): Override me") @abc.abstractmethod - def recv(self): + def recv(self, header: str): """abstract method""" print("receive(): Override me") - # def get_key_from_password(self, password: str) -> bytes: - # """ it uses a password to generate a base64, urlsafe, encrypted key - # Args: - # password (str): password to use to generate the key - # Returns: - # key (bytearray): key encrypted with the password - # """ - # salt = os.urandom(16) - # kdf = PBKDF2HMAC( - # algorithm=hashes.SHA256(), - # length=32, - # salt=salt, - # iterations=100000, - # backend=default_backend() - # ) - # key = base64.urlsafe_b64encode(kdf.derive(bytes(password, 'utf-8'))) - # return key - # +''' + def get_key_from_password(self, password: str) -> bytes: + """ it uses a password to generate a base64, urlsafe, encrypted key + Args: + password (str): password to use to generate the key + Returns: + key (bytearray): key encrypted with the password + """ + salt = os.urandom(16) + kdf = PBKDF2HMAC( + algorithm=hashes.SHA256(), + length=32, + salt=salt, + iterations=100000, + backend=default_backend() + ) + key = base64.urlsafe_b64encode(kdf.derive(bytes(password, 'utf-8'))) + return key +''' diff --git a/QKDSimkit/QKDSimChannels/qexceptions.py b/QKDSimkit/src/core/qexceptions.py similarity index 100% rename from QKDSimkit/QKDSimChannels/qexceptions.py rename to QKDSimkit/src/core/qexceptions.py diff --git a/QKDSimkit/QKDSimClients/receiver.py b/QKDSimkit/src/core/receiver.py similarity index 95% rename from QKDSimkit/QKDSimClients/receiver.py rename to QKDSimkit/src/core/receiver.py index 0d1576f..4e31987 100644 --- a/QKDSimkit/QKDSimClients/receiver.py +++ b/QKDSimkit/src/core/receiver.py @@ -1,14 +1,14 @@ import socket import sys import logging -from QKDSimkit.QKDSimClients.node import Node -from QKDSimkit.QKDSimChannels.models import Photon -from QKDSimkit.QKDSimChannels.qexceptions import qsocketerror, qobjecterror +from .node import Node +from .models import Photon +from .qexceptions import qsocketerror, qobjecterror logging.basicConfig(level=logging.DEBUG) -class receiver(Node): +class Receiver(Node): """Receiver class, it expands node, it contains methods to communicate a sender node, it can't take action but it has to wait for the sender node for sending data, it can answer to a request with a message or to a message with an acknowledgement""" @@ -47,7 +47,6 @@ def listen_quantum(self): def recv(self, header: str) -> str: """Receive function for receiver node - it listen for a message, in case the header of the received message doesn't match it checks if an acknowledgment for the received message has been already sent or if the received message is an acknowledgement itself for a previously sent message, it sends a new acknowledgment in the first case and it sends again the message in the @@ -74,7 +73,6 @@ def recv(self, header: str) -> str: logging.info("Sent: " + label + ':' + self.sent_messages[label] + ':') continue elif label == header: - # dec_message = self.decrypt_not_qpulse(label, received[1]) dec_message = received[1] to_be_sent = (self.ID + ':' + header + ":ack:").encode() self.socket.send(to_be_sent) @@ -94,7 +92,6 @@ def recv(self, header: str) -> str: def send(self, header: str, message: str): """ Send method for receiver - it listens for the request from the sender node, in case the header of the received message doesn't match it checks if an acknowledgment for the received header has been already sent or if the message for the requested header has been already sent, it sends a new acknowledgement in the first case and it sends again the message in @@ -136,4 +133,4 @@ def send(self, header: str, message: str): sys.exit() except Exception as err: logging.error('Bob failed to receive {0}:\n{1}'.format(header, str(err))) - sys.exit() + sys.exit() \ No newline at end of file diff --git a/QKDSimkit/QKDSimClients/sender.py b/QKDSimkit/src/core/sender.py similarity index 92% rename from QKDSimkit/QKDSimClients/sender.py rename to QKDSimkit/src/core/sender.py index 3ae2b5f..8060cc4 100644 --- a/QKDSimkit/QKDSimClients/sender.py +++ b/QKDSimkit/src/core/sender.py @@ -1,14 +1,14 @@ import socket import sys import logging -from QKDSimkit.QKDSimClients.node import Node -from QKDSimkit.QKDSimChannels.models import Photon -from QKDSimkit.QKDSimChannels.qexceptions import qsocketerror, qobjecterror +from .node import Node +from .models import Photon +from .qexceptions import qsocketerror, qobjecterror logging.basicConfig(level=logging.DEBUG) -class sender(Node): +class Sender(Node): """Sender class, it expands Node, it contains methods to communicate a receiver node, the general idea is that this node dictate the communication and the receiver can just answer""" @@ -57,7 +57,6 @@ def generate_reconciled_key(self): def send(self, header: str, message: str): """Sender method for sender node - it sends a message and wait for an acknowledgment if it doesn't receive the ack in the given time timeout_in_seconds it may try multiple times depending on the variable connection_attempts Args: @@ -65,7 +64,6 @@ def send(self, header: str, message: str): message (str): string to be sent """ try: - #data = self.encrypt_not_qpulse(header, message) data = header + ':' + message + ':' to_be_sent = (self.ID + ':' + data).encode() for i in range(self.connection_attempts): @@ -86,7 +84,6 @@ def send(self, header: str, message: str): def recv(self, header: str): """Receiver method for sender node - It will send a request message with the given header and it will wait for the response for a time timeout_in_seconds it may try multiple times depending on the variable connection_attempts, every received message with a different header will be discarded @@ -101,7 +98,6 @@ def recv(self, header: str): if not received: continue if received[0] == header: - # dec_message = self.decrypt_not_qpulse(received[0], received[1]) dec_message = received[1] logging.info("Received: " + header + ":" + dec_message) return dec_message diff --git a/QKDSimkit/QKDSimClients/utils.py b/QKDSimkit/src/core/utils.py similarity index 99% rename from QKDSimkit/QKDSimClients/utils.py rename to QKDSimkit/src/core/utils.py index 543a9a1..fbbd8a0 100644 --- a/QKDSimkit/QKDSimClients/utils.py +++ b/QKDSimkit/src/core/utils.py @@ -7,7 +7,6 @@ def validate(shared_key: list, other_shared_key: list) -> float: """It compares two keys to find differences - Args: shared_key (str): first key other_shared_key (str): second key diff --git a/QKDSimkit/QKDSimClients/p2p_servers.py b/QKDSimkit/src/p2p_servers.py similarity index 71% rename from QKDSimkit/QKDSimClients/p2p_servers.py rename to QKDSimkit/src/p2p_servers.py index 1263d0a..312ef19 100644 --- a/QKDSimkit/QKDSimClients/p2p_servers.py +++ b/QKDSimkit/src/p2p_servers.py @@ -1,9 +1,8 @@ import uvicorn import json import logging -import QKD_Alice -import QKD_Bob import argparse +import core from pydantic import BaseModel from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware @@ -11,8 +10,8 @@ logging.basicConfig(level=logging.ERROR) -alice = FastAPI() -bob = FastAPI() +alice_app = FastAPI() +bob_app = FastAPI() class qkdParams(BaseModel): @@ -20,27 +19,28 @@ class qkdParams(BaseModel): size: int = 256 ID: str = 'id' + def answer_get(number, size, ID, type): answer = {} keys = [] - s = json.load(open('../config.json', ))['channel'] + s = json.load(open('../data/config.json', ))['channel'] address = '{}:{}'.format(s['host'], s['port']) for i in range(number): if type == 'Alice': - key = QKD_Alice.import_key(channel_address=address, ID=ID, size=size) + key = core.alice.import_key(channel_address=address, ID=ID, size=size) if type == 'Bob': - key = QKD_Bob.import_key(channel_address=address, ID=ID, size=size) + key = core.bob.import_key(channel_address=address, ID=ID, size=size) keys.append({"key_ID": i, "key": key}) answer["keys"] = keys return answer -@alice.get("/test") +@alice_app.get("/test") async def root(number: int = 1, size: int = 256, ID: str = 'id'): return answer_get(number, size, ID, 'Alice') -@bob.get("/test") +@bob_app.get("/test") async def root(number: int = 1, size: int = 256, ID: str = 'id'): return answer_get(number, size, ID, 'Bob') @@ -49,7 +49,7 @@ async def root(number: int = 1, size: int = 256, ID: str = 'id'): "*" ] -alice.add_middleware( +alice_app.add_middleware( CORSMiddleware, allow_origins=origins, allow_credentials=True, @@ -57,7 +57,7 @@ async def root(number: int = 1, size: int = 256, ID: str = 'id'): allow_headers=["*"], ) -bob.add_middleware( +bob_app.add_middleware( CORSMiddleware, allow_origins=origins, allow_credentials=True, @@ -68,9 +68,11 @@ async def root(number: int = 1, size: int = 256, ID: str = 'id'): if __name__ == '__main__': parser = argparse.ArgumentParser(description='Server for Quantumacy') group = parser.add_mutually_exclusive_group(required=True) - group.add_argument('-a', '--sender', action='store_const', dest='node', const='alice', help='Run this server as a sender (Alice) node') - group.add_argument('-b', '--receiver', action='store_const', dest='node', const='bob', help='Run this server as a receiver (Bob) node') + group.add_argument('-a', '--sender', action='store_const', dest='node', const='alice', + help='Run this server as a sender (Alice) node') + group.add_argument('-b', '--receiver', action='store_const', dest='node', const='bob', + help='Run this server as a receiver (Bob) node') parser.add_argument('-c', '--channel', default=':5000', type=str, help='Specify the address of the channel [host:port]') a = parser.parse_args() - uvicorn.run('p2p_servers:{}'.format(a.node), port=5002) + uvicorn.run('p2p_servers:{}_app'.format(a.node), port=5003)