Ejemplo n.º 1
0
class Client():
    def __init__(self):
        self.current_protocol = "SEP"  # changed to FMP later
        self.expected_incoming_msg_type = "RES_INIT"
        self.ver = 1
        self.NET_PATH = "./network"
        self.FOLDER_PATH = "./client"
        if (self.NET_PATH[-1] != '/') and (self.NET_PATH[-1] != '\\'):
            self.NET_PATH += '/'
        if (self.FOLDER_PATH[-1] != '/') and (self.FOLDER_PATH[-1] != '\\'):
            self.FOLDER_PATH += '/'
        self.DST_ADDR = None
        self.OWN_ADDR = input('Type your own address: ')  # Ex. A, B, or C
        self.netif = network_interface(self.NET_PATH, self.OWN_ADDR)
        self.protocols = Protocols()
        self.session_key = None

        self.client_prikey = None
        self.client_pubkey = None
        self.server_pubkey = None

        self.timeout = 30

    def clean_keys(self):
        self.client_prikey = None
        self.client_pubkey = None
        self.server_pubkey = None

    def load_client_prikey(self):
        try:
            key = open(
                self.FOLDER_PATH + "private_keys/" + self.OWN_ADDR + ".pem",
                "rb").read()
            self.client_prikey = RSA.import_key(key)
        except:
            print("Error: could not load client private key")

    def load_client_pubkey(self):
        try:
            key = open(
                self.FOLDER_PATH + "public_keys/" + self.OWN_ADDR + ".pem",
                "rb").read()
            self.client_pubkey = RSA.import_key(key)
        except:
            print("Error: could not load client public key")

    def load_server_pubkey(self):
        try:
            key = open(
                self.FOLDER_PATH + "public_keys/" + self.DST_ADDR + ".pem",
                "rb").read()
            self.server_pubkey = RSA.import_key(key)
        except:
            print("Error: could not load server public key")

    def fix_length(self, input_byte, desired_length):
        if (len(input_byte) > desired_length):
            print("Error fixing the length of byte")
            return None
        res = b""
        for i in range(desired_length - len(input_byte)):
            res += b"\x00"
        res += input_byte
        return res

    def handle_options(self):
        try:
            opts, args = getopt.getopt(sys.argv[1:],
                                       shortopts='hp:a',
                                       longopts=['help', 'path=', 'addr='])
        except getopt.GetoptError:
            print('wrong options')
            sys.exit(1)

    def generate_nonce(self):
        return secrets.token_bytes(32)

    def SEP_ENC(self, ptext, key):
        cipher_rsa = PKCS1_OAEP.new(key)
        ctext = cipher_rsa.encrypt(ptext)
        return ctext

    def SEP_DEC(self, ctext, key):
        cipher_rsa = PKCS1_OAEP.new(key)
        ptext = cipher_rsa.decrypt(ctext)
        return ptext

    def SEP_SIGN(self, message, key):
        h = SHA256.new(message)
        signature = pkcs1_15.new(key).sign(h)
        return signature

    def SEP_GEN(self, ver, type_sep, type_msg, sender_id, recipient_id,
                encrypted_content, sig_key):
        msg = b""
        msg += ver
        msg += type_sep
        msg += type_msg
        msg += len(encrypted_content).to_bytes(length=5,
                                               byteorder='big')  #msg_len
        msg += sender_id
        msg += recipient_id
        msg += encrypted_content
        sig = self.SEP_SIGN(msg, sig_key)
        #print("Generated sig: ")
        #print(sig)
        msg += sig
        return msg

    def SEP_REQ_INIT(self):

        # generate SEP REQ_INIT
        #header
        ver = self.ver.to_bytes(length=1, byteorder='big')
        type_sep = self.protocols.protocol2num("SEP").to_bytes(length=1,
                                                               byteorder='big')
        type_msg = self.protocols.sep_type2num("REQ_INIT").to_bytes(
            length=1, byteorder='big')
        sender_id = self.fix_length(self.OWN_ADDR.encode('utf-8'), 4)
        recipient_id = self.fix_length(self.DST_ADDR.encode('utf-8'), 4)

        #keys to be used
        #server_public_key = RSA.import_key(open("public_keys/"+self.DST_ADDR+".pem").read())
        #client_private_key = RSA.import_key(open("private_keys/"+self.OWN_ADDR+".pem").read())

        #content
        nonce = self.generate_nonce()
        #pubkey_a = self.client_pubkey
        pubkey_a = self.client_pubkey.export_key()
        #with open("public_keys/"+self.OWN_ADDR+".pem", "rb") as f: pubkey_a = f.read()
        ctext = b""
        ctext += len(pubkey_a).to_bytes(length=4, byteorder='big')
        ctext += pubkey_a
        nonce_encrypted = self.SEP_ENC(nonce, self.server_pubkey)
        ctext += nonce_encrypted

        #send msg
        msg = self.SEP_GEN(ver, type_sep, type_msg, sender_id, recipient_id,
                           ctext, self.client_prikey)
        self.netif.send_msg(self.DST_ADDR, msg)

        #print("Generated nonce: ")
        #print(nonce)
        #print("encrypted nonce: ")
        #print(nonce_encrypted)
        return nonce

    def SEP_REQ_LOGIN(self):
        password = getpass('Type your login password: '******'big')
        type_sep = self.protocols.protocol2num("SEP").to_bytes(length=1,
                                                               byteorder='big')
        type_msg = self.protocols.sep_type2num("REQ_LOGIN").to_bytes(
            length=1, byteorder='big')
        sender_id = self.fix_length(self.OWN_ADDR.encode('utf-8'), 4)
        recipient_id = self.fix_length(self.DST_ADDR.encode('utf-8'), 4)

        #keys to be used
        #server_public_key = RSA.import_key(open("public_keys/"+self.DST_ADDR+".pem").read())
        #client_private_key = RSA.import_key(open("private_keys/"+self.OWN_ADDR+".pem").read())

        #content
        ctext = self.SEP_ENC(password.encode('utf-8'), self.server_pubkey)

        #send msg
        msg = self.SEP_GEN(ver, type_sep, type_msg, sender_id, recipient_id,
                           ctext, self.client_prikey)
        self.netif.send_msg(self.DST_ADDR, msg)
        return True

    def read_SEP(self, msg):
        type_msg = msg[2]
        if type_msg == self.protocols.sep_type2num(
                self.expected_incoming_msg_type):
            print("(OK): message type matches")
        else:
            print("Error: unexpected message type")
            return None
        msg_len = int.from_bytes(msg[3:8], 'big')  #int
        if len(msg) == 16 + msg_len + 256:
            print("(OK): message length matches")
        else:
            print("Error: message length does not match")
            return None
        sender_id = msg[8:12].decode('utf-8').strip(chr(0))  #string
        recipient_id = msg[12:16].decode('utf-8').strip(chr(0))  #string
        if recipient_id == self.OWN_ADDR:
            print("(OK): correct recipient address")
        else:
            print("Error: wrong recipient address")
            return None

        # verify signature
        sig = msg[-256:]
        h = SHA256.new(msg[:-256])
        try:
            pkcs1_15.new(self.server_pubkey).verify(h, sig)
            print("(OK): signature valid")
        except:
            print("Error: signature invalid")
            return None

        if type_msg == self.protocols.sep_type2num('RES_INIT'):
            #client_private_key = RSA.import_key(open("private_keys/"+self.OWN_ADDR+".pem").read())
            #server_public_key = RSA.import_key(open("public_keys/"+self.DST_ADDR+".pem").read())
            encrypted_nonce = msg[16:-256]

            # decrypt nonce
            cipher_rsa = PKCS1_OAEP.new(self.client_prikey)
            decrypted_nonce = cipher_rsa.decrypt(encrypted_nonce)
            return decrypted_nonce
        elif type_msg == self.protocols.sep_type2num('RES_LOGIN'):
            encrypted_msg_content = msg[16:-256]

            # decrypt
            cipher_rsa = PKCS1_OAEP.new(self.client_prikey)
            decrypted_msg_content = cipher_rsa.decrypt(encrypted_msg_content)

            login_status = decrypted_msg_content[0]
            if login_status == 0:
                print("Error: login failed due to incorrect password")
                return None
            elif login_status == 1:  # login sucess
                session_key = decrypted_msg_content[1:]
                print("(OK): password correct")
                return session_key
            else:
                print("Error: uknown response type")
        else:
            print("Error: unknown message type")
        return None

    def connect(self):
        self.DST_ADDR = input('Type a server address: ')
        self.load_client_prikey()
        self.load_client_pubkey()
        self.load_server_pubkey()
        # send SEP REQ_INIT
        nonce = None
        while True:
            nonce = self.SEP_REQ_INIT()
            if nonce is not None:
                print("SEP REQ INIT sent")
                break
        # wait for SEP RES_INIT
        self.expected_incoming_msg_type = "RES_INIT"
        stime = time.time()
        while True:
            status, msg = self.netif.receive_msg(blocking=False)
            if status:
                print("SEP RES_INIT received")
                nonce_returned = self.read_SEP(msg)
                if nonce == nonce_returned:
                    print("(OK): server verified")
                    print("SEP RES_INIT accpeted")
                    break
                else:
                    print("Error: server verification failed")
                    return None
            #check request timeout
            etime = time.time()
            if (etime - stime > self.timeout):  # 20 sec timeout
                print("Connection request timed out:", self.timeout, "sec")
                return None
            time.sleep(0.5)
        # send SEP REQ_LOGIN
        while True:
            res = self.SEP_REQ_LOGIN()
            if res:
                break
        # wait for SEP RES_LOGIN
        self.expected_incoming_msg_type = "RES_LOGIN"
        stime = time.time()
        while True:
            status, msg = self.netif.receive_msg(blocking=False)
            if status:
                print("SEP RES_LOGIN received")
                session_key = self.read_SEP(msg)
                if session_key is not None:
                    return session_key
                else:
                    continue
            etime = time.time()
            if (etime - stime > self.timeout):
                print("Connection request timed out:", self.timeout, "sec")
                return None
            time.sleep(0.5)

    def start(self):
        session_key = None
        while True:
            session_key = self.connect()
            if session_key is not None:
                break
        print("Connection established with", self.DST_ADDR)
        print(session_key)
