1*6ccd8248SMilanka Ringwald#!/usr/bin/env python3 21fbe4564SMatthias Ringwald# 31fbe4564SMatthias Ringwald# Simulate network of Bluetooth Controllers 41fbe4564SMatthias Ringwald# 51fbe4564SMatthias Ringwald# Each simulated controller has an HCI H4 interface 61fbe4564SMatthias Ringwald# Network configuration will be stored in a YAML file or similar 71fbe4564SMatthias Ringwald# 81fbe4564SMatthias Ringwald# Copyright 2017 BlueKitchen GmbH 91fbe4564SMatthias Ringwald# 101fbe4564SMatthias Ringwald 111fbe4564SMatthias Ringwald 121fbe4564SMatthias Ringwaldimport os 131fbe4564SMatthias Ringwaldimport pty 141fbe4564SMatthias Ringwaldimport select 151fbe4564SMatthias Ringwaldimport subprocess 161fbe4564SMatthias Ringwaldimport sys 171fbe4564SMatthias Ringwaldimport bisect 181fbe4564SMatthias Ringwaldimport time 191fbe4564SMatthias Ringwald 20*6ccd8248SMilanka Ringwald# fallback: try to import PyCryptodome as (an almost drop-in) replacement for the PyCrypto library 21*6ccd8248SMilanka Ringwaldtry: 22*6ccd8248SMilanka Ringwald from Cryptodome.Cipher import AES 23*6ccd8248SMilanka Ringwald import Cryptodome.Random as Random 24*6ccd8248SMilanka Ringwaldexcept ImportError: 25*6ccd8248SMilanka Ringwald # fallback: try to import PyCryptodome as (an almost drop-in) replacement for the PyCrypto library 26*6ccd8248SMilanka Ringwald try: 271fbe4564SMatthias Ringwald from Crypto.Cipher import AES 28*6ccd8248SMilanka Ringwald import Crypto.Random as Random 29*6ccd8248SMilanka Ringwald except ImportError: 30*6ccd8248SMilanka Ringwald print("\n[!] PyCryptodome required but not installed (using random value instead)") 31*6ccd8248SMilanka Ringwald print("[!] Please install PyCryptodome, e.g. 'pip3 install pycryptodomex' or 'pip3 install pycryptodome'\n") 32*6ccd8248SMilanka Ringwald 331fbe4564SMatthias Ringwald 341fbe4564SMatthias Ringwalddef little_endian_read_16(buffer, pos): 351fbe4564SMatthias Ringwald return ord(buffer[pos]) + (ord(buffer[pos+1]) << 8) 361fbe4564SMatthias Ringwald 371fbe4564SMatthias Ringwalddef as_hex(data): 381fbe4564SMatthias Ringwald str_list = [] 391fbe4564SMatthias Ringwald for byte in data: 401fbe4564SMatthias Ringwald str_list.append("{0:02x} ".format(ord(byte))) 411fbe4564SMatthias Ringwald return ''.join(str_list) 421fbe4564SMatthias Ringwald 431fbe4564SMatthias Ringwaldadv_type_names = ['ADV_IND', 'ADV_DIRECT_IND_HIGH', 'ADV_SCAN_IND', 'ADV_NONCONN_IND', 'ADV_DIRECT_IND_LOW'] 441fbe4564SMatthias Ringwaldtimers_timeouts = [] 451fbe4564SMatthias Ringwaldtimers_callbacks = [] 461fbe4564SMatthias Ringwald 471fbe4564SMatthias Ringwaldclass H4Parser: 481fbe4564SMatthias Ringwald 491fbe4564SMatthias Ringwald def __init__(self): 501fbe4564SMatthias Ringwald self.packet_type = "NONE" 511fbe4564SMatthias Ringwald self.reset() 521fbe4564SMatthias Ringwald 531fbe4564SMatthias Ringwald def set_packet_handler(self, handler): 541fbe4564SMatthias Ringwald self.handler = handler 551fbe4564SMatthias Ringwald 561fbe4564SMatthias Ringwald def reset(self): 571fbe4564SMatthias Ringwald self.bytes_to_read = 1 581fbe4564SMatthias Ringwald self.buffer = '' 591fbe4564SMatthias Ringwald self.state = "H4_W4_PACKET_TYPE" 601fbe4564SMatthias Ringwald 611fbe4564SMatthias Ringwald def parse(self, data): 621fbe4564SMatthias Ringwald self.buffer += data 631fbe4564SMatthias Ringwald self.bytes_to_read -= 1 641fbe4564SMatthias Ringwald if self.bytes_to_read == 0: 651fbe4564SMatthias Ringwald if self.state == "H4_W4_PACKET_TYPE": 661fbe4564SMatthias Ringwald self.buffer = '' 671fbe4564SMatthias Ringwald if data == chr(1): 681fbe4564SMatthias Ringwald # cmd 691fbe4564SMatthias Ringwald self.packet_type = "CMD" 701fbe4564SMatthias Ringwald self.state = "W4_CMD_HEADER" 711fbe4564SMatthias Ringwald self.bytes_to_read = 3 721fbe4564SMatthias Ringwald if data == chr(2): 731fbe4564SMatthias Ringwald # acl 741fbe4564SMatthias Ringwald self.packet_type = "ACL" 751fbe4564SMatthias Ringwald self.state = "W4_ACL_HEADER" 761fbe4564SMatthias Ringwald self.bytes_to_read = 4 771fbe4564SMatthias Ringwald return 781fbe4564SMatthias Ringwald if self.state == "W4_CMD_HEADER": 791fbe4564SMatthias Ringwald self.bytes_to_read = ord(self.buffer[2]) 801fbe4564SMatthias Ringwald self.state = "H4_W4_PAYLOAD" 811fbe4564SMatthias Ringwald if self.bytes_to_read > 0: 821fbe4564SMatthias Ringwald return 831fbe4564SMatthias Ringwald # fall through to handle payload len = 0 841fbe4564SMatthias Ringwald if self.state == "W4_ACL_HEADER": 851fbe4564SMatthias Ringwald self.bytes_to_read = little_endian_read_16(buffer, 2) 861fbe4564SMatthias Ringwald self.state = "H4_W4_PAYLOAD" 871fbe4564SMatthias Ringwald if self.bytes_to_read > 0: 881fbe4564SMatthias Ringwald return 891fbe4564SMatthias Ringwald # fall through to handle payload len = 0 901fbe4564SMatthias Ringwald if self.state == "H4_W4_PAYLOAD": 911fbe4564SMatthias Ringwald self.handler(self.packet_type, self.buffer) 921fbe4564SMatthias Ringwald self.reset() 931fbe4564SMatthias Ringwald return 941fbe4564SMatthias Ringwald 951fbe4564SMatthias Ringwaldclass HCIController: 961fbe4564SMatthias Ringwald 971fbe4564SMatthias Ringwald def __init__(self): 981fbe4564SMatthias Ringwald self.fd = -1 991fbe4564SMatthias Ringwald self.random = Random.new() 1001fbe4564SMatthias Ringwald self.name = 'BTstack Mesh Simulator' 1011fbe4564SMatthias Ringwald self.bd_addr = 'aaaaaa' 1021fbe4564SMatthias Ringwald self.parser = H4Parser() 1031fbe4564SMatthias Ringwald self.parser.set_packet_handler(self.packet_handler) 1041fbe4564SMatthias Ringwald self.adv_enabled = 0 1051fbe4564SMatthias Ringwald self.adv_type = 0 1061fbe4564SMatthias Ringwald self.adv_interval_min = 0 1071fbe4564SMatthias Ringwald self.adv_interval_max = 0 1081fbe4564SMatthias Ringwald self.adv_data = '' 1091fbe4564SMatthias Ringwald self.scan_enabled = False 1101fbe4564SMatthias Ringwald 1111fbe4564SMatthias Ringwald def parse(self, data): 1121fbe4564SMatthias Ringwald self.parser.parse(data) 1131fbe4564SMatthias Ringwald 1141fbe4564SMatthias Ringwald def set_fd(self,fd): 1151fbe4564SMatthias Ringwald self.fd = fd 1161fbe4564SMatthias Ringwald 1171fbe4564SMatthias Ringwald def set_bd_addr(self, bd_addr): 1181fbe4564SMatthias Ringwald self.bd_addr = bd_addr 1191fbe4564SMatthias Ringwald 1201fbe4564SMatthias Ringwald def set_name(self, name): 1211fbe4564SMatthias Ringwald self.name = name 1221fbe4564SMatthias Ringwald 1231fbe4564SMatthias Ringwald def set_adv_handler(self, adv_handler, adv_handler_context): 1241fbe4564SMatthias Ringwald self.adv_handler = adv_handler 1251fbe4564SMatthias Ringwald self.adv_handler_context = adv_handler_context 1261fbe4564SMatthias Ringwald 1271fbe4564SMatthias Ringwald def is_scanning(self): 1281fbe4564SMatthias Ringwald return self.scan_enabled 1291fbe4564SMatthias Ringwald 1301fbe4564SMatthias Ringwald def emit_command_complete(self, opcode, result): 1311fbe4564SMatthias Ringwald # type, event, len, num commands, opcode, result 1321fbe4564SMatthias Ringwald os.write(self.fd, '\x04\x0e' + chr(3 + len(result)) + chr(1) + chr(opcode & 255) + chr(opcode >> 8) + result) 1331fbe4564SMatthias Ringwald 1341fbe4564SMatthias Ringwald def emit_adv_report(self, event_type, rssi): 1351fbe4564SMatthias Ringwald # type, event, len, Subevent_Code, Num_Reports, Event_Type[i], Address_Type[i], Address[i], Length[i], Data[i], RSSI[i] 1361fbe4564SMatthias Ringwald event = '\x04\x3e' + chr(12 + len(self.adv_data)) + chr(2) + chr(1) + chr(event_type) + chr(0) + self.bd_addr[::-1] + chr(len(self.adv_data)) + self.adv_data + chr(rssi) 1371fbe4564SMatthias Ringwald self.adv_handler(self.adv_handler_context, event) 1381fbe4564SMatthias Ringwald 1391fbe4564SMatthias Ringwald def handle_set_adv_enable(self, enable): 1401fbe4564SMatthias Ringwald self.adv_enabled = enable 1411fbe4564SMatthias Ringwald print('Node %s adv enable %u' % (self.name, self.adv_enabled)) 1421fbe4564SMatthias Ringwald if self.adv_enabled: 1431fbe4564SMatthias Ringwald add_timer(1, self.handle_adv_timer, self) 1441fbe4564SMatthias Ringwald else: 1451fbe4564SMatthias Ringwald remove_timer(self.handle_adv_timer, self) 1461fbe4564SMatthias Ringwald 1471fbe4564SMatthias Ringwald def handle_set_adv_data(self, data): 1481fbe4564SMatthias Ringwald self.adv_data = data 1491fbe4564SMatthias Ringwald print('Node %s adv data %s' % (self.name, as_hex(self.adv_data))) 1501fbe4564SMatthias Ringwald 1511fbe4564SMatthias Ringwald def handle_set_adv_params(self, interval_min, interval_max, adv_type): 1521fbe4564SMatthias Ringwald self.adv_interval_min = interval_min * 0.625 1531fbe4564SMatthias Ringwald self.adv_interval_max = interval_max * 0.625 1541fbe4564SMatthias Ringwald self.adv_type = adv_type 1551fbe4564SMatthias Ringwald print('Node %s adv interval min/max %u/%u ms, type %s' % (self.name, self.adv_interval_min, self.adv_interval_max, adv_type_names[self.adv_type])) 1561fbe4564SMatthias Ringwald 1571fbe4564SMatthias Ringwald def handle_adv_timer(self, context): 1581fbe4564SMatthias Ringwald if self.adv_enabled: 1591fbe4564SMatthias Ringwald self.emit_adv_report(0, 0) 1601fbe4564SMatthias Ringwald add_timer(self.adv_interval_min, self.handle_adv_timer, self) 1611fbe4564SMatthias Ringwald 1621fbe4564SMatthias Ringwald def packet_handler(self, packet_type, packet): 1631fbe4564SMatthias Ringwald opcode = little_endian_read_16(packet, 0) 1641fbe4564SMatthias Ringwald # print ("%s, opcode 0x%04x" % (self.name, opcode)) 1651fbe4564SMatthias Ringwald if opcode == 0x0c03: 1661fbe4564SMatthias Ringwald self.emit_command_complete(opcode, '\x00') 1671fbe4564SMatthias Ringwald return 1681fbe4564SMatthias Ringwald if opcode == 0x1001: 1691fbe4564SMatthias Ringwald self.emit_command_complete(opcode, '\x00\x10\x00\x06\x86\x1d\x06\x0a\x00\x86\x1d') 1701fbe4564SMatthias Ringwald return 1711fbe4564SMatthias Ringwald if opcode == 0x0c14: 1721fbe4564SMatthias Ringwald self.emit_command_complete(opcode, '\x00' + self.name) 1731fbe4564SMatthias Ringwald return 1741fbe4564SMatthias Ringwald if opcode == 0x1002: 1751fbe4564SMatthias Ringwald self.emit_command_complete(opcode, '\x00\xff\xff\xff\x03\xfe\xff\xff\xff\xff\xff\xff\xff\xf3\x0f\xe8\xfe\x3f\xf7\x83\xff\x1c\x00\x00\x00\x61\xf7\xff\xff\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') 1761fbe4564SMatthias Ringwald return 1771fbe4564SMatthias Ringwald if opcode == 0x1009: 1781fbe4564SMatthias Ringwald # read bd_addr 1791fbe4564SMatthias Ringwald self.emit_command_complete(opcode, '\x00' + self.bd_addr[::-1]) 1801fbe4564SMatthias Ringwald return 1811fbe4564SMatthias Ringwald if opcode == 0x1005: 1821fbe4564SMatthias Ringwald # read buffer size 1831fbe4564SMatthias Ringwald self.emit_command_complete(opcode, '\x00\x36\x01\x40\x0a\x00\x08\x00') 1841fbe4564SMatthias Ringwald return 1851fbe4564SMatthias Ringwald if opcode == 0x1003: 1861fbe4564SMatthias Ringwald # read local supported features 1871fbe4564SMatthias Ringwald self.emit_command_complete(opcode, '\x00\xff\xff\x8f\xfe\xf8\xff\x5b\x87') 1881fbe4564SMatthias Ringwald return 1891fbe4564SMatthias Ringwald if opcode == 0x0c01: 1901fbe4564SMatthias Ringwald self.emit_command_complete(opcode, '\x00') 1911fbe4564SMatthias Ringwald return 1921fbe4564SMatthias Ringwald if opcode == 0x2002: 1931fbe4564SMatthias Ringwald # le read buffer size 1941fbe4564SMatthias Ringwald self.emit_command_complete(opcode, '\x00\x00\x00\x00') 1951fbe4564SMatthias Ringwald return 1961fbe4564SMatthias Ringwald if opcode == 0x200f: 1971fbe4564SMatthias Ringwald # read whitelist size 1981fbe4564SMatthias Ringwald self.emit_command_complete(opcode, '\x00\x19') 1991fbe4564SMatthias Ringwald return 2001fbe4564SMatthias Ringwald if opcode == 0x200b: 2011fbe4564SMatthias Ringwald # set scan parameters 2021fbe4564SMatthias Ringwald self.emit_command_complete(opcode, '\x00') 2031fbe4564SMatthias Ringwald return 2041fbe4564SMatthias Ringwald if opcode == 0x200c: 2051fbe4564SMatthias Ringwald # set scan enabled 2061fbe4564SMatthias Ringwald self.scan_enabled = ord(packet[3]) 2071fbe4564SMatthias Ringwald self.emit_command_complete(opcode, '\x00') 2081fbe4564SMatthias Ringwald return 2091fbe4564SMatthias Ringwald if opcode == 0x0c6d: 2101fbe4564SMatthias Ringwald # write le host supported 2111fbe4564SMatthias Ringwald self.emit_command_complete(opcode, '\x00') 2121fbe4564SMatthias Ringwald return 2131fbe4564SMatthias Ringwald if opcode == 0x2017: 2141fbe4564SMatthias Ringwald # LE Encrypt - key 16, data 16 2151fbe4564SMatthias Ringwald key = packet[18:2:-1] 2161fbe4564SMatthias Ringwald data = packet[35:18:-1] 2171fbe4564SMatthias Ringwald cipher = AES.new(key) 2181fbe4564SMatthias Ringwald result = cipher.encrypt(data) 2191fbe4564SMatthias Ringwald self.emit_command_complete(opcode, result[::-1]) 2201fbe4564SMatthias Ringwald return 2211fbe4564SMatthias Ringwald if opcode == 0x2018: 2221fbe4564SMatthias Ringwald # LE Rand 2231fbe4564SMatthias Ringwald self.emit_command_complete(opcode, '\x00' + self.random.read(8)) 2241fbe4564SMatthias Ringwald return 2251fbe4564SMatthias Ringwald if opcode == 0x2006: 2261fbe4564SMatthias Ringwald # Set Adv Params 2271fbe4564SMatthias Ringwald self.handle_set_adv_params(little_endian_read_16(packet,3), little_endian_read_16(packet,5), ord(packet[6])) 2281fbe4564SMatthias Ringwald self.emit_command_complete(opcode, '\x00') 2291fbe4564SMatthias Ringwald return 2301fbe4564SMatthias Ringwald if opcode == 0x2008: 2311fbe4564SMatthias Ringwald # Set Adv Data 2321fbe4564SMatthias Ringwald len = ord(packet[3]) 2331fbe4564SMatthias Ringwald self.handle_set_adv_data(packet[4:4+len]) 2341fbe4564SMatthias Ringwald self.emit_command_complete(opcode, '\x00') 2351fbe4564SMatthias Ringwald return 2361fbe4564SMatthias Ringwald if opcode == 0x200a: 2371fbe4564SMatthias Ringwald # Set Adv Enable 2381fbe4564SMatthias Ringwald self.handle_set_adv_enable(ord(packet[3])) 2391fbe4564SMatthias Ringwald self.emit_command_complete(opcode, '\x00') 2401fbe4564SMatthias Ringwald return 2411fbe4564SMatthias Ringwald print("Opcode 0x%0x not handled!" % opcode) 2421fbe4564SMatthias Ringwald 2431fbe4564SMatthias Ringwaldclass Node: 2441fbe4564SMatthias Ringwald 2451fbe4564SMatthias Ringwald def __init__(self): 2461fbe4564SMatthias Ringwald self.name = 'node' 2471fbe4564SMatthias Ringwald self.master = -1 2481fbe4564SMatthias Ringwald self.slave = -1 2491fbe4564SMatthias Ringwald self.slave_ttyname = '' 2501fbe4564SMatthias Ringwald self.controller = HCIController() 2511fbe4564SMatthias Ringwald 2521fbe4564SMatthias Ringwald def set_name(self, name): 2531fbe4564SMatthias Ringwald self.controller.set_name(name) 2541fbe4564SMatthias Ringwald self.name = name 2551fbe4564SMatthias Ringwald 2561fbe4564SMatthias Ringwald def get_name(self): 2571fbe4564SMatthias Ringwald return self.name 2581fbe4564SMatthias Ringwald 2591fbe4564SMatthias Ringwald def set_bd_addr(self, bd_addr): 2601fbe4564SMatthias Ringwald self.controller.set_bd_addr(bd_addr) 2611fbe4564SMatthias Ringwald 2621fbe4564SMatthias Ringwald def start_process(self): 2631fbe4564SMatthias Ringwald print('Node: %s' % self.name) 2641fbe4564SMatthias Ringwald (self.master, self.slave) = pty.openpty() 2651fbe4564SMatthias Ringwald self.slave_ttyname = os.ttyname(self.slave) 2661fbe4564SMatthias Ringwald print('- tty %s' % self.slave_ttyname) 2671fbe4564SMatthias Ringwald print('- fd %u' % self.master) 2681fbe4564SMatthias Ringwald self.controller.set_fd(self.master) 2691fbe4564SMatthias Ringwald subprocess.Popen(['./mesh', '-d', self.slave_ttyname]) 2701fbe4564SMatthias Ringwald 2711fbe4564SMatthias Ringwald def get_master(self): 2721fbe4564SMatthias Ringwald return self.master 2731fbe4564SMatthias Ringwald 2741fbe4564SMatthias Ringwald def parse(self, c): 2751fbe4564SMatthias Ringwald self.controller.parse(c) 2761fbe4564SMatthias Ringwald 2771fbe4564SMatthias Ringwald def set_adv_handler(self, adv_handler, adv_handler_context): 2781fbe4564SMatthias Ringwald self.controller.set_adv_handler(adv_handler, adv_handler_context) 2791fbe4564SMatthias Ringwald 2801fbe4564SMatthias Ringwald def inject_packet(self, event): 2811fbe4564SMatthias Ringwald os.write(self.master, event) 2821fbe4564SMatthias Ringwald 2831fbe4564SMatthias Ringwald def is_scanning(self): 2841fbe4564SMatthias Ringwald return self.controller.is_scanning() 2851fbe4564SMatthias Ringwald 2861fbe4564SMatthias Ringwalddef get_time_millis(): 2871fbe4564SMatthias Ringwald return int(round(time.time() * 1000)) 2881fbe4564SMatthias Ringwald 2891fbe4564SMatthias Ringwalddef add_timer(timeout_ms, callback, context): 2901fbe4564SMatthias Ringwald global timers_timeouts 2911fbe4564SMatthias Ringwald global timers_callbacks 2921fbe4564SMatthias Ringwald 2931fbe4564SMatthias Ringwald timeout = get_time_millis() + timeout_ms; 2941fbe4564SMatthias Ringwald pos = bisect.bisect(timers_timeouts, timeout) 2951fbe4564SMatthias Ringwald timers_timeouts.insert(pos, timeout) 2961fbe4564SMatthias Ringwald timers_callbacks.insert(pos, (callback, context)) 2971fbe4564SMatthias Ringwald 2981fbe4564SMatthias Ringwalddef remove_timer(callback, context): 2991fbe4564SMatthias Ringwald if (callback, context) in timers_callbacks: 3001fbe4564SMatthias Ringwald indices = [timers_callbacks.index(t) for t in timers_callbacks if t[0] == callback and t[1] == context] 3011fbe4564SMatthias Ringwald index = indices[0] 3021fbe4564SMatthias Ringwald timers_callbacks.pop(index) 3031fbe4564SMatthias Ringwald timers_timeouts.pop(index) 3041fbe4564SMatthias Ringwald 3051fbe4564SMatthias Ringwalddef run(nodes): 3061fbe4564SMatthias Ringwald # create map fd -> node 3071fbe4564SMatthias Ringwald nodes_by_fd = { node.get_master():node for node in nodes} 3081fbe4564SMatthias Ringwald read_fds = nodes_by_fd.keys() 3091fbe4564SMatthias Ringwald while True: 3101fbe4564SMatthias Ringwald # process expired timers 3111fbe4564SMatthias Ringwald time_ms = get_time_millis() 3121fbe4564SMatthias Ringwald while len(timers_timeouts) and timers_timeouts[0] < time_ms: 3131fbe4564SMatthias Ringwald timers_timeouts.pop(0) 3141fbe4564SMatthias Ringwald (callback,context) = timers_callbacks.pop(0) 3151fbe4564SMatthias Ringwald callback(context) 3161fbe4564SMatthias Ringwald # timer timers_timeouts? 3171fbe4564SMatthias Ringwald if len(timers_timeouts): 3181fbe4564SMatthias Ringwald timeout = (timers_timeouts[0] - time_ms) / 1000.0 3191fbe4564SMatthias Ringwald (read_ready, write_ready, exception_ready) = select.select(read_fds,[],[], timeout) 3201fbe4564SMatthias Ringwald else: 3211fbe4564SMatthias Ringwald (read_ready, write_ready, exception_ready) = select.select(read_fds,[],[]) 3221fbe4564SMatthias Ringwald for fd in read_ready: 3231fbe4564SMatthias Ringwald node = nodes_by_fd[fd] 3241fbe4564SMatthias Ringwald c = os.read(fd, 1) 3251fbe4564SMatthias Ringwald node.parse(c) 3261fbe4564SMatthias Ringwald 3271fbe4564SMatthias Ringwalddef adv_handler(src_node, event): 3281fbe4564SMatthias Ringwald global nodes 3291fbe4564SMatthias Ringwald # print('adv from %s' % src_node.get_name()) 3301fbe4564SMatthias Ringwald for dst_node in nodes: 3311fbe4564SMatthias Ringwald if src_node == dst_node: 3321fbe4564SMatthias Ringwald continue 3331fbe4564SMatthias Ringwald if not dst_node.is_scanning(): 3341fbe4564SMatthias Ringwald continue 3351fbe4564SMatthias Ringwald print('Adv %s -> %s - %s' % (src_node.get_name(), dst_node.get_name(), as_hex(event[14:-1]))) 3361fbe4564SMatthias Ringwald dst_node.inject_packet(event) 3371fbe4564SMatthias Ringwald 3381fbe4564SMatthias Ringwald# parse configuration file passed in via cmd line args 3391fbe4564SMatthias Ringwald# TODO 3401fbe4564SMatthias Ringwald 3411fbe4564SMatthias Ringwaldnode1 = Node() 3421fbe4564SMatthias Ringwaldnode1.set_name('node_1') 3431fbe4564SMatthias Ringwaldnode1.set_bd_addr('aaaaaa') 3441fbe4564SMatthias Ringwaldnode1.set_adv_handler(adv_handler, node1) 3451fbe4564SMatthias Ringwaldnode1.start_process() 3461fbe4564SMatthias Ringwald 3471fbe4564SMatthias Ringwaldnode2 = Node() 3481fbe4564SMatthias Ringwaldnode2.set_name('node_2') 3491fbe4564SMatthias Ringwaldnode2.set_bd_addr('bbbbbb') 3501fbe4564SMatthias Ringwaldnode2.set_adv_handler(adv_handler, node2) 3511fbe4564SMatthias Ringwaldnode2.start_process() 3521fbe4564SMatthias Ringwald 3531fbe4564SMatthias Ringwaldnodes = [node1, node2] 3541fbe4564SMatthias Ringwald 3551fbe4564SMatthias Ringwaldrun(nodes) 356