def test_application_data(): name = 'test_node' addr = get_random_bytes(2) devkey = get_random_bytes(16) device_uuid = get_random_bytes(16) network = f'test_net' num_apps = 10 apps = [] for x in range(num_apps): apps.append(f'test_app{x}') data = NodeData(name=name, addr=addr, network=network, device_uuid=device_uuid, devkey=devkey, apps=apps) assert file_helper.file_exist(base_dir + node_dir + name + '.yml') is \ False data.save() assert file_helper.file_exist(base_dir + node_dir + name + '.yml') is \ True r_data = NodeData.load(base_dir + node_dir + name + '.yml') assert data == r_data
def __header_segmented_transport_pdu(self, soft_ctx: SoftContext, seg_o: int) -> bytes: net_data = NetworkData.load(base_dir + net_dir + soft_ctx.network_name + '.yml') node_data = NodeData.load(base_dir + node_dir + soft_ctx.node_name + '.yml') seq_auth = (int.from_bytes(net_data.iv_index, 'big') << 24) | \ node_data.seq self.net_layer.hard_ctx.seq_zero = seq_auth & 0x1fff first_byte = 0x80 if not soft_ctx.is_devkey: app_data = ApplicationData.load(base_dir + app_dir + soft_ctx.application_name + '.yml') aid = crypto.k4(n=app_data.key) first_byte = first_byte | 0x40 else: node_data = NodeData.load(base_dir + node_dir + soft_ctx.node_name + '.yml') aid = crypto.k4(n=node_data.devkey) first_byte = (first_byte | (aid[0] & 0x3f)).to_bytes(1, 'big') cont = (self.net_layer.hard_ctx.seg_n & 0x1f) cont = cont | ((seg_o & 0x1f) << 5) cont = cont | ((self.net_layer.hard_ctx.seq_zero & 0x1fff) << 10) cont = cont.to_bytes(3, 'big') return first_byte + cont
def _encrypt_access_pdu(self, pdu: bytes, soft_ctx: SoftContext) -> bytes: net_data = NetworkData.load(base_dir + net_dir + soft_ctx.network_name + '.yml') node_data = NodeData.load(base_dir + node_dir + soft_ctx.node_name + '.yml') self.net_layer.hard_ctx.seq = node_data.seq if not soft_ctx.is_devkey: app_data = ApplicationData.load(base_dir + app_dir + soft_ctx.application_name + '.yml') app_key = app_data.key app_nonce = b'\x01\x00' + \ self.net_layer.hard_ctx.seq.to_bytes(3, 'big') + \ soft_ctx.src_addr + soft_ctx.dst_addr + net_data.iv_index else: node_data = NodeData.load(base_dir + node_dir + soft_ctx.node_name + '.yml') app_key = node_data.devkey app_nonce = b'\x02\x00' + \ self.net_layer.hard_ctx.seq.to_bytes(3, 'big') + \ soft_ctx.src_addr + soft_ctx.dst_addr + net_data.iv_index result, mic = crypto.aes_ccm_complete(key=app_key, nonce=app_nonce, text=pdu, adata=b'', mic_size=4) return result + mic
async def send_pdu(self, transport_pdu: bytes, soft_ctx: SoftContext): net_data = NetworkData.load(base_dir + net_dir + soft_ctx.network_name + '.yml') node_data = NodeData.load(base_dir + node_dir + soft_ctx.node_name + '.yml') self.hard_ctx.seq = node_data.seq nid, encryption_key, privacy_key = \ self._gen_security_material(net_data) ivi = ((int.from_bytes(net_data.iv_index, 'big') & 0x01) << 7) ctl = 0x80 if self.hard_ctx.is_ctrl_msg else 0x00 ttl = 0x02 seq = self.hard_ctx.seq src = soft_ctx.src_addr net_nonce = b'\x00' + (ctl | ttl).to_bytes(1, 'big') + \ seq.to_bytes(3, 'big') + src + b'\x00\x00' + net_data.iv_index enc_dst, enc_transport_pdu, net_mic = self._encrypt( soft_ctx, transport_pdu, encryption_key, net_nonce) obsfucated = self._obsfucate(ctl, ttl, seq, src, enc_dst, enc_transport_pdu, net_mic, privacy_key, net_data) network_pdu = (ivi | nid).to_bytes(1, 'big') + obsfucated + enc_dst + \ enc_transport_pdu + net_mic await self.send_queue.put((b'message_s', network_pdu)) self.__increment_seq(soft_ctx)
def info(name): '''Get description about a node''' node_data = NodeData.load(base_dir + node_dir + name + '.yml') click.echo(click.style('***** Node data *****', fg='cyan')) click.echo(click.style(str(node_data), fg='cyan'))
async def model_subscription_add(client_element: Element, target: str, model_id: bytes, addr: bytes) -> bool: node_data = NodeData.load(base_dir + node_dir + target + '.yml') opcode = b'\x80\x1b' r_opcode = b'\x80\x1f' parameters = node_data.addr[::-1] + addr[::-1] + model_id[::-1] context = SoftContext(src_addr=b'\x00\x01', dst_addr=node_data.addr, node_name=node_data.name, network_name=node_data.network, application_name='', is_devkey=True, ack_timeout=3, segment_timeout=3) success = await client_element.send_message(opcode=opcode, parameters=parameters, ctx=context) if not success: return False r_content = await client_element.recv_message(opcode=r_opcode, segment_timeout=1, timeout=3, ctx=context) if r_content: if r_content[0] == 0 and r_content[1:] == parameters: return True return False
def new(name, network, address, uuid, template): '''Create a new node''' if template: if template[0]: name = template[0] if template[1]: network = template[1] if template[2]: address = template[2] if template[3]: uuid = template[3] if template[4]: tmpl = template[4] validate_name(None, None, name) validate_network(None, None, network) validate_addr(None, None, address) validate_uuid(None, None, uuid) if len(uuid) < 32: uuid = bytes.fromhex(uuid) + bytes((32 - len(uuid)) // 2) elif len(uuid) > 32: uuid = bytes.fromhex(uuid)[0:16] else: uuid = bytes.fromhex(uuid) print(f'address: {address}') address = bytes.fromhex(address) # provisioning device success, devkey = provisioning_device(uuid, network, address, name, False) if not success: click.echo(click.style('Error in provisioning', fg='red')) else: if tmpl['name_is_seq']: template_helper.update_sequence(tmpl, 'name') if tmpl['addr_is_seq']: template_helper.update_sequence(tmpl, 'address', custom_pattern=tmpl['name']) node_data = NodeData(name=name, addr=address, network=network, device_uuid=uuid, devkey=devkey) node_data.save() click.echo(click.style('A new node was created.', fg='green')) click.echo(click.style(str(node_data), fg='green'))
def __search_node_by_addr(self, addr: bytes) -> str: filenames = file_helper.list_files(base_dir + node_dir) for f in filenames: node_data = NodeData.load(base_dir + node_dir + f) if addr == node_data.addr: return node_data.name return ''
def send(target, opcode, parameters, devkey, app): '''Send a message to node''' click.echo(click.style(f'Sending message [{opcode}, {parameters}] to ' f'"{target}" node', fg='green')) node_data = NodeData.load(base_dir + node_dir + target + '.yml') opcode = bytes.fromhex(opcode) parameters = bytes.fromhex(parameters) try: loop = asyncio.get_event_loop() client_element = Element() if devkey: app_name = '' is_devkey = True elif not node_data.apps: click.echo(click.style('Using devkey beacuse node hasn\'t ' 'application registred', fg='yellow')) app_name = '' is_devkey = True else: if app in node_data.apps: app_name = app is_devkey = False else: app_name = node_data.apps[0] is_devkey = False context = SoftContext(src_addr=b'\x00\x01', dst_addr=node_data.addr, node_name=node_data.name, network_name=node_data.network, application_name=app_name, is_devkey=is_devkey, ack_timeout=30, segment_timeout=10) run_seq_t = run_seq([ client_element.spwan_tasks(loop), client_element.send_message(opcode=opcode, parameters=parameters, ctx=context) ]) loop.run_until_complete(run_seq_t) except KeyboardInterrupt: click.echo(click.style('Interruption by user', fg='yellow')) except RuntimeError: click.echo('Runtime error') except Exception as e: click.echo(f'Unknown error\n[{e}]') finally: client_element.disconnect() tasks_running = asyncio.Task.all_tasks() for t in tasks_running: t.cancel() loop.stop() # 4d096b543184ab000000000000000000000000 # PublishTTL default 7 (4.3.2.16)
def _decrypt_transport_pdu(self, pdu: bytes, ctx: SoftContext, first_seq: int) -> bytes: if self.net_layer.hard_ctx.szmic == 0: encrypted_pdu = pdu[0:-4] transport_mic = pdu[-4:] else: encrypted_pdu = pdu[0:-8] transport_mic = pdu[-8:] self.log.debug(f'Encrypted pdu: {encrypted_pdu.hex()}, mic = ' f'{transport_mic.hex()}') net_data = NetworkData.load(base_dir + net_dir + ctx.network_name + '.yml') if ctx.is_devkey: self.log.debug(f'Using devkey, node name [{ctx.node_name}]') if not ctx.node_name: self.log.debug('No node found') return None node_data = NodeData.load(base_dir + node_dir + ctx.node_name + '.yml') key = node_data.devkey nonce = b'\x02' else: app_data = ApplicationData.load(base_dir + app_dir + ctx.application_name + '.yml') key = app_data.key nonce = b'\x01' nonce += (self.net_layer.hard_ctx.szmic << 7).to_bytes(1, 'big') nonce += (first_seq).to_bytes(3, 'big') nonce += ctx.src_addr nonce += ctx.dst_addr nonce += net_data.iv_index access_pdu, mic_is_ok = crypto.aes_ccm_decrypt(key=key, nonce=nonce, text=encrypted_pdu, mic=transport_mic) self.log.debug(f'Access PDU: {access_pdu.hex()}, first seq: ' f'{hex(first_seq)}') if not mic_is_ok: self.log.debug(f'Mic is wrong, pdu: {access_pdu.hex()}, seq: ' f'{hex(first_seq)}') return None else: return access_pdu
async def send_cmd(client_element: Element, target: str, opcode: bytes, parameters: bytes, application: str): node_data = NodeData.load(base_dir + node_dir + target + '.yml') app_name = application if application else node_data.apps[0] context = SoftContext(src_addr=b'\x00\x01', dst_addr=node_data.addr, node_name=node_data.name, network_name=node_data.network, application_name=app_name, is_devkey=False, ack_timeout=3, segment_timeout=3) success = await client_element.send_message(opcode=opcode, parameters=parameters, ctx=context) return success
def _unsegmented_transport_pdu(self, pdu: bytes, soft_ctx: SoftContext) -> bytes: if not soft_ctx.is_devkey: app_data = ApplicationData.load(base_dir + app_dir + soft_ctx.application_name + '.yml') aid = crypto.k4(n=app_data.key) unseg_tr_pdu = 0x40 else: node_data = NodeData.load(base_dir + node_dir + soft_ctx.node_name + '.yml') aid = crypto.k4(n=node_data.devkey) unseg_tr_pdu = 0x00 unseg_tr_pdu = (unseg_tr_pdu | (int.from_bytes(aid, 'big') & 0x3f)).to_bytes( 1, 'big') unseg_tr_pdu += pdu return unseg_tr_pdu
async def appkey_add(client_element: Element, target: str, application: str) -> bool: node_data = NodeData.load(base_dir + node_dir + target + '.yml') app_data = ApplicationData.load(base_dir + app_dir + application + '.yml') net_data = NetworkData.load(base_dir + net_dir + app_data.network + '.yml') net_key = int.from_bytes(net_data.key_index, 'big') app_key = int.from_bytes(app_data.key_index, 'big') key_index = (net_key | (app_key << 12)).to_bytes(3, 'big')[::-1] opcode = b'\x00' r_opcode = b'\x80\x03' parameters = key_index + app_data.key context = SoftContext(src_addr=b'\x00\x01', dst_addr=node_data.addr, node_name=node_data.name, network_name=node_data.network, application_name='', is_devkey=True, ack_timeout=3, segment_timeout=3) success = await client_element.send_message(opcode=opcode, parameters=parameters, ctx=context) if not success: return False r_content = await client_element.recv_message(opcode=r_opcode, segment_timeout=1, timeout=3, ctx=context) if r_content: if r_content[0] == 0 and r_content[1:] == key_index: if app_data.name not in node_data.apps: node_data.apps.append(app_data.name) node_data.save() if node_data.name not in app_data.nodes: app_data.nodes.append(node_data.name) app_data.save() return True return False
async def model_publication_set(client_element: Element, target: str, model_id: bytes, addr: bytes, application: str) -> bool: node_data = NodeData.load(base_dir + node_dir + target + '.yml') app_data = ApplicationData.load(base_dir + app_dir + application + '.yml') key_index = app_data.key_index default_ttl = b'\x07' period = b'\x00' default_xmit_int = b'\x40' opcode = b'\x03' r_opcode = b'\x80\x19' parameters = node_data.addr[::-1] + addr[::-1] + key_index[::-1] + \ default_ttl[::-1] + period[::-1] + default_xmit_int[::-1] + \ model_id[::-1] context = SoftContext(src_addr=b'\x00\x01', dst_addr=node_data.addr, node_name=node_data.name, network_name=node_data.network, application_name='', is_devkey=True, ack_timeout=3, segment_timeout=3) success = await client_element.send_message(opcode=opcode, parameters=parameters, ctx=context) if not success: return False r_content = await client_element.recv_message(opcode=r_opcode, segment_timeout=1, timeout=3, ctx=context) if r_content: if r_content[0] == 0 and r_content[1:] == parameters: return True return False
def __increment_seq(self, soft_ctx: SoftContext): node_name = soft_ctx.node_name node_data = NodeData.load(base_dir + node_dir + node_name + '.yml') node_data.seq += 1 node_data.save()
def add_appkey(target, app): '''Add a appkey to node''' click.echo( click.style( f'Add the appkey of "{app}" application to ' f'"{target}" node', fg='green')) node_data = NodeData.load(base_dir + node_dir + target + '.yml') app_data = ApplicationData.load(base_dir + app_dir + app + '.yml') net_data = NetworkData.load(base_dir + net_dir + node_data.network + '.yml') net_key = int.from_bytes(net_data.key_index, 'big') app_key = int.from_bytes(app_data.key_index, 'big') key_index = (net_key | (app_key << 12)).to_bytes(3, 'big')[::-1] opcode = b'\x00' r_opcode = b'\x80\x03' parameters = key_index + app_data.key try: loop = asyncio.get_event_loop() client_element = Element() context = SoftContext(src_addr=b'\x00\x01', dst_addr=node_data.addr, node_name=node_data.name, network_name=node_data.network, application_name='', is_devkey=True, ack_timeout=10, segment_timeout=3) run_seq_t = run_seq([ client_element.spwan_tasks(loop), client_element.send_message(opcode=opcode, parameters=parameters, ctx=context), client_element.recv_message(opcode=r_opcode, segment_timeout=3, timeout=10, ctx=context) ]) results = loop.run_until_complete(run_seq_t) content = results[2][0] if content: if content[0] == 0: if content[1:] == key_index: click.echo( click.style('App key add with successful', fg='green')) if app_data.name not in node_data.apps: node_data.apps.append(app_data.name) node_data.save() if node_data.name not in app_data.nodes: app_data.nodes.append(node_data.name) app_data.save() else: click.echo( click.style(f'Wrong key index: {content[1:].hex()}', fg='red')) else: click.echo( click.style(f'Fail. Error code: {content[0:1].hex()}', fg='red')) except KeyboardInterrupt: click.echo(click.style('Interruption by user', fg='yellow')) except RuntimeError: click.echo('Runtime error') except Exception: click.echo(traceback.format_exc()) finally: client_element.disconnect() tasks_running = asyncio.Task.all_tasks() for t in tasks_running: t.cancel() loop.stop()
def req(target, opcode, parameters, r_opcode, devkey, app): '''Request a message to node (Send a message and wait the response)''' click.echo( click.style( f'Sending message [{opcode}, {parameters}] to ' f'"{target}" node and wait receive a message with ' f'opcode {r_opcode}', fg='green')) node_data = NodeData.load(base_dir + node_dir + target + '.yml') print(f'node addr: {node_data.addr}') opcode = bytes.fromhex(opcode) r_opcode = bytes.fromhex(r_opcode) parameters = bytes.fromhex(parameters) try: loop = asyncio.get_event_loop() client_element = Element() if devkey: app_name = '' is_devkey = True elif not node_data.apps: click.echo( click.style( 'Using devkey beacuse node hasn\'t ' 'application registred', fg='yellow')) app_name = '' is_devkey = True else: if app in node_data.apps: app_name = app is_devkey = False else: app_name = node_data.apps[0] is_devkey = False context = SoftContext(src_addr=b'\x00\x01', dst_addr=node_data.addr, node_name=node_data.name, network_name=node_data.network, application_name=app_name, is_devkey=is_devkey, ack_timeout=30, segment_timeout=10) run_seq_t = run_seq([ client_element.spwan_tasks(loop), client_element.send_message(opcode=opcode, parameters=parameters, ctx=context), client_element.recv_message(opcode=r_opcode, segment_timeout=3, timeout=10, ctx=context) ]) results = loop.run_until_complete(run_seq_t) content = results[2][0] if content: click.echo( click.style(f'Message received: {content.hex()}', fg='white')) else: click.echo( click.style( f'No message with opcode {r_opcode.hex()} ' f'received', fg='red')) except KeyboardInterrupt: click.echo(click.style('Interruption by user', fg='yellow')) except RuntimeError: click.echo('Runtime error') except Exception as e: click.echo(f'Unknown error\n[{e}]') finally: client_element.disconnect() tasks_running = asyncio.Task.all_tasks() for t in tasks_running: t.cancel() loop.stop()