Ejemplo n.º 2
0
class ScoreCardView:
    def __init__(self):

        # Initialize Data Objects
        self.dvh = None
        self.dvh_counts = []
        self.dvh_counts_for_plot = None
        self.bin_count = 0
        self.bin_counts = None
        self.roi_keys = None
        self.roi_names = None
        self.roi_key_map = None
        self.plans = None
        self.structures = None
        self.protocol_data = None
        self.roi_override = {}
        self.aliases = StructureAliases()
        self.protocols = Protocols()
        self.source_data = ColumnDataSource(data=dict(roi_name=[], roi_template=[], roi_key=[], volume=[], min_dose=[],
                                                      mean_dose=[], max_dose=[], constraint=[], constraint_calc=[],
                                                      pass_fail=[], calc_type=[]))
        self.source_plot = ColumnDataSource(data=dict(x=[], y=[], color=[], roi=[], roi_key=[]))
        self.colors = itertools.cycle(palette)

        self.__define_layout_objects()
        self.__do_bind()
        self.__do_layout()

        self.update_protocol_data()
        self.initialize_source_data()

    def __define_layout_objects(self):
        # Report heading data
        self.select_plan = Select(title='Plan:', width=400)
        self.button_refresh_plans = Button(label='Scan DICOM Inbox', button_type='primary')
        self.select_protocol = Select(title='Protocol:', options=self.protocols.protocol_names, value='TG101', width=150)
        self.select_fx = Select(title='Fractions:', value='3', options=self.fractionation_options, width=60)
        self.button_calculate = Button(label='Calculate Scorecard', button_type='primary')
        self.button_delete_roi = Button(label='Delete Constraint', button_type='warning')
        self.button_calculate_dvhs = Button(label='Calculate DVHs', button_type='primary', width=200)
        self.select_roi_template = Select(title='Template ROI:')
        self.select_roi = Select(title='Plan ROI:')
        self.max_dose_volume = Div(text="<b>Point defined as %scc" % MAX_DOSE_VOLUME)

        self.columns = [TableColumn(field="roi_template", title="Template ROI"),
                        TableColumn(field="roi_name", title="ROI"),
                        TableColumn(field='volume', title='Volume (cc)', formatter=NumberFormatter(format="0.00")),
                        TableColumn(field='min_dose', title='Min Dose (Gy)', formatter=NumberFormatter(format="0.00")),
                        TableColumn(field='mean_dose', title='Mean Dose (Gy)', formatter=NumberFormatter(format="0.00")),
                        TableColumn(field='max_dose', title='Max Dose (Gy)', formatter=NumberFormatter(format="0.00")),
                        TableColumn(field='constraint', title='Constraint'),
                        TableColumn(field='constraint_calc', title='Value', formatter=NumberFormatter(format="0.00")),
                        TableColumn(field='pass_fail', title='Pass/Fail', formatter=self.__pass_fail_formatter)]
        self.data_table = DataTable(source=self.source_data, columns=self.columns, index_position=None,
                                    width=1000, height=300)

        tools = "pan,wheel_zoom,box_zoom,reset,crosshair,save"
        self.plot = figure(plot_width=800, plot_height=475, tools=tools, active_drag="box_zoom")
        # Set x and y axis labels
        self.plot.xaxis.axis_label = "Dose (Gy)"
        self.plot.yaxis.axis_label = "Normalized Volume"
        self.plot.min_border_left = 60
        self.plot.min_border_bottom = 60
        self.plot.add_tools(HoverTool(show_arrow=False, line_policy='next',
                                      tooltips=[('Plan ROI', '@roi'),
                                                ('Dose', '$x'),
                                                ('Volume', '$y')]))
        self.plot.xaxis.axis_label_text_font_size = "12pt"
        self.plot.yaxis.axis_label_text_font_size = "12pt"
        self.plot.xaxis.major_label_text_font_size = "10pt"
        self.plot.yaxis.major_label_text_font_size = "10pt"
        self.plot.yaxis.axis_label_text_baseline = "bottom"
        self.plot.lod_factor = 100  # level of detail during interactive plot events

        self.plot.multi_line('x', 'y', source=self.source_plot,
                             selection_color='color', line_width=3, alpha=0,
                             line_dash='solid', nonselection_alpha=0, selection_alpha=1)

        columns = [TableColumn(field="roi", title="Select Structures to Plot")]
        self.plot_rois = DataTable(source=self.source_plot, columns=columns, index_position=None,
                                   width=200, height=350)

    def __do_bind(self):
        self.button_calculate.on_click(self.initialize_source_data)
        self.button_delete_roi.on_click(self.delete_selected_rows)
        self.select_protocol.on_change('value', self.protocol_listener)
        self.select_fx.on_change('value', self.fx_listener)
        self.button_refresh_plans.on_click(self.update_plan_options)
        self.select_plan.on_change('value', self.plan_listener)
        self.select_roi_template.on_change('value', self.template_roi_listener)
        self.select_roi.on_change('value', self.roi_listener)
        self.source_data.selected.on_change('indices', self.source_select)
        self.button_calculate_dvhs.on_click(self.update_dvh_plot)

    def __do_layout(self):

        self.layout = column(self.button_refresh_plans,
                             row(self.select_plan, self.select_protocol, self.select_fx),
                             row(self.button_calculate, self.button_delete_roi),
                             row(self.select_roi_template, self.select_roi),
                             self.max_dose_volume,
                             self.data_table,
                             row(self.plot, Spacer(width=10), column(self.button_calculate_dvhs,
                                                                     self.plot_rois)))

    @property
    def __pass_fail_formatter(self):
        # Data tables
        # custom js to highlight mismatches in red with white text
        template = """
                           <div style="background:<%= 
                               (function colorfrommismatch(){
                                   if(pass_fail != "" ){
                                       if(pass_fail == "Fail"){
                                          return('HIGHLIGHT_COLOR_FAIL')
                                       }
                                       if(pass_fail == "Pass"){
                                          return('HIGHLIGHT_COLOR_PASS')
                                       }
                                   }
                                   }()) %>; 
                               color: <%= 
                                    (function colorfrommismatch(){
                                        if(pass_fail == "Fail"){return('TEXT_COLOR_FAIL')}
                                        }()) %>;"> 
                           <%= value %>
                           </div>
                           """
        template = template.replace('HIGHLIGHT_COLOR_FAIL', 'red')
        template = template.replace('TEXT_COLOR_FAIL', 'white')
        template = template.replace('HIGHLIGHT_COLOR_PASS', 'lightgreen')
        template = template.replace('TEXT_COLOR_PASS', 'black')
        return HTMLTemplateFormatter(template=template)

    # Properties -------------------------------------------------------------------
    @property
    def fractionation(self):
        return "%sFx" % self.select_fx.value

    @property
    def fractionation_options(self):
        return self.protocols.get_fractionations(self.protocol)

    @property
    def protocol(self):
        return self.select_protocol.value

    @property
    def current_struct_file(self):
        return self.plans[self.select_plan.value]['rtstruct']

    # Listeners -------------------------------------------------------------------
    def protocol_listener(self, attr, old, new):
        self.select_fx.options = self.fractionation_options
        if self.select_fx.value not in self.select_fx.options:
            self.select_fx.value = self.select_fx.options[0]
        else:  # Changing select_fx.value will prompt the following two lines
            self.update_protocol_data()
            self.initialize_source_data()

    def fx_listener(self, attr, old, new):
        self.update_protocol_data()
        self.initialize_source_data()

    def plan_listener(self, attr, old, new):
        self.roi_override = {}
        if new in list(self.plans):
            self.initialize_source_data()

    def template_roi_listener(self, attr, old, new):
        self.update_roi_select()

    def roi_listener(self, attr, old, new):
        template_rois = self.source_data.data['roi_template']
        indices = [i for i, roi in enumerate(template_rois) if roi == self.select_roi_template.value]
        if indices:
            patches = {'roi_name': [(i, new) for i in indices]}
            self.source_data.patch(patches)
        if new:
            self.roi_override[self.select_roi_template.value] = new
            self.button_calculate.button_type = 'success'
            total = len(indices)
            for i in indices:
                self.button_calculate.label = 'Calculating ScoreCard %s of %s' % (i+1, total)
                key = self.roi_key_map[new]
                self.calculate_dvh(key)
                self.update_table_row(i, key)
                self.source_data.patch({'roi_key': [(i, key)]})
                self.update_constraint(i)
            self.button_calculate.label = 'Calculate Scorecard'
            self.button_calculate.button_type = 'primary'
        else:
            if self.button_calculate.button_type == 'primary' and self.select_roi_template.value in self.roi_override:
                self.roi_override.pop(self.select_roi_template.value)
            patches = {'volume': [(i, 0.) for i in indices],
                       'min_dose': [(i, 0.) for i in indices],
                       'mean_dose': [(i, 0.) for i in indices],
                       'max_dose': [(i, 0.) for i in indices],
                       'constraint_calc': [(i, 0.) for i in indices],
                       'pass_fail': [(i, '') for i in indices],
                       'roi_key': [(i, '') for i in indices]}
            self.source_data.patch(patches)

    def source_select(self, attr, old, new):
        if new:
            self.select_roi_template.value = self.source_data.data['roi_template'][new[0]]

    # Methods -------------------------------------------------------------------
    def update_protocol_data(self):
        self.protocol_data = self.protocols.get_column_data(self.protocol, self.fractionation)

    def initialize_source_data(self):
        data = self.protocol_data
        self.bin_counts = None
        self.bin_count = None
        self.dvh = {}
        self.dvh_counts_for_plot = []
        self.dvh_counts = []
        row_count = len(data['roi_template'])
        new_data = {'roi_template': data['roi_template'],
                    'roi_key': [''] * row_count,
                    'roi_name': [''] * row_count,
                    'volume': [''] * row_count,
                    'min_dose': [''] * row_count,
                    'mean_dose': [''] * row_count,
                    'max_dose': [''] * row_count,
                    'constraint': data['string_rep'],
                    'constraint_calc': [''] * row_count,
                    'pass_fail': [''] * row_count,
                    'calc_type': data['calc_type']}

        self.source_plot.data = {'x': [], 'y': [], 'color': [], 'roi': [], 'roi_key': []}

        self.source_data.data = new_data
        self.update_roi_template_select()
        if self.select_plan.value:
            self.button_calculate.label = 'Calculating Scorecard...'
            self.button_calculate.button_type = 'success'
            self.update_plan_structures()

    def delete_selected_rows(self):
        selected_indices = self.source_data.selected.indices
        selected_indices.sort(reverse=True)
        if not selected_indices:
            selected_indices = [0]
        data = self.source_data.data
        for index in selected_indices:
            for key in list(data):
                data[key].pop(index)

        self.source_data.data = data
        self.source_data.selected.indices = []

    def update_roi_template_select(self):
        options = list(set(self.source_data.data['roi_template']))
        options.sort()
        self.select_roi_template.options = options
        if self.select_roi_template.value not in options:
            self.select_roi_template.value = options[0]

    def update_plan_options(self):
        self.button_refresh_plans.button_type = 'success'
        self.button_refresh_plans.label = 'Updating...'
        self.plans = get_plans(INBOX_DIR)
        self.select_plan.options = list(self.plans)
        self.button_refresh_plans.button_type = 'primary'
        self.button_refresh_plans.label = 'Scan DICOM Inbox'
        if self.select_plan.value not in list(self.plans):
            self.select_plan.value = list(self.plans)[0]

    def update_plan_structures(self):
        self.structures = dicomparser.DicomParser(self.current_struct_file).GetStructures()
        self.roi_keys = [key for key in self.structures if self.structures[key]['type'].upper() != 'MARKER']
        self.roi_names = [str(self.structures[key]['name']) for key in self.roi_keys]
        self.roi_key_map = {name: self.roi_keys[i] for i, name in enumerate(self.roi_names)}
        self.select_roi.options = [''] + self.roi_names
        self.update_roi_select()
        self.match_rois()

    def update_roi_select(self):
        index = self.source_data.data['roi_template'].index(self.select_roi_template.value)
        self.select_roi.value = self.source_data.data['roi_name'][index]

    def match_rois(self):
        matches = self.aliases.match_protocol_rois(self.source_data.data['roi_template'], self.roi_names)
        patches = {'roi_name': [], 'roi_key': []}
        for i, protocol_roi in enumerate(self.source_data.data['roi_template']):
            if protocol_roi in list(matches):
                match = matches[protocol_roi]
            else:
                match = ''
            if protocol_roi in list(self.roi_override):
                match = self.roi_override[protocol_roi]
            if match:
                key = self.roi_key_map[match]
            else:
                key = ''
            patches['roi_name'].append((i, match))
            patches['roi_key'].append((i, key))
        self.source_data.patch(patches)
        self.update_roi_select()
        self.calculate_protocol_dvhs()

    def update_table_row(self, index, key):
        patch = {'volume': [(index, self.dvh[key].volume)],
                 'min_dose': [(index, self.dvh[key].min)],
                 'mean_dose': [(index, self.dvh[key].mean)],
                 'max_dose': [(index, self.dvh[key].max)]}
        self.source_data.patch(patch)

    def calculate_dvh(self, key):
        if key not in list(self.dvh):
            files = self.plans[self.select_plan.value]
            self.dvh[key] = dvhcalc.get_dvh(files['rtstruct'], files['rtdose'], key)

    def calculate_dvhs(self):
        current_button_type = self.button_calculate.button_type
        current_button_label = self.button_calculate.label
        self.button_calculate_dvhs.button_type = 'success'

        files = self.plans[self.select_plan.value]
        total = len(self.roi_keys)
        for i, key in enumerate(list(self.roi_keys)):
            self.button_calculate_dvhs.label = 'Calculating DVH: %s of %s' % (i+1, total)
            if key not in list(self.dvh):
                self.dvh[key] = dvhcalc.get_dvh(files['rtstruct'], files['rtdose'], key)
        self.dvh_counts = [self.dvh[key].counts for key in self.roi_keys]
        self.button_calculate.button_type = current_button_type
        self.button_calculate.label = current_button_label

    def calculate_protocol_dvhs(self):
        self.dvh = {}
        total = len([key for key in self.source_data.data['roi_key'] if key != ''])
        counter = 1
        for i, key in enumerate(self.source_data.data['roi_key']):
            if key:
                self.button_calculate.label = 'Calculating Scorecard %s of %s' % (counter, total)
                self.calculate_dvh(key)
                self.update_table_row(i, key)
                self.update_constraint(i)
                counter += 1

        self.button_calculate.label = 'Calculate Scorecard'
        self.button_calculate.button_type = 'primary'

    def update_constraint(self, index):

        if self.source_data.data['roi_name'][index]:
            constraint = self.calculate_constraint(index)

            operator = self.protocol_data['operator'][index]
            threshold = self.protocol_data['threshold_value'][index]

            if constraint is not None:
                if operator == '<':
                    status = constraint < threshold
                else:
                    status = constraint > threshold
                status = ['Fail', 'Pass'][status]
            else:
                status = ''

            self.source_data.patch({'constraint_calc': [(index, constraint)],
                                    'pass_fail': [(index, status)]})

    def calculate_constraint(self, index):
        dvh = self.dvh[self.source_data.data['roi_key'][index]]
        calc_type = self.source_data.data['calc_type'][index]
        input_value = self.protocol_data['input_value'][index]
        if calc_type == 'Volume':
            ans = dvh.dose_constraint(input_value, volume_units='cm3')
            return float(str(ans).split(' ')[0])
        if calc_type == 'Dose':
            ans = dvh.volume_constraint(input_value, dose_units='Gy')
            return float(str(ans).split(' ')[0])
        if calc_type == 'Mean':
            return self.source_data.data['mean_dose'][index]
        if calc_type == 'MVS':
            ans = dvh.volume_constraint(input_value, dose_units='Gy')
            ans = float(str(ans).split(' ')[0])
            return self.source_data.data['volume'][index] - ans

        return None

    @property
    def volumes(self):
        return np.array([self.dvh[key].volume for key in self.roi_keys])

    def pad_dvh_counts(self):
        self.bin_count = max([len(dvh) for dvh in self.dvh_counts])
        self.dvh_counts_for_plot = []
        self.bin_counts = []
        for dvh in self.dvh_counts:
            if dvh[0]:
                self.dvh_counts_for_plot.append(np.divide(dvh, dvh[0]))
            else:
                self.dvh_counts_for_plot.append(np.array([0] * self.bin_count))
            np.append(self.dvh_counts_for_plot[-1], [0])  # ensure last value is 0
            x_axis = np.divide(np.array(list(range(len(self.dvh_counts_for_plot[-1])))), 100.)
            self.bin_counts.append(x_axis)

    def update_dvh_plot(self):
        self.calculate_dvhs()
        self.pad_dvh_counts()
        colors = [color for j, color in zip(range(len(self.dvh_counts_for_plot)), self.colors)]
        self.source_plot.data = {'x': self.bin_counts,
                                 'y': self.dvh_counts_for_plot,
                                 'color': colors,
                                 'roi': self.roi_names,
                                 'roi_key': self.roi_keys}
        self.button_calculate_dvhs.label = 'Calculate DVHs'
        self.button_calculate_dvhs.button_type = 'primary'
 def __init__(self):
     logging.debug(
         f"{self.__class__.__name__ } - Initialising master server.")
     self.transport = None
     self.storage = Storage()
     self.protocols = Protocols()
