def basic_check(self, received_testscript_msg_bytes): """ Basic check for all the LoRaWAN test steps. It verifies the MIC of the message and sets a flag if the received message if of a CONFIRMED_UP type (so an ACK could be sent to the DUT). """ super().basic_check( received_testscript_msg_bytes=received_testscript_msg_bytes) self.received_testscript_msg = flora_messages.GatewayMessage( json_ttm_str=received_testscript_msg_bytes.decode()) lorawan_msg = self.received_testscript_msg.parse_lorawan_message() mtype_str = lorawan_msg.mhdr.mtype_str # Register in flag if the received message needs Acknowdlegment knowdlege if mtype_str in ('CONFIRMED_UP', ): self.ctx_test_manager.device_under_test.message_to_ack = True else: self.ctx_test_manager.device_under_test.message_to_ack = False network_key = self.ctx_test_manager.device_under_test.loramac_params.nwkskey if mtype_str in ('JOIN_REQUEST', ): network_key = self.ctx_test_manager.device_under_test.appkey logger.info( f"Checking MIC using key {utils.bytes_to_text(network_key)}.") calculated_mic = lorawan_msg.calculate_mic(key=network_key) if not lorawan_msg.mic_bytes == calculated_mic: description_template = "Wrong MIC.\nKey: {key}\nMIC: {received_mic}\nCalculated: {calc}" raise lorawan_errors.MICError( description=description_template.format( key=utils.bytes_to_text(network_key), received_mic=utils.bytes_to_text(lorawan_msg.mic_bytes), calc=utils.bytes_to_text(calculated_mic)), test_case=self.ctx_test_manager.tc_name, step_name=self.name, last_message=self.received_testscript_msg.get_printable_str())
def __str__(self): """ Human readable string representation of the MAC Command.""" ret_str = "{name} MAC Command.\n".format(name=type(self).__name__.split('.')[-1]) ret_str += "Command ID (CID): 0x{cid}\n".format(cid=utils.bytes_to_text(self.cid)) ret_str += "Size: {size}\n".format(size=self.command_size) ret_str += "Content: 0x{content}\n".format(content=utils.bytes_to_text(self.content)) return ret_str
def to_print_str(self): """ Creates a human readable string with the contained information.""" DevAddr = bytes_to_text(self.devaddr), DevEUI = bytes_to_text(self.deveui), AppKey = bytes_to_text(self.appkey), AppSKey = bytes_to_text(self.appskey), NwkSKey = bytes_to_text(self.nwkskey) return f"DevAddr: {DevAddr}\nDevEUI: {DevEUI}\nAppKey: {AppKey}\nAppSKey: {AppSKey}\nNwkSKey: {NwkSKey}"
def __str__(self): dadd = utils.bytes_to_text(self.loramac_params.devaddr) deui = utils.bytes_to_text(self.deveui) ask = utils.bytes_to_text(self.loramac_params.appskey) nsk = utils.bytes_to_text(self.loramac_params.nwkskey) ak = utils.bytes_to_text(self.appkey) return f"\tDevAddr: {dadd}\n\tDevEUI: {deui}\n\tAppSKey: {ask}\n\tNwkSKey: {nsk}\n\tAppKey: {ak}\n"
def analyze_join_request(self, join_request_phypayload_bytes): appeui_bytes = join_request_phypayload_bytes[8:0:-1] deveui_bytes = join_request_phypayload_bytes[16:8:-1] devnonce = join_request_phypayload_bytes[-5:-7:-1] self._used_otaa_devnonces.append(devnonce) app_hex = utils.bytes_to_text(appeui_bytes) dev_hex = utils.bytes_to_text(deveui_bytes) nonce_hex = utils.bytes_to_text(devnonce[::-1]) logger.info( f"appEUI: {app_hex}\ndevEUI: {dev_hex}\ndevnonce: {nonce_hex}")
def __str__(self): ret_str = super().__str__() ret_str += "ChIndex: 0x{chi_b}\n".format(chi_b=utils.bytes_to_text(self.chindex)) ret_str += "Freq: 0x{freq_b} -> {freq_mhz}\n".format(freq_b=utils.bytes_to_text(self.freq), freq_mhz=lorawan_parameters.freq_24bits_to_mhz( freq_bytes=self.freq[::-1])) ret_str += "DrRange: 0x{dr_b}\n".format(dr_b=utils.bytes_to_text(self.drrange)) ret_str += "Min DR: {min_dr}\n".format(min_dr=self.mindr) ret_str += "Max DR: {max_dr}\n".format(max_dr=self.maxdr) return ret_str
def __str__(self): """ Human readable string representation.""" retstr = '' retstr += "----DevAddr: {0}\n".format( utils.bytes_to_text(self.devaddr_bytes)) retstr += "----FCtrl: {0}\n".format(self.fctrl.fctrl_binary_str()) retstr += str(self.fctrl) retstr += "----FCnt: {0} ({1})\n".format( self.get_fcnt_int(), utils.bytes_to_text(self.fcnt_bytes)) retstr += "----FOpts: {0}\n".format(self.fopts_to_str()) return retstr
def accept_join(self, devnonce, dlsettings, rxdelay, cflist): """ Updates the session information and creates the PHYPayload of a join accept message to be sent to the DUT. :param devnonce: 2 bytes of the device nonce used in the join request message. :param dlsettings: byte of the dlsettings field (join accept) :param rxdelay: byte of the rxdelay field (join accept) :param cflist: 16 bytes with the frequency list. :return: bytes of the lorawan join accept message PHYPayload. """ appnonce = struct.pack("<L", self.create_appnonce())[:3] devaddr_int = random.randint(0, 2**32 - 1) nwkid_int = (devaddr_int & 0xfe000000) // 2**25 netid_int = (random.randint(0, 2**24 - 1) & 0xffff80) | nwkid_int devaddr = struct.pack(">L", devaddr_int) netid = struct.pack(">L", netid_int)[-3:] appnonce_netid_devnonce = appnonce + netid[::-1] + devnonce[::-1] appkey_bytes = bytes.fromhex(self.appkey_hex) nwkskey = utils.aes128_encrypt( appkey_bytes, b'\x01' + appnonce_netid_devnonce + bytes(7)) appskey = utils.aes128_encrypt( appkey_bytes, b'\x02' + appnonce_netid_devnonce + bytes(7)) logger.info(f"AppSKey: {utils.bytes_to_text(appskey)}") logger.info(f"NwkSKey: {utils.bytes_to_text(nwkskey)}") self.store_used_devnonce(devnonce) macpayload = appnonce + netid[:: -1] + devaddr[:: -1] + dlsettings + rxdelay + cflist mhdr_macpayload = lorawan_parameters.MHDR.JOIN_ACCEPT + macpayload mic = utils.aes128_cmac(appkey_bytes, mhdr_macpayload)[:4] join_accept_phypayload = ( lorawan_parameters.MHDR.JOIN_ACCEPT + utils.aes128_decrypt( key=appkey_bytes, cipher_text=macpayload + mic)) devaddr_hex = utils.bytes_to_text(devaddr) appskey_hex = utils.bytes_to_text(appskey) nwkskey_hex = utils.bytes_to_text(nwkskey) self.update_device_session(devaddr_hex=devaddr_hex, appskey_hex=appskey_hex, nwkskey_hex=nwkskey_hex) self.rx1_dr_offset = (int.from_bytes(dlsettings, byteorder='big') & 0x70) >> 4 rx2_dr = (int.from_bytes(dlsettings, byteorder='big') & 0x0f) self.rx2_dr = lorawan_parameters.LORA_DR[rx2_dr] seconds_delay = max(1, (int.from_bytes(rxdelay, byteorder='big') & 0x0f)) self.rx1_delay = seconds_delay * lorawan_parameters.TIMING.MS_IN_SEC self.last_join_accept_hex = utils.bytes_to_text(join_accept_phypayload)
def fopts_to_str(self): """ (LoRaWANFHDR) -> (str) Get string repr of FOpts """ if self.fopts_bytes is None: return None else: return utils.bytes_to_text(self.fopts_bytes)
def step_handler(self, ch, method, properties, body): """ Pong message handler.""" if not self.received_testscript_msg: self.received_testscript_msg = flora_messages.GatewayMessage( json_ttm_str=body.decode()) received_lorawan = self.received_testscript_msg.parse_lorawan_message() appskey = self.ctx_test_manager.device_under_test.loramac_params.appskey received_frmpayload = received_lorawan.get_frmpayload_plaintext( key=appskey) mtype_str = received_lorawan.mhdr.mtype_str if mtype_str in ('UNCONFIRMED_UP', 'UNCONFIRMED_DOWN', 'CONFIRMED_UP', 'CONFIRMED_DOWN'): if (received_lorawan.macpayload.fport_int == 224 and received_frmpayload[0:1] == lorawan.lorawan_parameters.testing.TEST_CODE.PINGPONG): if not received_frmpayload == self.expected_bytes: raise lorawan_errors.EchoError( description="PONG {0} received when expecting {1}.". format(utils.bytes_to_text(received_frmpayload), utils.bytes_to_text(self.expected_bytes)), step_name=self.name, test_case=self.ctx_test_manager.tc_name, last_message=self.received_testscript_msg. get_printable_str(encryption_key=appskey)) else: # If it's a data message, but not a PONG, the FRMPayload is decrypted and showed in the GUI. raise test_errors.UnexpectedResponseError( description="Waiting for a PONG response.", step_name=self.name, last_message=self.received_testscript_msg. get_printable_str(encryption_key=appskey), test_case=self.ctx_test_manager.tc_name) else: raise test_errors.UnexpectedResponseError( description="Waiting for a PONG response.", step_name=self.name, last_message=self.received_testscript_msg.get_printable_str(), test_case=self.ctx_test_manager.tc_name)
def step_handler(self, ch, method, properties, body): super().step_handler(ch, method, properties, body) lw_response, send_ping, _ = self.pingpong_echo_exchange( next_step=self.next_step) lw_response_wrong_mic = lw_response[:-4:] + b'\xff\xff\xff\xff' device = self.ctx_test_manager.device_under_test json_nwk_response = self.received_testscript_msg.create_nwk_response_str( phypayload=lw_response_wrong_mic, delay=device.loramac_params.rx1_delay, datr_offset=device.loramac_params.rx1_dr_offset) self.send_downlink(msg=json_nwk_response, routing_key=message_broker.routing_keys.toAgent + '.gw1') # Manually decrease the downlink counter. self.ctx_test_manager.ctx_test_session_coordinator.downlink_counter -= 1 mic = bytes_to_text(lw_response[-4::]) mic_modif = bytes_to_text(b'\xff\xff\xff\xff') self.print_step_info( sending=send_ping, additional_message=f"Modified MIC: {mic} ->{mic_modif}\n")
def get_printable_str(self, encryption_key=None, ignore_format_errors=False): """ Creates a human readable string representation of the message.""" logger.info( f"Getting printable representation of the Gateway Message.") lorawan_message = self.parse_lorawan_message( ignore_format_errors=ignore_format_errors) ret_str = f"tmst: {self.testingtool_msg_dict['tmst']}, freq: {self.testingtool_msg_dict['freq']}, DR: {self.testingtool_msg_dict['datr']}\n" phypay = utils.bytes_to_text( base64.b64decode(self.testingtool_msg_dict["data"])) ret_str += f"PHYPayload: {phypay} (Size: {self.testingtool_msg_dict['size']} bytes)\n" ret_str += str(lorawan_message) if encryption_key is None: return ret_str frmpayload_plaintext = lorawan_message.get_frmpayload_plaintext( key=encryption_key) if frmpayload_plaintext is None: return ret_str frmpay = utils.bytes_to_text(frmpayload_plaintext) ekey = utils.bytes_to_text(encryption_key) ret_str += f"Decrypted FRMPayload: {frmpay}\n (Key {ekey})\n" return ret_str
def store_used_devnonce(self, devnonce_bytes): """ (EndDevice, int) -> (None) Store the used device nonce to avoid repeated use of the same value and prevent replay attacks. :param devnonce: (int) Value to store as a used device nonce. :return: None """ devnonce_hex_new = utils.bytes_to_text(devnonce_bytes) devnonce_hex_list = self.get_used_devnoce_hex_list() if devnonce_hex_new in devnonce_hex_list: raise scheduler_errors.DuplicatedNonce() if len(devnonce_hex_list) > 3: devnonce_hex_list = devnonce_hex_list[1:] devnonce_hex_list.append(devnonce_hex_new) self.used_otaa_devnonces_hex = ",".join(devnonce_hex_list)
def step_handler(self, ch, method, properties, body): super().step_handler(ch, method, properties, body) frmpayload = None fport = None fctr = lorawan_parameters.FCTRL.DOWN_ADROFF_ACKOFF_FPENDOFF_FOPTLEN0 fopts = b'' if self.piggybacked: fctr = struct.pack('B', len(self.command_bytes) % 16) fopts = self.command_bytes if self.in_frmpayload: frmpayload = self.command_bytes fport = 0 lw_response = self.ctx_test_manager.device_under_test.prepare_lorawan_data( frmpayload=frmpayload, fport=fport, mhdr=lorawan_parameters.MHDR.CONFIRMED_DOWN, fctr=fctr, fopts=fopts) device = self.ctx_test_manager.device_under_test json_nwk_response = self.received_testscript_msg.create_nwk_response_str( phypayload=lw_response, delay=device.loramac_params.rx1_delay, datr_offset=device.loramac_params.rx1_dr_offset) self.send_downlink(msg=json_nwk_response, routing_key=message_broker.routing_keys.toAgent + '.gw1') # manually decrease the downlink counter, because this message should be ignored by the DUT. if self.piggybacked and self.in_frmpayload: self.ctx_test_manager.ctx_test_session_coordinator.downlink_counter -= 1 if self.received_testscript_msg.parse_lorawan_message( ).macpayload.fport_int == 0: key = self.ctx_test_manager.device_under_test.loramac_params.nwkskey else: key = self.ctx_test_manager.device_under_test.loramac_params.appskey self.print_step_info( received_str=self.received_testscript_msg.get_printable_str( encryption_key=key), additional_message= "Sending MAC Command: 0x{comm}\nPiggybacked: {p}\nIn FRMPayload: {f}" .format(comm=utils.bytes_to_text(self.command_bytes), p=self.piggybacked, f=self.in_frmpayload))
def print_step_info(self, received_str=None, sending=None, additional_message=None): """ Handles the display of the information of the current step, logging, printing on standard output or displaying in the GUI the information related to the step of the test under execution. :param received_str: Recieved message string. :param sending: Message to be sent to the user in this step. :param additional_message: Additional message to be presented to the user. :return: None """ if not received_str: received_str = self.received_testscript_msg.get_printable_str( encryption_key=self.ctx_test_manager.device_under_test. loramac_params.appskey) step_report = ui_reports.InputFormBody( title=f"{self.ctx_test_manager.tc_name.upper()}: Step information", tag_key=self.ctx_test_manager.tc_name, tag_value=" ") if self.next_step: step_name = self.next_step.name else: step_name = "No next step." step_info_str = f"\nNext step: {step_name}\nReceived from DUT:\n {received_str}" step_report.add_field( ui_reports.ParagraphField(name=f"Completed Step: {self.name}", value="")) for line in step_info_str.split("\n"): step_report.add_field( ui_reports.ParagraphField(name="", value=line)) if sending: step_report.add_field( ui_reports.ParagraphField(name="Sending to DUT:", value=utils.bytes_to_text(sending))) if additional_message: step_report.add_field( ui_reports.ParagraphField(name="Additional information:", value=additional_message)) ui_publisher.display_on_gui( msg_str=str(step_report), key_prefix=message_broker.service_names.test_session_coordinator)
def setdefault(self, key, default=None): self._assert_mutable() key = bytes_to_text(key, self.encoding) default = bytes_to_text(default, self.encoding) return super(QueryDict, self).setdefault(key, default)
def __str__(self): ret_str = super().__str__() ret_str += "Battery Level: 0x{bat_b}\n".format(bat_b=utils.bytes_to_text(self.battery)) ret_str += "Margin: 0x{mar_b}\n".format(mar_b=utils.bytes_to_text(self.margin)) return ret_str
def test_bytes_nosep(self, bytes_to_convert, expected): """ Tests the utility function with no separator ('').""" assert utils.bytes_to_text(bytes_to_convert) == expected
def appendlist(self, key, value): self._assert_mutable() key = bytes_to_text(key, self.encoding) value = bytes_to_text(value, self.encoding) super(QueryDict, self).appendlist(key, value)
def setlist(self, key, list_): self._assert_mutable() key = bytes_to_text(key, self.encoding) list_ = [bytes_to_text(elt, self.encoding) for elt in list_] super(QueryDict, self).setlist(key, list_)
def __setitem__(self, key, value): self._assert_mutable() key = bytes_to_text(key, self.encoding) value = bytes_to_text(value, self.encoding) super(QueryDict, self).__setitem__(key, value)
def test_bytes_defaultsep(self, bytes_to_convert, expected): """ Tests the utility function with the default separator (' ', a space).""" assert utils.bytes_to_text(bytes_to_convert) == expected
def up_message_handler(self, body_str): received_testscript_msg = flora_messages.GatewayMessage( json_ttm_str=body_str) lorawan_msg = received_testscript_msg.parse_lorawan_message() logger.info("--------------------------------------------------------\n") logger.info(f"Received Uplink: {str(lorawan_msg)}") mtype_int = lorawan_msg.mhdr.mtype_int if lorawan_msg.mhdr.mhdr_bytes == lorawan_parameters.MHDR.JOIN_REQUEST: try: devnonce = lorawan_msg.macpayload.devnonce_bytes deveui_hex = utils.bytes_to_text(lorawan_msg.macpayload.deveui_bytes).upper() appeui_hex = utils.bytes_to_text(lorawan_msg.macpayload.appeui_bytes).upper() if not self.sessions_handler.is_registered(dev_eui_hex=deveui_hex, app_eui_hex=appeui_hex): logger.info(f"Device Not Registered: {deveui_hex}") return self.sessions_handler.process_otta_join( deveui_hex=deveui_hex, devnonce=devnonce, dlsettings=self.accept_dlsettings, rxdelay=self.accept_rxdelay, cflist=self.accept_cflist) jaccept_phypayload = self.sessions_handler.get_joinaccept_bytes( deveui_hex=deveui_hex) json_nwk_response = received_testscript_msg.create_nwk_response_str( phypayload=jaccept_phypayload, delay=lorawan_parameters.TIMING.JOIN_ACCEPT_DELAY1, datr_offset=lorawan_parameters.DR_OFFSET.RX1_DEFAULT) logger.info(f"Sending Join Accept: {str(json_nwk_response)}") self.downlink_mq_interface.send(routing_key=routing_keys.fromSchedulerToAgent, data=json_nwk_response) except scheduler_errors.DuplicatedNonce as dne: logger.info(f"Ignoring Duplicated nonce {devnonce}") elif lorawan_msg.mhdr.mhdr_bytes == lorawan_parameters.MHDR.UNCONFIRMED_UP: devaddrhex = utils.bytes_to_text(lorawan_msg.macpayload.fhdr.devaddr_bytes).upper() network_key_hex = self.sessions_handler.get_nwk_s_key_hex(dev_addr_hex=devaddrhex) if network_key_hex is None: logger.info(f"No active session for device: {devaddrhex}") return network_key = bytes.fromhex(network_key_hex) calculated_mic = lorawan_msg.calculate_mic(key=network_key) dev_eui_hex = self.sessions_handler.get_dev_eui_hex(dev_addr_hex=devaddrhex) if not lorawan_msg.mic_bytes == calculated_mic: logger.info( f"Wrong MIC. Expecting {calculated_mic}, Device {dev_eui_hex} ({devaddrhex}).") logger.info(f"MIC OK (NwkSKey: {utils.bytes_to_text(network_key)}, dev {dev_eui_hex})") frmpayload_command = bytes.fromhex( self.sessions_handler.get_command_hex(dev_addr_hex=devaddrhex)) lw_response = self.sessions_handler.prepare_lorawan_data(dev_eui_hex=dev_eui_hex, frmpayload=frmpayload_command) json_nwk_response = received_testscript_msg.create_nwk_response_str( phypayload=lw_response, delay=lorawan_parameters.TIMING.RECEIVE_DELAY1, datr_offset=lorawan_parameters.DR_OFFSET.RX1_DEFAULT) logger.info(f"Sending Downlink Data: {str(json_nwk_response)}") self.downlink_mq_interface.send(routing_key=routing_keys.fromSchedulerToAgent, data=json_nwk_response)