class MasterServer:
    def __init__(self):
        logging.debug(
            f"{self.__class__.__name__ } - Initialising master server.")
        self.transport = None
        self.storage = Storage()
        self.protocols = Protocols()

    def connection_made(self, transport) -> NoReturn:
        self.transport = transport

    def datagram_received(self, data: bytes, address: Tuple[str,
                                                            int]) -> NoReturn:
        response = None
        logging.debug(
            f"{self.__class__.__name__ } - Recieved {data} from {address}")
        result: Dict = self.protocols.parse_data(data)

        if result.get("class", None) == "B2M":
            response = self.handle_client(result)
        elif result.get("class", None) == "S2M":
            response = self.handle_server(result, address)
        else:
            pass

        if response:
            self.send_response(response, address)

    def send_response(self, response: bytes, address: Tuple[str,
                                                            int]) -> NoReturn:
        logging.debug(
            f"{self.__class__.__name__ } - Sending {response} to {address}")
        self.transport.sendto(response, address)

    def handle_client(self, result: Dict) -> bytes:
        logging.debug(f"{self.__class__.__name__ } - Header belongs to client")
        response_header = result.get("resp", None)
        server_list = self.storage.list_server_addresses(result.get("game"))
        processed_server_list = [self.pack_address(_) for _ in server_list]
        return self.create_response(response_header, processed_server_list)

    def handle_server(self, result, address: Tuple[str, int]) -> bytes:
        logging.debug(f"{self.__class__.__name__ } - Header belongs to server")
        server = GameServer(address, result)
        if self.storage.get_server(server):
            self.storage.update_server(server)
        else:
            self.storage.create_server(server)

        if not server.active:
            self.storage.server_shutdown(server)

        return result.get("resp", None)

    @staticmethod
    def create_response(header: bytes, response: List[bytes]) -> bytes:
        seperator = b""
        if header:
            response.insert(0, header)

        return seperator.join(response)

    @staticmethod
    def pack_address(address: str) -> bytes:
        """
        Takes string formatted address;
        eg, '192.168.0.1:27910'
        Converts to 6 byte binary string.
        H = unsigned short
        """
        port_format = ">H"
        ip, port = address.split(":")
        ip = ip_address(ip).packed
        port = struct.pack(port_format, int(port))
        return ip + port
Ejemplo n.º 5
0
class Server():
    def __init__(self):
        self.ver = 1
        self.current_protocol = 'SEP'  # changed to FMP later
        self.expected_incoming_msg_type = "REQ_INIT"
        self.OWN_ADDR = "Z"  #input("Type your own address: ")
        print("Starting server as Z...")
        self.DST_ADDR = None
        self.NET_PATH = "./network"
        self.FOLDER_PATH = "./server"
        if (self.NET_PATH[-1] != '/') and (self.NET_PATH[-1] != '\\'):
            self.NET_PATH += '/'
        if (self.FOLDER_PATH[-1] != '/') and (self.FOLDER_PATH[-1] != '\\'):
            self.FOLDER_PATH += '/'
        self.netif = network_interface(self.NET_PATH, self.OWN_ADDR)
        self.protocols = Protocols()
        self.session_key = None
        self.client_pubkey = None
        self.client_pubkey_len = None
        self.server_prikey = None

        #to decrypted saved password and prikey
        self.passphrase = "sepfmp"
        self.iv = b'\x8c\xbcW\xcf\x0b\xa6\x00\xec\xa7\x94\xd2\x9a\x01Z\xd7\xfc'

        self.timeout = 30

    def load_server_prikey(self):
        try:
            key = open(
                self.FOLDER_PATH + "private_keys/" + self.OWN_ADDR + ".pem",
                "rb").read()
            self.server_prikey = RSA.import_key(key,
                                                passphrase=self.passphrase)
        except:
            print("Error: could not load server private key")

    def fix_length(self, input_byte, desired_length):
        if (len(input_byte) > desired_length):
            print("Error fixing the length of byte")
            return
        res = b""
        for i in range(desired_length - len(input_byte)):
            res += b"\x00"
        res += input_byte
        return res

    def generate_session_key(self):
        return secrets.token_bytes(32)

    def mkpad(self, s, size):
        s = s.encode("utf-8")  # UTF-8文字列をバイト列に変換する
        pad = b' ' * (size - len(s) % size)  # 特定の長さの倍数にするための空白を生成
        return s + pad

    def verify_password(self, password):
        key = self.mkpad(self.passphrase, 16)
        cipher = AES.new(key, AES.MODE_CBC, self.iv)
        ctext = open(self.FOLDER_PATH + "password/" + self.DST_ADDR,
                     "rb").read()
        correct_pwd = cipher.decrypt(ctext)
        correct_pwd = correct_pwd.strip(b" ")
        if correct_pwd == password:
            return True
        return False

    def SEP_ENC(self, ptext, key):
        cipher_rsa = PKCS1_OAEP.new(key)
        ctext = cipher_rsa.encrypt(ptext)
        return ctext

    def SEP_DEC(self, ctext, key):
        cipher_rsa = PKCS1_OAEP.new(key)
        ptext = cipher_rsa.decrypt(ctext)
        return ptext

    def SEP_SIGN(self, message, key):
        h = SHA256.new(message)
        signature = pkcs1_15.new(key).sign(h)
        return signature

    def SEP_GEN(self, ver, type_sep, type_msg, sender_id, recipient_id,
                encrypted_content, sig_key):
        msg = b""
        msg += ver
        msg += type_sep
        msg += type_msg
        msg += len(encrypted_content).to_bytes(length=5,
                                               byteorder='big')  #msg_len
        msg += sender_id
        msg += recipient_id
        msg += encrypted_content
        sig = self.SEP_SIGN(msg, sig_key)
        #print("Generated sig: ")
        #print(sig)
        msg += sig
        return msg

    def SEP_RES_INIT(self, nonce):
        #header
        ver = self.ver.to_bytes(length=1, byteorder='big')
        type_sep = self.protocols.protocol2num("SEP").to_bytes(length=1,
                                                               byteorder='big')
        type_msg = self.protocols.sep_type2num("RES_INIT").to_bytes(
            length=1, byteorder='big')
        sender_id = self.fix_length(self.OWN_ADDR.encode('utf-8'), 4)
        recipient_id = self.fix_length(self.DST_ADDR.encode('utf-8'), 4)

        #content - encrypted nonce
        ctext = self.SEP_ENC(nonce, self.client_pubkey)

        #send msg
        msg = self.SEP_GEN(ver, type_sep, type_msg, sender_id, recipient_id,
                           ctext, self.server_prikey)
        self.netif.send_msg(self.DST_ADDR, msg)

    def SEP_RES_LOGIN(self, status, session_key=None):
        #header
        ver = self.ver.to_bytes(length=1, byteorder='big')
        type_sep = self.protocols.protocol2num("SEP").to_bytes(length=1,
                                                               byteorder='big')
        type_msg = self.protocols.sep_type2num("RES_LOGIN").to_bytes(
            length=1, byteorder='big')
        sender_id = self.fix_length(self.OWN_ADDR.encode('utf-8'), 4)
        recipient_id = self.fix_length(self.DST_ADDR.encode('utf-8'), 4)
        login_status = None
        ctext = None
        if status:
            s = 1
            login_status = s.to_bytes(length=1, byteorder='big')
            ptext = login_status + session_key
            ctext = self.SEP_ENC(ptext, self.client_pubkey)
        else:
            s = 0
            login_status = s.to_bytes(length=1, byteorder='big')
            ctext = self.SEP_ENC(login_status, self.client_pubkey)

        msg = self.SEP_GEN(ver, type_sep, type_msg, sender_id, recipient_id,
                           ctext, self.server_prikey)
        self.netif.send_msg(self.DST_ADDR, msg)

    def read_SEP(self, msg):
        type_msg = msg[2]
        if type_msg == self.protocols.sep_type2num(
                self.expected_incoming_msg_type):
            print("(OK): protocol type matches")
        else:
            print("Error: unexpected message type")
            return None
        msg_len = int.from_bytes(msg[3:8], 'big')  #int
        if len(msg) == 16 + msg_len + 256:
            print("(OK): message length matches")
        else:
            print("Error: message length does not match")
            return None
        sender_id = msg[8:12].decode('utf-8').strip(chr(0))  #string
        recipient_id = msg[12:16].decode('utf-8').strip(chr(0))  #string
        if recipient_id == self.OWN_ADDR:
            print("(OK): correct recipient address")
        else:
            print("Error: wrong recipient address")
            return None

        if self.client_pubkey is None and type_msg == self.protocols.sep_type2num(
                'REQ_INIT'):
            self.client_pubkey_len = int.from_bytes(msg[16:20], 'big')
            self.client_pubkey = RSA.import_key(msg[20:20 +
                                                    self.client_pubkey_len])

        #verify signature
        sig = msg[-256:]
        h = SHA256.new(msg[:-256])
        try:
            pkcs1_15.new(self.client_pubkey).verify(h, sig)
            print("(OK): sginature valid")
        except:
            print("Error: signature invalid")
            return

        if type_msg == self.protocols.sep_type2num('REQ_INIT'):
            encrypted_nonce = msg[20 + self.client_pubkey_len:-256]

            #decrypt nonce
            #self.server_prikey = RSA.import_key(open("private_keys/"+self.OWN_ADDR+".pem").read())
            cipher_rsa = PKCS1_OAEP.new(self.server_prikey)
            decrypted_nonce = cipher_rsa.decrypt(encrypted_nonce)

            #print("type_msg", type_msg)
            #print("msg_len",msg_len)
            #print("sender",sender_id)
            #print("recipient", recipient_id)
            #print("pubkey_a")
            #print(pubkey_a)
            #print("encrypted nonce")
            #print(encrypted_nonce)
            #print("sig")
            #print(sig)

            #update internal parameters
            self.DST_ADDR = sender_id
            return decrypted_nonce
        elif type_msg == self.protocols.sep_type2num('REQ_LOGIN'):
            encrypted_password = msg[16:-256]

            #decrypt password
            #self.server_prikey = RSA.import_key(open("private_keys/"+self.OWN_ADDR+".pem").read())
            cipher_rsa = PKCS1_OAEP.new(self.server_prikey)
            decrypted_password = cipher_rsa.decrypt(encrypted_password)
            return decrypted_password
        else:
            print("Error: unknown message type")
            return None

    def read_msg(self, msg):
        ver = msg[0]
        type_protocol = msg[1]
        print(ver)
        print(type_protocol)
        if type_protocol != self.protocols.protocol2num(self.current_protocol):
            print(
                "Error: unexpected protocol type - received message protocol="
                + type_protocol)
            return None, None, None

        if type_protocol == self.protocols.protocol2num("SEP"):
            return ver, type_protocol, self.read_SEP(msg)
        elif type_protocol == self.protocols.protocol2num("FMP"):
            pass
            #Todo: create self.read_FMP

    def connect(self):
        self.current_protocol = "SEP"
        self.expected_incoming_msg_type = "REQ_INIT"
        status = None
        msg = None
        # wait for SEP REQ_INIT
        self.expected_incoming_msg_type = "REQ_INIT"
        while True:
            status, msg = self.netif.receive_msg(blocking=True)
            print("SEP REQ_INIT received")
            ver, type_protocol, decrypted_nonce = self.read_msg(msg)
            if decrypted_nonce is not None:
                print("SEP REQ_INIT accepted")
                self.SEP_RES_INIT(decrypted_nonce)
                print("SEP RES_INIT sent")
                break
            else:
                print("Error: SEP REQ_INIT rejected")
                return None
        # wait for SEP REQ_LOGIN
        self.expected_incoming_msg_type = "REQ_LOGIN"
        stime = time.time()
        while True:
            status, msg = self.netif.receive_msg(blocking=False)
            if status:
                print("SEP REQ_LOGIN received")
                ver, type_protocol, password = self.read_msg(msg)
                if password is not None:
                    print("SEP REQ_LOGIN accepted")
                    # TODO SEP RES LOGIN
                    if self.verify_password(password):
                        print("(OK): password ok")
                        session_key = self.generate_session_key()
                        self.SEP_RES_LOGIN(True, session_key=session_key)
                        print("SEP RES_LOGIN sent")
                        return session_key
                    else:
                        print("(!!): password incorrect")
                        self.SEP_RES_LOGIN(False)
                        print("SEP RES_LOGIN sent")
                        continue  # keep waiting for correct password
            #cehck request timeout
            etime = time.time()
            if (etime - stime > self.timeout):
                print("Connection request timed out:", self.timeout, "sec")
                return None
            time.sleep(0.5)

    def start(self):
        self.load_server_prikey()
        session_key = None
        while True:
            session_key = self.connect()
            if session_key is not None:
                break
        print("Connection established with", self.DST_ADDR)
Ejemplo n.º 6
0
class Client():
    def __init__(self):
        self.current_protocol = "SEP"  # changed to FMP later
        self.expected_incoming_msg_type = "RES_INIT"
        self.ver = 1
        self.NET_PATH = "./network"
        self.FOLDER_PATH = "./client"
        if (self.NET_PATH[-1] != '/') and (self.NET_PATH[-1] != '\\'):
            self.NET_PATH += '/'
        if (self.FOLDER_PATH[-1] != '/') and (self.FOLDER_PATH[-1] != '\\'):
            self.FOLDER_PATH += '/'
        self.DST_ADDR = None
        self.OWN_ADDR = input('Type your own address: ')  # Ex. A, B, or C
        self.netif = network_interface(self.NET_PATH, self.OWN_ADDR)
        self.protocols = Protocols()
        self.timeout = 30
        self.sleep_time = 0.1

        self.session_key = None

        self.client_prikey = None
        self.client_pubkey = None
        self.server_pubkey = None

        #fmp seq num
        self.seq_last_received = 0
        self.seq_last_sent = 0

        self.DATA_LOC = "my_data"

    def clean_keys(self):
        self.client_prikey = None
        self.client_pubkey = None
        self.server_pubkey = None

    def load_client_prikey(self):
        try:
            key = open(
                self.FOLDER_PATH + "private_keys/" + self.OWN_ADDR + ".pem",
                "rb").read()
            self.client_prikey = RSA.import_key(key)
        except:
            print("Error: could not load client private key")

    def load_client_pubkey(self):
        try:
            key = open(
                self.FOLDER_PATH + "public_keys/" + self.OWN_ADDR + ".pem",
                "rb").read()
            self.client_pubkey = RSA.import_key(key)
        except:
            print("Error: could not load client public key")

    def load_server_pubkey(self):
        try:
            key = open(
                self.FOLDER_PATH + "public_keys/" + self.DST_ADDR + ".pem",
                "rb").read()
            self.server_pubkey = RSA.import_key(key)
        except:
            print("Error: could not load server public key")

    def fix_length(self, input_byte, desired_length):
        if (len(input_byte) > desired_length):
            print("Error fixing the length of byte")
            return None
        res = b""
        for i in range(desired_length - len(input_byte)):
            res += b"\x00"
        res += input_byte
        return res

    def handle_options(self):
        try:
            opts, args = getopt.getopt(sys.argv[1:],
                                       shortopts='hp:a',
                                       longopts=['help', 'path=', 'addr='])
        except getopt.GetoptError:
            print('wrong options')
            sys.exit(1)

    def generate_nonce(self):
        return secrets.token_bytes(32)

    def SEP_ENC(self, ptext, key):
        cipher_rsa = PKCS1_OAEP.new(key)
        ctext = cipher_rsa.encrypt(ptext)
        return ctext

    def SEP_DEC(self, ctext, key):
        cipher_rsa = PKCS1_OAEP.new(key)
        ptext = cipher_rsa.decrypt(ctext)
        return ptext

    def SEP_SIGN(self, message, key):
        h = SHA256.new(message)
        signature = pkcs1_15.new(key).sign(h)
        return signature

    def SEP_GEN(self, ver, type_sep, type_msg, sender_id, recipient_id,
                encrypted_content, sig_key):
        msg = b""
        msg += ver
        msg += type_sep
        msg += type_msg
        msg += len(encrypted_content).to_bytes(length=5,
                                               byteorder='big')  #msg_len
        msg += sender_id
        msg += recipient_id
        msg += encrypted_content
        sig = self.SEP_SIGN(msg, sig_key)
        #print("Generated sig: ")
        #print(sig)
        msg += sig
        return msg

    def SEP_REQ_INIT(self):

        # generate SEP REQ_INIT
        #header
        ver = self.ver.to_bytes(length=1, byteorder='big')
        type_sep = self.protocols.protocol2num("SEP").to_bytes(length=1,
                                                               byteorder='big')
        type_msg = self.protocols.sep_type2num("REQ_INIT").to_bytes(
            length=1, byteorder='big')
        sender_id = self.fix_length(self.OWN_ADDR.encode('utf-8'), 4)
        recipient_id = self.fix_length(self.DST_ADDR.encode('utf-8'), 4)

        #content
        nonce = self.generate_nonce()
        #pubkey_a = self.client_pubkey
        pubkey_a = self.client_pubkey.export_key()
        #with open("public_keys/"+self.OWN_ADDR+".pem", "rb") as f: pubkey_a = f.read()
        ctext = b""
        ctext += len(pubkey_a).to_bytes(length=4, byteorder='big')
        ctext += pubkey_a
        nonce_encrypted = self.SEP_ENC(nonce, self.server_pubkey)
        ctext += nonce_encrypted

        #send msg
        msg = self.SEP_GEN(ver, type_sep, type_msg, sender_id, recipient_id,
                           ctext, self.client_prikey)
        self.netif.send_msg(self.DST_ADDR, msg)
        #print("Generated nonce: ")
        #print(nonce)
        #print("encrypted nonce: ")
        #print(nonce_encrypted)
        return nonce

    def SEP_REQ_LOGIN(self):
        password = getpass('Type your login password: '******'big')
        type_sep = self.protocols.protocol2num("SEP").to_bytes(length=1,
                                                               byteorder='big')
        type_msg = self.protocols.sep_type2num("REQ_LOGIN").to_bytes(
            length=1, byteorder='big')
        sender_id = self.fix_length(self.OWN_ADDR.encode('utf-8'), 4)
        recipient_id = self.fix_length(self.DST_ADDR.encode('utf-8'), 4)

        #keys to be used
        #server_public_key = RSA.import_key(open("public_keys/"+self.DST_ADDR+".pem").read())
        #client_private_key = RSA.import_key(open("private_keys/"+self.OWN_ADDR+".pem").read())

        #content
        ctext = self.SEP_ENC(password.encode('utf-8'), self.server_pubkey)

        #send msg
        msg = self.SEP_GEN(ver, type_sep, type_msg, sender_id, recipient_id,
                           ctext, self.client_prikey)
        self.netif.send_msg(self.DST_ADDR, msg)
        return True

    def read_SEP(self, msg):
        type_msg = msg[2]
        if type_msg == self.protocols.sep_type2num(
                self.expected_incoming_msg_type):
            print("(OK): message type matches")
        else:
            print("Error: unexpected message type")
            return None
        msg_len = int.from_bytes(msg[3:8], 'big')  #int
        if len(msg) == 16 + msg_len + 256:
            print("(OK): message length matches")
        else:
            print("Error: message length does not match")
            return None
        sender_id = msg[8:12].decode('utf-8').strip(chr(0))  #string
        recipient_id = msg[12:16].decode('utf-8').strip(chr(0))  #string
        if recipient_id == self.OWN_ADDR:
            print("(OK): correct recipient address")
        else:
            print("Error: wrong recipient address")
            return None

        # verify signature
        sig = msg[-256:]
        h = SHA256.new(msg[:-256])
        try:
            pkcs1_15.new(self.server_pubkey).verify(h, sig)
            print("(OK): signature valid")
        except:
            print("Error: signature invalid")
            return None

        if type_msg == self.protocols.sep_type2num('RES_INIT'):
            #client_private_key = RSA.import_key(open("private_keys/"+self.OWN_ADDR+".pem").read())
            #server_public_key = RSA.import_key(open("public_keys/"+self.DST_ADDR+".pem").read())
            encrypted_nonce = msg[16:-256]

            # decrypt nonce
            cipher_rsa = PKCS1_OAEP.new(self.client_prikey)
            decrypted_nonce = cipher_rsa.decrypt(encrypted_nonce)
            return decrypted_nonce
        elif type_msg == self.protocols.sep_type2num('RES_LOGIN'):
            encrypted_msg_content = msg[16:-256]

            # decrypt
            cipher_rsa = PKCS1_OAEP.new(self.client_prikey)
            decrypted_msg_content = cipher_rsa.decrypt(encrypted_msg_content)

            login_status = decrypted_msg_content[0]
            if login_status == 0:
                print("Error: login failed due to incorrect password")
                return None
            elif login_status == 1:  # login sucess
                session_key = decrypted_msg_content[1:]
                print("(OK): password correct")
                return session_key
            else:
                print("Error: uknown response type")
        else:
            print("Error: unknown message type")
        return None

    def read_msg(self, msg):
        ver = msg[0]
        type_protocol = msg[1]

        if type_protocol != self.protocols.protocol2num(self.current_protocol):
            print(
                "Error: unexpected protocol type - received message protocol="
                + type_protocol)
            return None, None, None

        if type_protocol == self.protocols.protocol2num("SEP"):
            return ver, type_protocol, self.read_SEP(msg)
        elif type_protocol == self.protocols.protocol2num("FMP"):
            return ver, type_protocol, self.read_FMP(msg)

    def connect(self):
        self.DST_ADDR = input('Type a server address: ')
        self.load_client_prikey()
        self.load_client_pubkey()
        self.load_server_pubkey()
        # send SEP REQ_INIT
        nonce = None
        while True:
            nonce = self.SEP_REQ_INIT()
            if nonce is not None:
                print("SEP REQ INIT sent")
                break
        # wait for SEP RES_INIT
        self.expected_incoming_msg_type = "RES_INIT"
        stime = time.time()
        while True:
            status, msg = self.netif.receive_msg(blocking=False)
            if status:
                print("SEP RES_INIT received")
                ver, type_protocol, nonce_returned = self.read_msg(msg)
                if nonce == nonce_returned:
                    print("(OK): server verified")
                    print("SEP RES_INIT accpeted")
                    break
                else:
                    print("Error: server verification failed")
                    return None
            #check request timeout
            etime = time.time()
            if (etime - stime > self.timeout):  # 20 sec timeout
                print("Connection request timed out:", self.timeout, "sec")
                return None
            time.sleep(self.sleep_time)

        while True:
            # send SEP REQ_LOGIN
            while True:
                res = self.SEP_REQ_LOGIN()
                if res:
                    break
            # wait for SEP RES_LOGIN
            self.expected_incoming_msg_type = "RES_LOGIN"
            stime = time.time()
            while True:
                status, msg = self.netif.receive_msg(blocking=False)
                if status:
                    print("SEP RES_LOGIN received")
                    ver, type_protocol, session_key = self.read_msg(msg)
                    if session_key is not None:
                        return session_key
                    else:
                        break
                etime = time.time()
                if (etime - stime > self.timeout):
                    print("Connection request timed out:", self.timeout, "sec")
                    return None
                time.sleep(self.sleep_time)

    def gen_iv(self):
        return secrets.token_bytes(16)

    def FMP_ENC(self, ptext):
        iv = self.gen_iv()
        len(self.session_key)
        cipher = AES.new(self.session_key, AES.MODE_CBC, iv=iv)
        ctext = cipher.encrypt(pad(ptext, AES.block_size))
        return iv, ctext

    def FMP_MAC(self, msg):
        h = HMAC.new(self.session_key, digestmod=SHA256)
        h.update(msg)
        return h.digest()

    def FMP_DEC(self, iv, ctext):
        cipher = AES.new(self.session_key, AES.MODE_CBC, iv=iv)
        ptext = unpad(cipher.decrypt(ctext), AES.block_size)
        return ptext

    def FMP_GEN(self, type_msg, content):
        ver = self.ver.to_bytes(length=1, byteorder='big')
        type_fmp = self.protocols.protocol2num("FMP").to_bytes(length=1,
                                                               byteorder='big')
        type_msg = self.protocols.fmp_type2num(type_msg).to_bytes(
            length=1, byteorder='big')
        sender_id = self.fix_length(self.OWN_ADDR.encode('utf-8'), 4)
        recipient_id = self.fix_length(self.DST_ADDR.encode('utf-8'), 4)

        iv, encrypted_content = self.FMP_ENC(content)

        msg = b""
        msg += ver
        msg += type_fmp
        msg += type_msg
        msg += (len(iv) + len(encrypted_content)).to_bytes(length=5,
                                                           byteorder=('big'))
        msg += sender_id
        msg += recipient_id
        msg += iv
        msg += encrypted_content
        mac = self.FMP_MAC(msg)
        msg += mac

        return msg

    def FMP_LISTEN(self, command):
        # wait for FMP RES
        self.expected_incoming_msg_type = command

        stime = time.time()
        while True:
            status, msg = self.netif.receive_msg(blocking=False)
            if status:
                print("FMP_" + command, "response received")
                ver, type_protocol, content = self.read_msg(msg)
                if content is not None:
                    return content
                else:
                    print("Error: response message invalid")
                    continue
            etime = time.time()
            if (etime - stime > self.timeout):
                print("Error: no response received")
                return None
            time.sleep(self.sleep_time)
        return None

    def FMP_SEND(self, msg):
        self.netif.send_msg(self.DST_ADDR, msg)
        self.seq_last_sent += 1

    def FMP_MKD(self):
        dir_name = input("Type directory name: ")

        # content
        # 2 bytes for seq num
        content = (self.seq_last_sent + 1).to_bytes(2, 'big')
        content += dir_name.encode('utf-8')
        msg = self.FMP_GEN("MKD", content)
        self.FMP_SEND(msg)
        print("FMP_MKD request sent")

        res = self.FMP_LISTEN("MKD")
        res = int.from_bytes(res, 'big')
        if res == 0:
            print("Error: MKD failed")
        elif res == 1:
            print("MKD: directory created")

    def FMP_RMD(self):
        dir_name = input("Type directory name: ")
        confirm = input("All sub contents will be deleted: (y/n) ")
        if confirm != "y":
            print("RMD cancelled")
            return

        content = (self.seq_last_sent + 1).to_bytes(2, 'big')
        content += dir_name.encode('utf-8')
        msg = self.FMP_GEN("RMD", content)
        self.FMP_SEND(msg)
        print("FMP_RMD request sent")

        res = self.FMP_LISTEN("RMD")
        res = int.from_bytes(res, 'big')
        if res == 0:
            print("Error: RMD failed")
        elif res == 1:
            print("RMD: directory deleted")

    def FMP_GWD(self):
        content = (self.seq_last_sent + 1).to_bytes(2, 'big')
        msg = self.FMP_GEN("GWD", content)
        self.FMP_SEND(msg)
        print("FMP_GWD request sent")

        res = self.FMP_LISTEN("GWD")

        if res is not None:
            res = res.decode('utf-8')
            print(res)

    def FMP_CWD(self):
        dir_name = input("Type destination: ")

        # content
        # 2 bytes for seq num
        content = (self.seq_last_sent + 1).to_bytes(2, 'big')
        content += dir_name.encode('utf-8')
        msg = self.FMP_GEN("CWD", content)
        self.FMP_SEND(msg)
        print("FMP_CWD request sent")

        res = self.FMP_LISTEN("CWD")
        res = int.from_bytes(res, 'big')
        if res == 0:
            print("Error: CWD failed")
        elif res == 1:
            print("CWD: current directory changed")

    def FMP_LST(self):
        content = (self.seq_last_sent + 1).to_bytes(2, 'big')
        msg = self.FMP_GEN("LST", content)
        self.FMP_SEND(msg)
        print("FMP_LST request sent")

        res = self.FMP_LISTEN("LST")

        if res is not None:
            res = res.decode('utf-8')
            res = list(res.split(","))
            for i in res:
                print(i)

    def FMP_UPL(self):
        file_name = input("Type file name:")

        try:
            f_cont = open(
                os.path.join(self.FOLDER_PATH, self.DATA_LOC, file_name),
                "rb").read()
        except:
            print("Error: failed to read the file")
            return

        # content
        # 2 bytes for seq num
        content = (self.seq_last_sent + 1).to_bytes(2, 'big')
        content += len(file_name).to_bytes(2, ('big'))
        content += file_name.encode('utf-8')
        content += f_cont

        msg = self.FMP_GEN("UPL", content)
        self.FMP_SEND(msg)
        print("FMP_UPL request sent")

        res = self.FMP_LISTEN("UPL")
        res = int.from_bytes(res, 'big')
        if res == 0:
            print("Error: UPL failed")
        elif res == 1:
            print("UPL: file uploaded")

    def FMP_DNL(self):
        file_name = input("Type file name: ")

        content = (self.seq_last_sent + 1).to_bytes(2, 'big')
        content += file_name.encode('utf-8')
        msg = self.FMP_GEN("DNL", content)
        self.FMP_SEND(msg)
        print("FMP_DNL request sent")

        res = self.FMP_LISTEN("DNL")

        fcont = res[1:].decode('utf-8')
        res = res[0]
        if res == 0:
            print("Error: DNL failed")
        elif res == 1:
            open(os.path.join(self.FOLDER_PATH, self.DATA_LOC, file_name),
                 "w").write(fcont)
            print("DNL: file downloaded")

    def FMP_RMF(self):
        file_name = input("Type file name: ")
        content = (self.seq_last_sent + 1).to_bytes(2, 'big')
        content += file_name.encode('utf-8')
        msg = self.FMP_GEN("RMF", content)
        self.FMP_SEND(msg)
        print("FMP_RMF request sent")

        res = self.FMP_LISTEN("RMF")

        res = int.from_bytes(res, 'big')
        if res == 0:
            print("Error: RMF failed")
        elif res == 1:
            print("RMF: file removed")

    def FMP_END(self):
        confirm = input("Disconnect and terminate session: (y/n)")
        if confirm != 'y':
            print("END cancelled")
            return
        content = (self.seq_last_sent + 1).to_bytes(2, 'big')
        msg = self.FMP_GEN("END", content)
        self.FMP_SEND(msg)
        print("FMP_END request sent")
        res = self.FMP_LISTEN("END")

        res = int.from_bytes(res, 'big')
        if res == 0:
            print("Error: END failed")
        elif res == 1:
            os.sys.exit(0)
            print("END: session terminated")

    def read_FMP(self, msg):
        type_command = msg[2]
        msg_len = int.from_bytes(msg[3:8], 'big')  #int

        if len(msg) == 16 + msg_len + 32:
            print("(OK): message length matches")
        else:
            print("Error: message length does not match")
            return None
        sender_id = msg[8:12].decode('utf-8').strip(chr(0))  #string
        if sender_id == self.DST_ADDR:
            print("(OK): correct sender address")
        else:
            print("Error: wrong sender address")
        recipient_id = msg[12:16].decode('utf-8').strip(chr(0))  #string
        if recipient_id == self.OWN_ADDR:
            print("(OK): correct recipient address")
        else:
            print("Error: wrong recipient address")
            return None

        iv = msg[16:32]
        ctext = msg[32:-32]
        mac = msg[-32:]

        # verify mac
        h = HMAC.new(self.session_key, digestmod=SHA256)
        h.update(msg[:-32])
        try:
            h.verify(mac)
            print("(OK): MAC ok")
        except ValueError:
            print("Error: MAC verification failed")
            return None

        # decrypt
        ptext = self.FMP_DEC(iv, ctext)
        seq_num = int.from_bytes(ptext[0:2], byteorder='big')
        content = ptext[2:]

        # check seq_num
        if seq_num <= self.seq_last_received:
            print("Error: invalid sequence number")
            return None
        else:
            self.seq_last_received = seq_num
            print("(OK): sequence number ok")

        return content

    def fmp_process(self):
        command = input("Type command: ")
        if command == "MKD":
            self.FMP_MKD()
        elif command == "RMD":
            self.FMP_RMD()
        elif command == "GWD":
            self.FMP_GWD()
        elif command == "CWD":
            self.FMP_CWD()
        elif command == "LST":
            self.FMP_LST()
        elif command == "UPL":
            self.FMP_UPL()
        elif command == "DNL":
            self.FMP_DNL()
        elif command == "RMF":
            self.FMP_RMF()
        elif command == "END":
            self.FMP_END()
        else:
            print("Error: unknown command")

    def start(self):
        session_key = None
        while True:
            session_key = self.connect()
            if session_key is not None:
                break
        print("Connection established with", self.DST_ADDR)
        #print(session_key)

        self.current_protocol = "FMP"
        self.session_key = session_key
        self.seq_last_sent = 0

        while True:
            self.fmp_process()