def test_abort_on_responder(self, sim_engine): sim_engine = sim_engine(**COMMON_SIM_ENGINE_ARGS) install_sf(sim_engine.motes, SchedulingFunctionTwoStepForAbortion) root = sim_engine.motes[0] mote = sim_engine.motes[1] root.sf.issue_add_request(mote.get_mac_addr()) u.run_until_asn(sim_engine, 101) # mote should receive the request logs = u.read_log_file([SimLog.LOG_SIXP_RX['type']]) assert len(logs) == 1 assert logs[0]['_mote_id'] == mote.id # we should have two sixp.tx logs: one is for the request, the other is # for response logs = u.read_log_file([SimLog.LOG_SIXP_TX['type']]) assert len(logs) == 2 assert logs[0]['_mote_id'] == root.id assert logs[1]['_mote_id'] == mote.id assert logs[1]['packet']['app']['msgType'] == d.SIXP_MSG_TYPE_RESPONSE # abort the transaction on the responder assert len(mote.sixp.transaction_table) == 1 assert len(mote.tsch.txQueue) == 1 # handler should receive the "aborted" event assert mote.sf.received_aborted_event is False mote.sixp.abort_transaction(initiator_mac_addr=root.get_mac_addr(), responder_mac_addr=mote.get_mac_addr()) assert mote.sf.received_aborted_event is True assert len(mote.sixp.transaction_table) == 0 assert len(mote.tsch.txQueue) == 0
def test_abort_on_initiator(self, sim_engine, fixture_msg_type): sim_engine = sim_engine(**COMMON_SIM_ENGINE_ARGS) install_sf(sim_engine.motes, SchedulingFunctionThreeStep) root = sim_engine.motes[0] mote = sim_engine.motes[1] assert len(root.sixp.transaction_table) == 0 assert len(root.tsch.txQueue) == 0 root.sf.issue_add_request(mote.get_mac_addr()) if fixture_msg_type == d.SIXP_MSG_TYPE_REQUEST: logs = u.read_log_file([SimLog.LOG_SIXP_TX['type']]) assert len(logs) == 1 assert logs[0]['_mote_id'] == root.id assert logs[0]['packet']['app']['msgType'] == ( d.SIXP_MSG_TYPE_REQUEST ) else: assert fixture_msg_type == d.SIXP_MSG_TYPE_CONFIRMATION u.run_until_asn(sim_engine, 102) # we should have two sixp.rx logs: one is for the request, the # other is for response logs = u.read_log_file([SimLog.LOG_SIXP_RX['type']]) assert len(logs) == 2 assert logs[0]['_mote_id'] == mote.id assert logs[1]['_mote_id'] == root.id # we should have three sixp.tx logs for each msg_type logs = u.read_log_file([SimLog.LOG_SIXP_TX['type']]) assert len(logs) == 3 assert logs[0]['_mote_id'] == root.id assert logs[1]['_mote_id'] == mote.id assert logs[2]['_mote_id'] == root.id assert logs[2]['packet']['app']['msgType'] == ( d.SIXP_MSG_TYPE_CONFIRMATION ) # abort the transaction on the initiator assert len(root.sixp.transaction_table) == 1 assert len(root.tsch.txQueue) == 1 root.sixp.abort_transaction( initiator_mac_addr = root.get_mac_addr(), responder_mac_addr = mote.get_mac_addr() ) assert len(root.sixp.transaction_table) == 0 assert len(root.tsch.txQueue) == 0
def test_app_upstream( sim_engine, app, fixture_dao_period ): # at least one app packet should be observed during the simulation sim_engine = sim_engine( { 'exec_numMotes' : 2, 'exec_numSlotframesPerRun' : 1000, 'sf_class' : 'SFNone', 'conn_class' : 'Linear', 'secjoin_enabled' : False, 'app' : app, 'app_pkPeriod' : 2, 'app_pkPeriodVar' : 0, 'app_pkLength' : 90, 'app_burstTimestamp' : 1, 'app_burstNumPackets' : 5, 'rpl_daoPeriod' : fixture_dao_period } ) # give the network time to form u.run_until_end(sim_engine) # the number of 'app.tx' is the same as the number of generated packets. logs = u.read_log_file(filter=['app.tx']) # five packets should be generated per application assert len(logs) > 0
def test_app_upstream(sim_engine, app): """Test Application Upstream Traffic - objective : test if app generates and sends packets as expected - precondition: form a 2-mote linear network - precondition: app sends 5 packets during the simulation time - action : run the simulation for 10 seconds - expectation : each application sends five packets """ sim_engine = sim_engine( { 'exec_numMotes': 2, 'exec_numSlotframesPerRun': 11, 'sf_class': 'SFNone', 'conn_class': 'Linear', 'tsch_probBcast_ebProb': 0, 'app': app, 'app_pkPeriod': 2, 'app_pkPeriodVar': 0, 'app_pkLength': 90, 'app_burstTimestamp': 1, 'app_burstNumPackets': 5, }, force_initial_routing_and_scheduling_state=True, ) # give the network time to form u.run_until_asn(sim_engine, 1010) # the number of 'app.tx' is the same as the number of generated packets. logs = u.read_log_file(filter=['app.tx']) # five packets should be generated per application assert 5 <= len(logs)
def test_dodag_parent(sim_engine, fixture_rank_value): sim_engine = sim_engine(diff_config={ 'exec_numMotes': 3, 'secjoin_enabled': False }) root = sim_engine.motes[0] parent = sim_engine.motes[1] child = sim_engine.motes[2] # get them connected to the network eb = root.tsch._create_EB() parent.tsch._action_receiveEB(eb) child.tsch._action_receiveEB(eb) dio = root.rpl._create_DIO() dio['mac'] = {'srcMac': root.get_mac_addr()} parent.rpl.action_receiveDIO(dio) dio = parent.rpl._create_DIO() dio['mac'] = {'srcMac': parent.get_mac_addr()} child.rpl.action_receiveDIO(dio) # make sure they are ready for the test assert parent.clear_to_send_EBs_DATA() is True assert child.clear_to_send_EBs_DATA() is True assert len(child.rpl.of.parents) == 1 assert child.rpl.of.parents[0]['mac_addr'] == parent.get_mac_addr() # create a DIO of 'parent' fot the test dio = parent.rpl._create_DIO() dio['mac'] = {'srcMac': parent.get_mac_addr()} if fixture_rank_value == 'smaller': dio['app']['rank'] = child.rpl.get_rank() - d.RPL_MINHOPRANKINCREASE elif fixture_rank_value == 'same': dio['app']['rank'] = child.rpl.get_rank() elif fixture_rank_value == 'larger': dio['app']['rank'] = child.rpl.get_rank() + d.RPL_MINHOPRANKINCREASE else: raise NotImplementedError() # process the global clock sim_engine.asn += 10 # give the dio to 'child' child.rpl.action_receiveDIO(dio) # see what happened if fixture_rank_value == 'smaller': # the parent should remain in the parent set of the child assert child.rpl.of.parents[0]['mac_addr'] == parent.get_mac_addr() else: # the parent should have been removed from the parent set of the child assert len(child.rpl.of.parents) == 0 # the child should send a DIO having INFINITE_RANK logs = u.read_log_file(filter=[SimLog.LOG_RPL_DIO_TX['type']], after_asn=sim_engine.getAsn() - 1) assert len(logs) == 1 assert logs[0]['packet']['app']['rank'] == 65535
def check_all_nodes_send_x(motes, x): senders = list( set([ l['_mote_id'] for l in u.read_log_file([SimLog.LOG_TSCH_TXDONE['type']]) if l['packet']['type'] == x ])) assert sorted(senders) == sorted([m.id for m in motes])
def rpl_check_all_motes_send_DAOs(motes): senders = list( set([ l['_mote_id'] for l in u.read_log_file([SimLog.LOG_TSCH_TXDONE['type']]) if l['packet']['type'] == 'DAO' ])) assert sorted(senders) == sorted([m.id for m in motes if m.id != 0])
def test_parent_switch(self, sim_engine): sim_engine = sim_engine( diff_config={ 'exec_numSlotframesPerRun': 4000, 'exec_numMotes': 3, 'app_pkPeriod': 0, 'sf_class': 'MSF', 'conn_class': 'Linear' }) # for quick access root = sim_engine.motes[0] mote_1 = sim_engine.motes[1] mote_2 = sim_engine.motes[2] asn_at_end_of_simulation = ( sim_engine.settings.tsch_slotframeLength * sim_engine.settings.exec_numSlotframesPerRun) # wait for hop_2 to get ready. this is when the network is ready to # operate. u.run_until_mote_is_ready_for_app(sim_engine, mote_2) assert sim_engine.getAsn() < asn_at_end_of_simulation # stop DIO (and EB) transmission sim_engine.settings.tsch_probBcast_ebProb = 0 # force mote_1 to switch its preferred parent old_parent = root new_parent = mote_2 # invalidate old_parent dio = old_parent.rpl._create_DIO() dio['mac'] = {'srcMac': old_parent.get_mac_addr()} dio['app']['rank'] = 65535 mote_1.rpl.action_receiveDIO(dio) # give a DIO from new_parent with a good rank dio = new_parent.rpl._create_DIO() dio['mac'] = {'srcMac': new_parent.get_mac_addr()} dio['app']['rank'] = 255 mote_1.rpl.action_receiveDIO(dio) # mote_1 should issue CLEAR to the old preferred parent and ADD to the # new one asn_start_testing = sim_engine.getAsn() u.run_until_end(sim_engine) logs = u.read_log_file(filter=[SimLog.LOG_SIXP_TX['type']], after_asn=asn_start_testing) def it_is_clear_request(packet): # return if the packet is a CLEAR request sent from mote_1 to # new_parent return ((packet['mac']['srcMac'] == mote_1.get_mac_addr()) and (packet['mac']['dstMac'] == old_parent.get_mac_addr()) and (packet['type'] == d.PKT_TYPE_SIXP) and (packet['app']['msgType'] == d.SIXP_MSG_TYPE_REQUEST) and (packet['app']['code'] == d.SIXP_CMD_CLEAR)) assert len([l for l in logs if it_is_clear_request(l['packet'])]) > 0
def test_no_fragment_loss(self, sim_engine, app_pkLength, fragmentation, fragmentation_ff_discard_vrb_entry_policy): """ Test it with a basic case in which there is no fragment loss - objective : test if packets are delivered to the root (destination) - precondition: form a 3-mote linear topology - action : send packets from each motes except for the root - expectation : the root receives the packets """ sim_engine = sim_engine( { 'exec_numMotes': 3, 'exec_numSlotframesPerRun': 10000, 'sf_class': 'SFNone', 'conn_class': 'Linear', 'app_pkPeriod': 5, 'app_pkPeriodVar': 0, 'tsch_probBcast_ebProb': 0, 'sixlowpan_reassembly_buffers_num': 2, 'app_pkLength': app_pkLength, 'fragmentation': fragmentation, 'fragmentation_ff_discard_vrb_entry_policy': fragmentation_ff_discard_vrb_entry_policy }, force_initial_routing_and_scheduling_state=True, ) # run the simulation for 1000 timeslots (10 seconds) u.run_until_asn(sim_engine, 1000) # the root should receive packet from both of the two motes during 10 seconds. # - Packets are generated at every 5 seconds # - The first packet is generated within the first 5 seconds # - the minimum e2e latency of one fragment from the leaf is about 2 sec # - a packet is divided into two fragments at most in this test # - two fragments from the leaf need at least 4 sec to reach the root senders = [] for log in u.read_log_file(filter=['app.rx']): srcIp = log['packet']['net']['srcIp'] if srcIp not in senders: senders.append(srcIp) if len(senders) == 2: # root should receive packets from both of the two motes # if it reaches here, it means success return assert False
def test_fragmentation_and_reassembly( self, sim_engine, app_pkLength, fragmentation, fragmentation_ff_discard_vrb_entry_policy): """Test fragmentation and reassembly themselves (w/o forwarding) - objective : test if a packet is divided to the expected number - precondition: form a 2-mote linear topology - precondition: app scheduled is done by hand (app_pkPeriod=0) - action : send a packet to the root - expectation : the number of fragments is the expected value """ sim_engine = sim_engine( diff_config={ 'exec_numMotes': 2, 'exec_numSlotframesPerRun': 20, 'sf_class': 'SFNone', 'conn_class': 'Linear', 'app_pkPeriod': 0, 'app_pkPeriodVar': 0, 'tsch_probBcast_ebProb': 0, 'tsch_max_payload_len': self.TSCH_MAX_PAYLOAD, 'tsch_tx_queue_size': self.TSCH_TX_QUEUE_SIZE, 'app_pkLength': app_pkLength, 'fragmentation': fragmentation, 'fragmentation_ff_discard_vrb_entry_policy': fragmentation_ff_discard_vrb_entry_policy }, force_initial_routing_and_scheduling_state=True) # send a packet from the leaf mote leaf = sim_engine.motes[1] # _send_a_single_packet() causes leaf to send a packet leaf.app._send_a_single_packet() # it's ready to test; run the simulation for long enough time u.run_until_asn(sim_engine, 1500) # check if fragment receptions happen the expected times logs = u.read_log_file(filter=['sixlowpan.pkt.rx']) assert (len([ log for log in logs if log['packet']['type'] == d.PKT_TYPE_FRAG ]) == math.ceil(float(app_pkLength) / self.TSCH_MAX_PAYLOAD))
def test_network_advertisement(sim_engine, fixture_adv_frame): sim_engine = sim_engine( diff_config={ 'exec_numMotes': 1, 'exec_numSlotframesPerRun': 100, # with 101 slots per slotframe, that's 10,100 slot total }) u.run_until_asn(sim_engine, 10000) logs = u.read_log_file(filter=['tsch.txdone']) # root should send more than one EB in a default simulation run assert len([l for l in logs if l['packet']['type'] == fixture_adv_frame]) > 0
def test_compute_battery_lifetime(sim_engine): # reproduce Issue #360 sim_engine = sim_engine( diff_config={ 'exec_numSlotframesPerRun': 1, 'exec_numMotes': 2, 'phy_numChans': 1, 'radio_stats_log_period_s': 60 }) root = sim_engine.motes[0] mote = sim_engine.motes[1] # set 0% of PDR to their links channel = d.TSCH_HOPPING_SEQUENCE[0] sim_engine.connectivity.matrix.set_pdr_both_directions(mote_id_1=root.id, mote_id_2=mote.id, channel=channel, pdr=0) # make up a radio activity of mote, which is supposed to consume # energy mote.radio.stats['tx_data'] = 100 # force mote to log radio stats (at ASN 0) mote.radio._log_stats() # stop the simulator at the last ASN u.run_until_asn(sim_engine, 101) # make mote synched mote.tsch.setIsSync(True) # confirm we have relevant logs logs = u.read_log_file(['radio.stats', 'tsch.synced']) logs = [log for log in logs if log['_mote_id'] == mote.id] assert len(logs) == 2 logs[0]['_type'] == 'radio.status' logs[0]['_asn'] == 0 logs[1]['_type'] == 'tsch.synced' logs[1]['_asn'] == 101 # run compute_kpis, which should end without raising an exception output = run_compute_kpis_py() # test done assert True
def test_dis_timer(sim_engine): sim_engine = sim_engine( diff_config={ 'exec_numMotes': 2, 'rpl_extensions': ['dis_unicast'], 'secjoin_enabled': False, 'app_pkPeriod': 0, 'tsch_keep_alive_interval': 0, 'exec_numSlotframesPerRun': rpl.Rpl.DEFAULT_DIS_INTERVAL_SECONDS + 1 }) root = sim_engine.motes[0] mote = sim_engine.motes[1] # get mote synchronized eb = root.tsch._create_EB() eb_dummy = { 'type': d.PKT_TYPE_EB, 'mac': { 'srcMac': '00-00-00-AA-AA-AA', # dummy 'dstMac': d.BROADCAST_ADDRESS, # broadcast 'join_metric': 1000 } } mote.tsch._action_receiveEB(eb) mote.tsch._action_receiveEB(eb_dummy) # disable root's communication capability by deleting the minimal shared # cell root.tsch.delete_minimal_cell() # run the simulation u.run_until_end(sim_engine) # collect DIS logs logs = u.read_log_file(filter=[SimLog.LOG_RPL_DIS_TX['type']]) # check DIS packets assert len(logs) > 1 # the first DIS should be sent to the link-local address of root because # 'dis_unicast' is specified logs[0]['packet']['net']['dstIp'] = root.get_ipv6_link_local_addr() # the rest should be sent to the multicast address despite of 'dis_unicast' for i in range(1, len(logs)): assert logs[i]['packet']['net'][ 'dstIp'] == d.IPV6_ALL_RPL_NODES_ADDRESS
def test_lockon(sim_engine): sim_engine = sim_engine( diff_config={ 'exec_numMotes': 2, 'exec_numSlotframesPerRun': 1, 'conn_class': 'Linear', 'app_pkPeriod': 0, 'secjoin_enabled': False, 'sf_class': 'SFNone', 'tsch_probBcast_ebProb': 0, 'rpl_daoPeriod': 0 }) # short-hands root = sim_engine.motes[0] hop_1 = sim_engine.motes[1] # force hop_1 to join the network eb = root.tsch._create_EB() hop_1.tsch._action_receiveEB(eb) dio = root.rpl._create_DIO() dio['mac'] = {'srcMac': root.get_mac_addr()} hop_1.rpl.action_receiveDIO(dio) # let hop_1 send an application packet hop_1.app._send_a_single_packet() # force random.random() to return 1, which will cause any frame not to be # received by anyone _random = random.random def return_one(self): return float(1) random.random = types.MethodType(return_one, random) # run the simulation u.run_until_end(sim_engine) # put the original random() back to random random.random = _random # root shouldn't lock on the frame hop_1 sent since root is not expected to # receive even the preamble of the packet. logs = u.read_log_file([SimLog.LOG_PROP_DROP_LOCKON['type']]) assert len(logs) == 0
def test_advertising_link(sim_engine): # EB should be sent only on links having ADVERTISING on sim_engine = sim_engine( diff_config={ 'exec_numMotes': 1, 'sf_class': 'SFNone', 'tsch_slotframeLength': 2, 'tsch_probBcast_ebProb': 1.0, }) root = sim_engine.motes[0] # disable DIO so that there will be no traffic except for EBs. root.rpl.trickle_timer.stop() slotframe = root.tsch.get_slotframe(0) # make sure we have one cell assert len(slotframe.get_busy_slots()) == 1 cells = slotframe.get_cells_by_mac_addr(None) assert len(cells) == 1 # the link-type of the minimal cell should be ADVERTISING minimal_cell = cells[0] assert minimal_cell.slot_offset == 0 assert minimal_cell.channel_offset == 0 assert (sorted(minimal_cell.options) == sorted( [d.CELLOPTION_RX, d.CELLOPTION_TX, d.CELLOPTION_SHARED])) assert minimal_cell.mac_addr == None assert minimal_cell.link_type == d.LINKTYPE_ADVERTISING # add one cell whose link-type is NORMAL at slotoffset 1 normal_cell = tsch.Cell(slot_offset=1, channel_offset=1, options=minimal_cell.options, mac_addr=None, is_advertising=False) assert normal_cell.link_type == d.LINKTYPE_NORMAL slotframe.add(normal_cell) # run the simulation; we should have EBs only on the minimal cells u.run_until_end(sim_engine) tx_logs = u.read_log_file(filter=[SimLog.LOG_TSCH_TXDONE['type']]) assert len(tx_logs) > 0 for tx_log in tx_logs: assert tx_log['slot_offset'] != normal_cell.slot_offset
def test_retransmission_count(sim_engine): sim_engine = sim_engine(diff_config={ 'exec_numSlotframesPerRun': 10, 'exec_numMotes': 2, 'app_pkPeriod': 0, 'rpl_daoPeriod': 0, 'tsch_probBcast_ebProb': 0, 'secjoin_enabled': False, 'tsch_keep_alive_interval': 0, 'conn_class': 'Linear' }, force_initial_routing_and_scheduling_state=True) # short-hands root = sim_engine.motes[0] hop1 = sim_engine.motes[1] connectivity_matrix = sim_engine.connectivity.connectivity_matrix # stop DIO timer root.rpl.trickle_timer.stop() hop1.rpl.trickle_timer.stop() # set 0% of PDR to the link between the two motes for channel in range(sim_engine.settings.phy_numChans): connectivity_matrix[root.id][hop1.id][channel]['pdr'] = 0 connectivity_matrix[hop1.id][root.id][channel]['pdr'] = 0 # make hop1 send an application packet hop1.app._send_a_single_packet() # run the simulation u.run_until_end(sim_engine) # check the log tx_logs = u.read_log_file([SimLog.LOG_TSCH_TXDONE['type']]) # hop1 should send out the frame six times: 1 for the initial transmission # and 5 for retransmissions assert len(tx_logs) == 1 + d.TSCH_MAXTXRETRIES for tx_log in tx_logs: assert tx_log['packet']['type'] == d.PKT_TYPE_DATA assert hop1.is_my_ipv6_addr(tx_log['packet']['net']['srcIp']) assert tx_log['packet']['app']['appcounter'] == 0
def test_no_txrx_cell_allocation_to_parent(self, sim_engine): sim_engine = sim_engine(diff_config={ 'exec_numMotes': 2, 'sf_class': 'MSF', 'conn_class': 'Linear', }) u.run_until_end(sim_engine) logs = [ log for log in u.read_log_file( filter=[SimLog.LOG_TSCH_ADD_CELL['type']]) if ((log['_mote_id'] == sim_engine.motes[1].id) and ( sorted(log['cellOptions']) == sorted( [d.CELLOPTION_TX, d.CELLOPTION_RX, d.CELLOPTION_SHARED])) and (log['neighbor'] is not None)) ] # mote_1 shouldn't schedule one TX/RX/SHARED cell to its parent # (mote_0) assert len(logs) == 0
def test_initial_dedicated_cell_allocation_to_parent(self, sim_engine): sim_engine = sim_engine( diff_config={ 'exec_numMotes': 2, 'sf_class': 'MSF', 'conn_class': 'Linear', 'app_pkPeriod': 0 }) u.run_until_end(sim_engine) logs = [ log for log in u.read_log_file( filter=[SimLog.LOG_TSCH_ADD_CELL['type']]) if ((log['_mote_id'] == sim_engine.motes[1].id) and ( sorted(log['cellOptions']) == sorted([d.CELLOPTION_TX])) and ( log['neighbor'] is not None)) ] # mote_1 should schedule one dedicated cell to its parent # (mote_0) assert len(logs) == 1
def test_no_available_cell(self, sim_engine, function_under_test): sim_engine = sim_engine( diff_config={ 'exec_numSlotframesPerRun': 1000, 'exec_numMotes': 2, 'app_pkPeriod': 0, 'sf_class': 'MSF', 'conn_class': 'Linear' }) # for quick access root = sim_engine.motes[0] hop_1 = sim_engine.motes[1] asn_at_end_of_simulation = ( sim_engine.settings.tsch_slotframeLength * sim_engine.settings.exec_numSlotframesPerRun) # wait for hop_1 to get ready. u.run_until_mote_is_ready_for_app(sim_engine, hop_1) assert sim_engine.getAsn() < asn_at_end_of_simulation # fill up the hop_1's schedule channel_offset = 0 cell_options = [d.CELLOPTION_TX] used_slots = hop_1.tsch.get_busy_slots(hop_1.sf.SLOTFRAME_HANDLE) for _slot in range(sim_engine.settings.tsch_slotframeLength): if _slot in used_slots: continue else: hop_1.tsch.addCell(slotOffset=_slot, channelOffset=channel_offset, neighbor=root.get_mac_addr(), cellOptions=cell_options, slotframe_handle=hop_1.sf.SLOTFRAME_HANDLE) assert (len(hop_1.tsch.get_busy_slots(hop_1.sf.SLOTFRAME_HANDLE)) == sim_engine.settings.tsch_slotframeLength) # put dummy stats so that scheduling adaptation can be triggered hop_1.sf.num_cells_elapsed = 100 hop_1.sf.num_cells_used = hop_1.sf.num_cells_elapsed # trigger scheduling adaptation if function_under_test == 'adapt_to_traffic': hop_1.sf._adapt_to_traffic(root.id) elif function_under_test == 'relocate': relocating_cell = filter( lambda cell: cell.options == [d.CELLOPTION_TX], hop_1.tsch.get_cells(root.get_mac_addr(), hop_1.sf.SLOTFRAME_HANDLE))[0] hop_1.sf._request_relocating_cells(neighbor=root.get_mac_addr(), cell_options=[d.CELLOPTION_TX], num_relocating_cells=1, cell_list=[relocating_cell]) else: # not implemented assert False # make sure the log is written into the file SimEngine.SimLog.SimLog().flush() # MSF should output a "schedule-full" error in the log file logs = u.read_log_file( filter=[SimLog.LOG_MSF_ERROR_SCHEDULE_FULL['type']], after_asn=sim_engine.getAsn() - 1) assert len(logs) == 1 assert logs[0]['_mote_id'] == hop_1.id
def test_tsch_clock(sim_engine, with_keep_alive): diff_config = { 'exec_numMotes' : 3, 'app_pkPeriod' : 0, 'app_pkPeriodVar' : 0, 'tsch_probBcast_ebProb' : 0, 'rpl_daoPeriod' : 0, 'exec_numSlotframesPerRun': 100, 'conn_class' : 'Linear' } if with_keep_alive is True: diff_config['tsch_keep_alive_interval'] = 10 else: diff_config['tsch_keep_alive_interval'] = 0 diff_config['exec_randomSeed'] = 7263092949079992026 sim_engine = sim_engine( diff_config = diff_config, force_initial_routing_and_scheduling_state = True ) log_type_clock_diff = 'clock_diff' sim_log = SimLog.SimLog() # static values macTsRxWait = 0.00222 # 2,220 usec defined for 2.4 GHz by IEEE 802.15.4-2015 # shorthands root = sim_engine.motes[0] hop_1 = sim_engine.motes[1] hop_2 = sim_engine.motes[2] slot_duration = sim_engine.settings.tsch_slotDuration slotframe_length = sim_engine.settings.tsch_slotframeLength max_drift = sim_engine.settings.tsch_clock_max_drift_ppm clock_interval = 1.0 / sim_engine.settings.tsch_clock_frequency guard_time = (macTsRxWait / 2) - (2 * clock_interval) def _check_and_log_clock_drift(): # without keep-alive, difference between the two clocks is # getting bigger and bigger. but, it should be within # -max_drift*2 and +max_drift*2 with offset in the range # between 0 and clock_interval diff_1 = hop_1.tsch.clock.get_drift() - root.tsch.clock.get_drift() diff_2 = hop_2.tsch.clock.get_drift() - hop_1.tsch.clock.get_drift() elapsed_time = sim_engine.getAsn() * slot_duration lower_bound_drift = ( elapsed_time * (-1 * max_drift * 2) + 0 ) upper_bound_drift = ( elapsed_time * (+1 * max_drift * 2) + clock_interval ) assert lower_bound_drift < diff_1 assert diff_1 < upper_bound_drift assert lower_bound_drift < diff_2 assert diff_2 < upper_bound_drift if with_keep_alive: assert abs(diff_1) < guard_time assert abs(diff_2) < guard_time # custom log for mote_id, diff in zip([hop_1.id, hop_2.id], [diff_1, diff_2]): sim_log.log( {'type': log_type_clock_diff, 'keys': ['_mote_id', 'value']}, {'_mote_id': mote_id, 'value': diff} ) _schedule_clock_drift_checking_and_logging() def _schedule_clock_drift_checking_and_logging(): sim_engine.scheduleAtAsn( asn = sim_engine.getAsn() + (1.0 / slot_duration), cb = _check_and_log_clock_drift, uniqueTag = 'check_and_log_clock_drift', intraSlotOrder = d.INTRASLOTORDER_ADMINTASKS ) _schedule_clock_drift_checking_and_logging() u.run_until_end(sim_engine) keep_alive_logs = [ log for log in u.read_log_file([SimLog.LOG_TSCH_TXDONE['type']]) if ( log['packet']['type'] == d.PKT_TYPE_KEEP_ALIVE ) ] if with_keep_alive is True: assert len(keep_alive_logs) > 0 else: assert len(keep_alive_logs) == 0
def test_retry(self, sim_engine): sim_engine = sim_engine( diff_config={ 'exec_numMotes': 2, 'sf_class': 'MSF', 'conn_class': 'Linear', 'secjoin_enabled': False, 'app_pkPeriod': 0, 'rpl_daoPeriod': 0, 'rpl_extensions': [], 'tsch_keep_alive_interval': 0, 'tsch_probBcast_ebProb': 0 }) root = sim_engine.motes[0] mote = sim_engine.motes[1] # make root not to respond to a 6P request root.sf.recv_request = SchedulingFunctionSFNone(root).recv_request # get the mote joined eb = root.tsch._create_EB() eb_dummy = { 'type': d.PKT_TYPE_EB, 'mac': { 'srcMac': '00-00-00-AA-AA-AA', # dummy 'dstMac': d.BROADCAST_ADDRESS, # broadcast 'join_metric': 1000 } } mote.tsch._action_receiveEB(eb) mote.tsch._action_receiveEB(eb_dummy) assert mote.tsch.isSync dio = root.rpl._create_DIO() dio['mac'] = { 'srcMac': root.get_mac_addr(), 'dstMac': d.BROADCAST_ADDRESS } # need to put the DIO to 6LoWPAN layer so that mote can learn # root's MAC address and schedule the autonomous shared cell. mote.sixlowpan.recvPacket(dio) assert mote.rpl.dodagId is not None # stop DIO timer to make this test simple root.rpl.trickle_timer.stop() mote.rpl.trickle_timer.stop() u.run_until_end(sim_engine) # we should see three 6P timeout logs logs = u.read_log_file( filter=[SimLog.LOG_SIXP_TRANSACTION_TIMEOUT['type']]) assert (len([log for log in logs if log['_mote_id'] == mote.id ]) == SchedulingFunctionMSF.MAX_RETRY + 1) # mote should lose its parent logs = u.read_log_file(filter=[SimLog.LOG_RPL_CHURN['type']]) assert len(logs) == 2 assert logs[0]['preferredParent'] == root.get_mac_addr() assert logs[1]['preferredParent'] is None
def test_tx_cell_selection(sim_engine, packet_type, destination, expected_cellOptions): # cell selection rules: # # - [CELLOPTION_TX] should be used for a unicast packet to a neighbor to whom a sender # has a dedicated TX cell # - [CELLOPTION_TX,CELLOPTION_RX,CELLOPTION_SHARED] should be used otherwise # # With force_initial_routing_and_scheduling_state True, each mote has one # shared (TX/RX/SHARED) cell and one TX cell to its parent. sim_engine = sim_engine(diff_config={ 'exec_numMotes': 3, 'sf_class': 'SFNone', 'conn_class': 'Linear', 'app_pkPeriod': 0, 'app_pkPeriodVar': 0, 'tsch_probBcast_ebProb': 0, }, force_initial_routing_and_scheduling_state=True) parent = sim_engine.motes[0] mote = sim_engine.motes[1] child = sim_engine.motes[2] packet = { 'type': packet_type, 'app': { 'rank': mote.rpl.get_rank(), }, 'net': { 'srcIp': mote.get_ipv6_link_local_addr() }, } # With packet_type=d.PKT_TYPE_DATA, we'll test if the right cell is chosen # to send a fragment. Set 180 to packet_length so that the packet is # divided into two fragments. if packet_type == d.PKT_TYPE_DATA: packet['net']['packet_length'] = 180 # set destination IPv6 address and and a corresponding neighbor entry if destination == 'broadcast': packet['net']['dstIp'] = d.IPV6_ALL_RPL_NODES_ADDRESS elif destination == 'parent': packet['net']['dstIp'] = parent.get_ipv6_link_local_addr() mote.sixlowpan.on_link_neighbor_list.append(parent.get_mac_addr()) elif destination == 'child': packet['net']['dstIp'] = child.get_ipv6_link_local_addr() mote.sixlowpan.on_link_neighbor_list.append(child.get_mac_addr()) # send a packet to the target destination mote.sixlowpan.sendPacket(packet) # wait for long enough for the packet to be sent u.run_until_asn(sim_engine, 1000) # see logs logs = [] # as mentioned above, we'll see logs for fragment packets when # packet_type=d.PKT_TYPE_DATA if packet_type == d.PKT_TYPE_DATA: test_packet_type = d.PKT_TYPE_FRAG else: test_packet_type = packet_type for log in u.read_log_file(filter=['tsch.txdone']): if ((mote.is_my_mac_addr(log['packet']['mac']['srcMac'])) and (log['packet']['type'] == test_packet_type)): logs.append(log) # transmission could be more than one due to retransmission assert (len(logs) > 0) for log in logs: slotframe = mote.tsch.slotframes[0] cell = slotframe.get_cells_at_asn(log['_asn'])[0] assert cell.options == expected_cellOptions
def test_avg_hops(sim_engine, fragmentation, app_pkLength, pkt_loss_mode): sim_engine = sim_engine( diff_config={ 'exec_numSlotframesPerRun': 40, 'exec_numMotes': 10, 'fragmentation': fragmentation, 'app': 'AppPeriodic', 'app_pkPeriod': 0, 'app_pkLength': app_pkLength, 'tsch_probBcast_ebProb': 0, 'rpl_daoPeriod': 0, 'conn_class': 'Linear' }, force_initial_routing_and_scheduling_state=True, ) # in this test, the leaf sends two application packets. when pkt_loss_mode # is 'with_pkt_loss', the second application packet will be lost at the # root. # shorthands root = sim_engine.motes[0] leaf = sim_engine.motes[-1] sim_settings = SimSettings.SimSettings() # make the app send an application packet leaf.app._send_a_single_packet() # wait for some time u.run_until_asn(sim_engine, 2020) # the root should receive the first application packet logs = u.read_log_file([SimLog.LOG_APP_RX['type']]) assert len(logs) == 1 assert logs[0]['_mote_id'] == root.id assert logs[0]['packet']['net']['srcIp'] == leaf.get_ipv6_global_addr() assert logs[0]['packet']['net']['dstIp'] == root.get_ipv6_global_addr() assert logs[0]['packet']['type'] == d.PKT_TYPE_DATA # make the root not receive at 6LoWPAN layer anything if pkt_loss_mode is # 'with_pkt_loss' if pkt_loss_mode == 'with_pkt_loss': assert root.dagRoot is True def recvPacket(self, packet): # do nothing; ignore the incoming packet pass root.sixlowpan.recvPacket = types.MethodType(recvPacket, root.sixlowpan) elif pkt_loss_mode == 'without_pkt_loss': # do nothing pass else: raise NotImplemented() # make the app send another application packet leaf.app._send_a_single_packet() # run the simulator until it ends u.run_until_end(sim_engine) # confirm the leaf sent two application packets logs = u.read_log_file([SimLog.LOG_APP_TX['type']]) assert len(logs) == 2 for i in range(2): assert logs[i]['_mote_id'] == leaf.id assert logs[i]['packet']['net']['srcIp'] == leaf.get_ipv6_global_addr() assert logs[i]['packet']['net']['dstIp'] == root.get_ipv6_global_addr() assert logs[i]['packet']['type'] == d.PKT_TYPE_DATA # run compute_kpis.py against the log file compute_kpis_path = os.path.join(os.path.dirname(__file__), '../bin', 'compute_kpis.py') output = subprocess.check_output('{0} \'{1}\''.format( 'python', compute_kpis_path), shell=True).split('\n') # remove blank lines output = [line for line in output if not re.match(r'^\s*$', line)] # confirm if compute_kpis.py referred the right log file # the first line of output has the log directory name assert (re.search(os.path.basename(sim_settings.getOutputFile()), output[0]) is not None) # convert the body of the output, which is a JSON string, to an object json_string = '\n'.join(output[1:-1]) kpis = json.loads(json_string) # the avg_hops should be the same number as leaf.id since we use a linear # topology here. assert kpis['null'][str(leaf.id)]['avg_hops'] == leaf.id
def test_pending_bit(sim_engine, fixture_pending_bit_enabled): sim_engine = sim_engine( diff_config={ 'exec_numMotes': 2, 'exec_numSlotframesPerRun': 3, 'sf_class': 'SFNone', 'secjoin_enabled': False, 'app_pkPeriod': 0, 'rpl_daoPeriod': 0, 'tsch_keep_alive_interval': 0, 'conn_class': 'Linear' }) # short-hands root = sim_engine.motes[0] mote = sim_engine.motes[1] # get the mote joined the network eb = root.tsch._create_EB() mote.tsch._action_receiveEB(eb) dio = root.rpl._create_DIO() dio['mac'] = {'srcMac': root.get_mac_addr()} mote.rpl.action_receiveDIO(dio) # activate the pending bit feature if necessary if fixture_pending_bit_enabled: root.tsch.enable_pending_bit() mote.tsch.enable_pending_bit() # add a shared cell on slot_offset 1 and channel offset 1 root.tsch.addCell(1, 1, None, [d.CELLOPTION_RX]) mote.tsch.addCell(1, 1, root.get_mac_addr(), [d.CELLOPTION_TX, d.CELLOPTION_SHARED]) # put two DATA packets and one DIO between them to the TX queue of the mote mote.tsch.txQueue = [] assert len(mote.tsch.txQueue) == 0 mote.app._send_packet(dstIp=root.get_ipv6_global_addr(), packet_length=10) mote.rpl._send_DIO() mote.app._send_packet(dstIp=root.get_ipv6_global_addr(), packet_length=10) assert len(mote.tsch.txQueue) == 3 u.run_until_end(sim_engine) # check logs logs = [ log for log in u.read_log_file(filter=[SimLog.LOG_TSCH_TXDONE['type']]) if log['packet']['type'] == d.PKT_TYPE_DATA ] assert len(logs) == 2 if fixture_pending_bit_enabled: # the second DATA packet is sent on the same channel as the first one # by the pending bit feature assert logs[0]['slot_offset'] == 1 assert logs[0]['channel_offset'] == 1 assert logs[1]['slot_offset'] == None assert logs[1]['channel_offset'] == None assert logs[0]['channel'] == logs[1]['channel'] assert logs[1]['_asn'] - logs[0]['_asn'] == 1 else: # two DATA packets should be sent on the shared cell in different slot # frames assert logs[0]['slot_offset'] == 1 assert logs[0]['channel_offset'] == 1 assert logs[1]['slot_offset'] == 1 assert logs[1]['channel_offset'] == 1 assert logs[0]['channel'] != logs[1]['channel'] assert ((logs[1]['_asn'] - logs[0]['_asn']) == sim_engine.settings.tsch_slotframeLength)
def test_retransmission_backoff_algorithm(sim_engine, cell_type): sim_engine = sim_engine( diff_config={ 'exec_numSlotframesPerRun': 10000, 'exec_numMotes': 2, 'app_pkPeriod': 0, 'secjoin_enabled': False, 'tsch_keep_alive_interval': 0 }) sim_log = SimLog.SimLog() # filter logs to make this test faster; we need only SimLog.LOG_TSCH_TXDONE sim_log.set_log_filters([SimLog.LOG_TSCH_TXDONE['type']]) # for quick access root = sim_engine.motes[0] hop_1 = sim_engine.motes[1] slotframe_length = sim_engine.settings.tsch_slotframeLength # increase TSCH_MAXTXRETRIES so that we can have enough retransmission # samples to validate d.TSCH_MAXTXRETRIES = 100 #== test setup == u.run_until_everyone_joined(sim_engine) # make hop_1 ready to send an application packet assert hop_1.rpl.dodagId is None dio = root.rpl._create_DIO() dio['mac'] = {'srcMac': root.get_mac_addr()} hop_1.rpl.action_receiveDIO(dio) assert hop_1.rpl.dodagId is not None # make root ignore all the incoming frame for this test def ignoreRx(self, packet, channel): self.waitingFor = None isACKed = False return isACKed root.tsch.rxDone = types.MethodType(ignoreRx, root.tsch) if cell_type == 'dedicated-cell': # allocate one TX=1/RX=1/SHARED=1 cell to the motes as their dedicate cell. cellOptions = [d.CELLOPTION_TX, d.CELLOPTION_RX, d.CELLOPTION_SHARED] assert len(root.tsch.get_cells(hop_1.get_mac_addr())) == 0 root.tsch.addCell(1, 1, hop_1.get_mac_addr(), cellOptions) assert len(root.tsch.get_cells(hop_1.get_mac_addr())) == 1 assert len(hop_1.tsch.get_cells(root.get_mac_addr())) == 0 hop_1.tsch.addCell(1, 1, root.get_mac_addr(), cellOptions) assert len(hop_1.tsch.get_cells(root.get_mac_addr())) == 1 # make sure hop_1 send a application packet when the simulator starts hop_1.tsch.txQueue = [] hop_1.app._send_a_single_packet() assert len(hop_1.tsch.txQueue) == 1 #== start test == asn_starting_test = sim_engine.getAsn() # run the simulator until hop_1 drops the packet or the simulation ends def drop_packet(self, packet, reason): if packet['type'] == d.PKT_TYPE_DATA: # pause the simulator sim_engine.pauseAtAsn(sim_engine.getAsn() + 1) hop_1.drop_packet = types.MethodType(drop_packet, hop_1) u.run_until_end(sim_engine) # confirm # - hop_1 sent the application packet to the root # - retransmission backoff worked logs = u.read_log_file(filter=[SimLog.LOG_TSCH_TXDONE['type']], after_asn=asn_starting_test) app_data_tx_logs = [] for log in logs: if ((log['_mote_id'] == hop_1.id) and (root.is_my_mac_addr(log['packet']['mac']['dstMac'])) and (log['packet']['type'] == d.PKT_TYPE_DATA)): app_data_tx_logs.append(log) assert len(app_data_tx_logs) == 1 + d.TSCH_MAXTXRETRIES # all transmission should have happened only on the dedicated cell if it's # available (it shouldn't transmit a unicast frame to the root on the # minimal (shared) cell. if cell_type == 'dedicated-cell': _cell = hop_1.tsch.get_cells(root.get_mac_addr())[0] expected_cell_offset = _cell.slot_offset elif cell_type == 'shared-cell': expected_cell_offset = 0 # the minimal (shared) cell else: raise NotImplementedError() for log in app_data_tx_logs: slot_offset = log['_asn'] % slotframe_length assert slot_offset == expected_cell_offset # retransmission should be performed after backoff wait; we should see gaps # between consecutive retransmissions. If all the gaps are 101 slots, that # is, one slotframe, this means there was no backoff wait between # transmissions. timestamps = [log['_asn'] for log in app_data_tx_logs] diffs = map(lambda x: x[1] - x[0], zip(timestamps[:-1], timestamps[1:])) assert len([diff for diff in diffs if diff != slotframe_length]) > 0
def test_propagation(sim_engine, fixture_propagation_test_type): PERFECT_PDR = 1.0 RSSI_VALUES = { 'perfect_rssi': -10, 'poor_rssi': -90, 'worst_rssi': -97, # the worst in rssi_pdr_table of Connectivity.py 'invalid_rssi': -1000 } num_motes = 2 num_frames = 1000 sim_engine = sim_engine( diff_config={ 'exec_numSlotframesPerRun': num_frames * (d.TSCH_MAXTXRETRIES + 1), 'exec_numMotes': num_motes, 'secjoin_enabled': False, 'app_pkPeriod': 0, 'rpl_of': 'OFNone', 'rpl_daoPeriod': 0, 'rpl_extensions': [], 'sf_class': 'SFNone', 'tsch_slotframeLength': 2, 'tsch_probBcast_ebProb': 0, 'tsch_keep_alive_interval': 0, 'tsch_tx_queue_size': num_frames, 'conn_class': 'Linear', # this is intentional 'phy_numChans': 1 }) root = sim_engine.motes[0] mote = sim_engine.motes[1] # aliases dst = root src = mote class TestConnectivityMatrixK7(ConnectivityMatrixK7): def _additional_initialization(self): # set up the connectivity matrix channel = d.TSCH_HOPPING_SEQUENCE[0] self.set_pdr(src.id, dst.id, channel, PERFECT_PDR) self.set_rssi(src.id, dst.id, channel, RSSI_VALUES[fixture_propagation_test_type]) # dump the connectivity matrix print 'The Connectivity Matrix ("1.0" means PDR of 100%):' self.dump() # replace the 'Linear' conn_class with the test purpose # conn_class, TestConnectivityMatrixK7 if fixture_propagation_test_type != 'test_setup': sim_engine.connectivity.matrix = TestConnectivityMatrixK7( sim_engine.connectivity) # add a dedicated TX cell in order to avoid backoff wait slot_offset = 1 channel_offset = 0 dst.tsch.addCell(slot_offset, channel_offset, src.get_mac_addr(), [d.CELLOPTION_RX]) src.tsch.addCell(slot_offset, channel_offset, dst.get_mac_addr(), [d.CELLOPTION_TX]) # get mote synchronized eb = root.tsch._create_EB() mote.tsch._action_receiveEB(eb) # put a fake EB to mote so that it can get synchronized # immediately eb_dummy = { 'type': d.PKT_TYPE_EB, 'mac': { 'srcMac': '00-00-00-AA-AA-AA', # dummy 'dstMac': d.BROADCAST_ADDRESS, # broadcast 'join_metric': 1000 } } mote.tsch._action_receiveEB(eb_dummy) # disabled the trickle timer root.rpl.trickle_timer.stop() mote.rpl.trickle_timer.stop() # [test types] # # test_setup: verify if we can set up a test environment # correctly, where there is no background traffic # # perfect_rssi/poor_rssi: all the transmission should succeed # since the links between the two motes have a PDR of 100% # regardless of their RSSI values if fixture_propagation_test_type != 'test_setup': # put frames to the TX queue of the source; use the keep-alive # frame as the test packet for seqno in range(num_frames): packet = { 'type': d.PKT_TYPE_KEEP_ALIVE, 'mac': { 'srcMac': src.get_mac_addr(), 'dstMac': dst.get_mac_addr() }, 'app': { 'seq': seqno } # for debugging purpose } src.tsch.enqueue(packet) u.run_until_end(sim_engine) num_transmissions = len(u.read_log_file([SimLog.LOG_TSCH_TXDONE['type']])) if fixture_propagation_test_type == 'test_setup': # we shouldn't see any transmission assert num_transmissions == 0 else: # num_transmissions contains the number of retransmissions if # any. in other words, num_transmissions should be equal to # num_frames when no frame is dropped assert num_transmissions == num_frames
def test_issue_188(self, sim_engine): """This test reproduces Issue #188 An exception was raised when a mote receives a request which has a different SeqNum from one in a valid transaction it still has. """ sim_engine = sim_engine( diff_config={ 'exec_numSlotframesPerRun': 10, 'exec_numMotes': 2, 'sf_class': 'SFNone', 'conn_class': 'Linear', 'tsch_probBcast_ebProb': 0 }, force_initial_routing_and_scheduling_state=True) # for quick access root = sim_engine.motes[0] hop_1 = sim_engine.motes[1] # step-0: set 1 (a non-zero value) to local SeqNum both of root and # hop_1 root.sixp._get_seqnum(hop_1.get_mac_addr()) # create a SeqNum entry root.sixp.increment_seqnum( hop_1.get_mac_addr()) # increment the SeqNum hop_1.sixp._get_seqnum(root.get_mac_addr()) # create a SeqNum entry hop_1.sixp.increment_seqnum( root.get_mac_addr()) # increment the SeqNum # step-1: let hop_1 issue an ADD request. In order to make the # transaction expire on hop_1, set a shorter timeout value on hop_1's # side timeout_seconds = 0.01 hop_1.sixp.send_request(dstMac=root.get_mac_addr(), command=d.SIXP_CMD_ADD, cellList=[], timeout_seconds=timeout_seconds) # step-2: let root issue an CLEAR request when it receives an ADD # request from hop_1. Then, the root will have two transactions to # hop_1 with each direction. result = {'root_received_add_request': False} def recv_add_request(self, packet): assert packet['type'] == d.PKT_TYPE_SIXP assert packet['app']['msgType'] == d.SIXP_MSG_TYPE_REQUEST assert packet['app']['code'] == d.SIXP_CMD_ADD if result['root_received_add_request'] is False: result['root_received_add_request'] = True # send a CLAER request to the responder self.mote.sixp.send_request(dstMac=hop_1.get_mac_addr(), command=d.SIXP_CMD_CLEAR) else: # do nothing pass root.sf.recv_request = types.MethodType(recv_add_request, root.sf) # step-3: let hop_1 issue another ADD request when it receives a CLAER # request from the hop_1. Then, the root will have two transactions to # the responder for each direction. result['hop_1_received_clear_request'] = False def recv_clear_request(self, packet): assert packet['type'] == d.PKT_TYPE_SIXP assert packet['app']['msgType'] == d.SIXP_MSG_TYPE_REQUEST assert packet['app']['code'] == d.SIXP_CMD_CLEAR result['hop_1_received_clear_request'] = True # send a new ADD request, which causes an exception unless the # bugfix is in place. self.mote.sixp.send_request(dstMac=root.get_mac_addr(), command=d.SIXP_CMD_ADD, cellList=[]) hop_1.sf.recv_request = types.MethodType(recv_clear_request, hop_1.sf) # run the simulator until the end; if there is no exception, it should # run through. u.run_until_end(sim_engine) assert result['root_received_add_request'] == True assert result['hop_1_received_clear_request'] == True # There should be one RC_ERR_BUSY from root logs = u.read_log_file([SimLog.LOG_SIXP_TX['type']]) rc_err_busy_logs = [ l for l in logs if ((l['packet']['app']['msgType'] == d.SIXP_MSG_TYPE_RESPONSE) and (l['packet']['app']['code'] == d.SIXP_RC_ERR_BUSY) and ( root.is_my_mac_addr(l['packet']['mac']['srcMac'])) and ( hop_1.is_my_mac_addr(l['packet']['mac']['dstMac']))) ] assert len(rc_err_busy_logs) == 1 # RC_ERR_BUSY should be sent to the ADD request with SeqNum of 0 assert rc_err_busy_logs[0]['packet']['app']['seqNum'] == 0
def test_locked_slot_in_relocation_request(self, sim_engine): # MSF shouldn't select a slot offset out of the candidate cell # list which is in locked_slots sim_engine = sim_engine( diff_config={ 'exec_numMotes': 2, 'sf_class': 'MSF', 'conn_class': 'Linear', 'secjoin_enabled': False, 'app_pkPeriod': 0, 'rpl_daoPeriod': 0, 'rpl_extensions': [], 'tsch_keep_alive_interval': 0, 'tsch_probBcast_ebProb': 0 }) root = sim_engine.motes[0] mote = sim_engine.motes[1] u.get_join(root, mote) # wait for a while u.run_until_asn(sim_engine, 2 * sim_engine.settings.tsch_slotframeLength) # mote should have one dedicated cell cells = mote.tsch.get_cells(root.get_mac_addr(), mote.sf.SLOTFRAME_HANDLE) assert len(cells) == 2 cells = [cell for cell in cells if cell.options == [d.CELLOPTION_TX]] assert len(cells) == 1 cell = cells[0] # send a RELOCATE request to mote, which has the used slot # offset in both of the candidate cell list and the relocation # cell list target_slot_offset = cell.slot_offset + 1 if (cell.slot_offset + 1) == sim_engine.settings.tsch_slotframeLength: target_slot_offset = 1 # put target_slot_offset into locked_slots. target_slot_offset # is in the candidate cell list mote.sf.locked_slots.add(target_slot_offset) root.sixp.send_request(dstMac=mote.get_mac_addr(), command=d.SIXP_CMD_RELOCATE, cellOptions=[d.CELLOPTION_RX], numCells=1, relocationCellList=[{ 'slotOffset': cell.slot_offset, 'channelOffset': cell.channel_offset }], candidateCellList=[{ 'slotOffset': target_slot_offset, 'channelOffset': 0 }], callback=None) u.run_until_asn( sim_engine, sim_engine.getAsn() + 2 * sim_engine.settings.tsch_slotframeLength) logs = u.read_log_file(filter=[SimLog.LOG_SIXP_RX['type']]) assert len(logs) == 4 # including the first round-trip for ADD response = logs[-1]['packet'] assert response['app']['msgType'] == d.SIXP_MSG_TYPE_RESPONSE assert response['app']['code'] == d.SIXP_RC_SUCCESS assert len(response['app']['cellList']) == 0
def test_tx_with_two_slotframes(sim_engine): sim_engine = sim_engine( diff_config={ 'app_pkPeriod': 0, 'exec_numMotes': 2, 'exec_numSlotframesPerRun': 1000, 'secjoin_enabled': False, 'sf_class': 'SFNone', 'conn_class': 'Linear', 'rpl_extensions': [], 'rpl_daoPeriod': 0 }) # shorthands root = sim_engine.motes[0] hop_1 = sim_engine.motes[1] # add one slotframe to the two motes for mote in sim_engine.motes: mote.tsch.add_slotframe(1, 101) asn_at_end_of_simulation = (sim_engine.settings.tsch_slotframeLength * sim_engine.settings.exec_numSlotframesPerRun) u.run_until_everyone_joined(sim_engine) assert sim_engine.getAsn() < asn_at_end_of_simulation # put DIO to hop1 dio = root.rpl._create_DIO() dio['mac'] = {'srcMac': root.get_mac_addr()} hop_1.rpl.action_receiveDIO(dio) # install one TX cells to each slotframe for i in range(2): hop_1.tsch.addCell(slotOffset=i + 1, channelOffset=0, neighbor=root.get_mac_addr(), cellOptions=[d.CELLOPTION_TX], slotframe_handle=i) root.tsch.addCell(slotOffset=i + 1, channelOffset=0, neighbor=hop_1.get_mac_addr(), cellOptions=[d.CELLOPTION_RX], slotframe_handle=i) # the first dedicated cell is scheduled at slot_offset 1, the other is at # slot_offset 2 cell_in_slotframe_0 = hop_1.tsch.get_cells(root.get_mac_addr(), 0)[0] cell_in_slotframe_1 = hop_1.tsch.get_cells(root.get_mac_addr(), 1)[0] # run until the end of this slotframe slot_offset = sim_engine.getAsn() % 101 u.run_until_asn(sim_engine, sim_engine.getAsn() + (101 - slot_offset - 1)) # send two application packets, which will be sent over the dedicated cells hop_1.app._send_a_single_packet() hop_1.app._send_a_single_packet() # run for one slotframe asn = sim_engine.getAsn() assert (asn % 101) == 100 # the next slot is slotoffset 0 u.run_until_asn(sim_engine, asn + 101) # check logs ## TX side (hop_1) logs = [ log for log in u.read_log_file(filter=[SimLog.LOG_TSCH_TXDONE['type']], after_asn=asn) if log['_mote_id'] == hop_1.id ] assert len(logs) == 2 assert (logs[0]['_asn'] % 101) == cell_in_slotframe_0.slot_offset assert (logs[1]['_asn'] % 101) == cell_in_slotframe_1.slot_offset ## RX side (root) logs = [ log for log in u.read_log_file(filter=[SimLog.LOG_TSCH_RXDONE['type']], after_asn=asn) if log['_mote_id'] == root.id ] assert len(logs) == 2 assert (logs[0]['_asn'] % 101) == cell_in_slotframe_0.slot_offset assert (logs[1]['_asn'] % 101) == cell_in_slotframe_1.slot_offset # confirm hop_1 has the minimal cell assert len(hop_1.tsch.get_cells(None)) == 1 assert (hop_1.tsch.get_cells(None)[0].options == [ d.CELLOPTION_TX, d.CELLOPTION_RX, d.CELLOPTION_SHARED ])
def test_msf(self, sim_engine): """ Test Scheduling Function Traffic Adaptation - objective : test if msf adjust the number of allocated cells in accordance with traffic - precondition: form a 2-mote linear network - precondition: the network is formed - action : change traffic - expectation : MSF should trigger ADD/DELETE/RELOCATE accordingly """ # to make this test easy, change # MSF_HOUSEKEEPINGCOLLISION_PERIOD to 1 second msf_housekeeping_period_backup = d.MSF_HOUSEKEEPINGCOLLISION_PERIOD d.MSF_HOUSEKEEPINGCOLLISION_PERIOD = 1 sim_engine = sim_engine( diff_config={ 'exec_randomSeed': 3413860673863013345, 'app_pkPeriod': 0, 'app_pkPeriodVar': 0, 'exec_numMotes': 2, 'exec_numSlotframesPerRun': 4000, 'rpl_daoPeriod': 0, 'tsch_keep_alive_interval': 0, 'tsch_probBcast_ebProb': 0, 'secjoin_enabled': False, 'sf_class': 'MSF', 'conn_class': 'Linear', }) # for quick access root = sim_engine.motes[0] mote = sim_engine.motes[1] # disable DIO def do_nothing(self): pass mote.rpl._send_DIO = types.MethodType(do_nothing, mote) # get the mote joined eb = root.tsch._create_EB() eb_dummy = { 'type': d.PKT_TYPE_EB, 'mac': { 'srcMac': '00-00-00-AA-AA-AA', # dummy 'dstMac': d.BROADCAST_ADDRESS, # broadcast 'join_metric': 1000 } } mote.tsch._action_receiveEB(eb) mote.tsch._action_receiveEB(eb_dummy) dio = root.rpl._create_DIO() dio['mac'] = { 'srcMac': root.get_mac_addr(), 'dstMac': d.BROADCAST_ADDRESS } mote.sixlowpan.recvPacket(dio) # 1. test autonomous cell installation # 1.1 test Non-SHARED autonomous cell cells = [ cell for cell in mote.tsch.get_cells( mac_addr=None, slotframe_handle=SchedulingFunctionMSF.SLOTFRAME_HANDLE) if cell.options == [d.CELLOPTION_TX, d.CELLOPTION_RX] ] assert len(cells) == 1 # 1.2 test SHARED autonomous cell to root cells = [ cell for cell in mote.tsch.get_cells( mac_addr=root.get_mac_addr(), slotframe_handle=SchedulingFunctionMSF.SLOTFRAME_HANDLE) if cell.options == [d.CELLOPTION_TX, d.CELLOPTION_RX, d.CELLOPTION_SHARED] ] assert len(cells) == 1 # 2. test dedicated cell allocation # 2.1 decrease MSF_MIN_NUM_TX and MSF_MAX_NUMCELLS to speed up this test d.MSF_MIN_NUM_TX = 10 d.MSF_MAX_NUMCELLS = 10 # 2.2 confirm the mote doesn't have any dedicated cell to its parent cells = [ cell for cell in mote.tsch.get_cells( mac_addr=root.get_mac_addr(), slotframe_handle=SchedulingFunctionMSF.SLOTFRAME_HANDLE) if cell.options == [d.CELLOPTION_TX] ] assert len(cells) == 0 # 2.3 the mote should have triggered a 6P to allocate one # dedicated cell logs = u.read_log_file(filter=[SimLog.LOG_SIXP_TX['type']]) assert len(logs) == 1 packet = logs[0]['packet'] assert packet['mac']['dstMac'] == root.get_mac_addr() assert packet['app']['msgType'] == d.SIXP_MSG_TYPE_REQUEST assert packet['app']['code'] == d.SIXP_CMD_ADD assert packet['app']['numCells'] == 1 assert packet['app']['cellOptions'] == [d.CELLOPTION_TX] # in order to test the traffic adaptation mechanism of MSF, # disable the pending bit feature assert mote.tsch.pending_bit_enabled is True mote.tsch.pending_bit_enabled = False # wait until the managed cell is available u.run_until_asn( sim_engine, sim_engine.getAsn() + mote.settings.tsch_slotframeLength * 2) # mote should have one managed cell scheduled cells = [ cell for cell in mote.tsch.get_cells( mac_addr=root.get_mac_addr(), slotframe_handle=SchedulingFunctionMSF.SLOTFRAME_HANDLE) if cell.options == [d.CELLOPTION_TX] ] assert len(cells) == 1 # 2.4 send an application packet per slotframe mote.settings.app_pkPeriod = (mote.settings.tsch_slotframeLength / 2 * mote.settings.tsch_slotDuration) mote.app.startSendingData() # 2.5 run for 10 slotframes assert mote.sf.cell_utilization == 0.0 u.run_until_asn( sim_engine, sim_engine.getAsn() + mote.settings.tsch_slotframeLength * 10) # 2.6 confirm the cell usage reaches 100% assert mote.sf.cell_utilization == 1.0 # 2.7 one dedicated cell should be allocated in the next 2 slotframes u.run_until_asn( sim_engine, sim_engine.getAsn() + mote.settings.tsch_slotframeLength * 2) cells = [ cell for cell in mote.tsch.get_cells( mac_addr=root.get_mac_addr(), slotframe_handle=SchedulingFunctionMSF.SLOTFRAME_HANDLE) if cell.options == [d.CELLOPTION_TX] ] assert len(cells) == 2 slot_offset = cells[0].slot_offset # adjust the packet interval mote.settings.app_pkPeriod = (mote.settings.tsch_slotframeLength / 3 * mote.settings.tsch_slotDuration) # 3. test cell relocation # 3.1 increase the following Rpl values in order to avoid invalidating # the root as a parent mote.rpl.of.MAX_NUM_OF_CONSECUTIVE_FAILURES_WITHOUT_ACK = 100 mote.rpl.of.UPPER_LIMIT_OF_ACCEPTABLE_ETX = 100 mote.rpl.of.MAXIMUM_STEP_OF_RANK = 100 # 3.2 deny input frames over the dedicated cell on the side of the root def rxDone_wrapper(self, packet, channel): if ((packet is not None) and ((self.engine.getAsn() % mote.settings.tsch_slotframeLength) == slot_offset)): self.active_cell = None self.waitingFor = None # silently discard this packet return False else: return self.rxDone_original(packet, channel) root.tsch.rxDone_original = root.tsch.rxDone root.tsch.rxDone = types.MethodType(rxDone_wrapper, root.tsch) # 3.3 run for the next 20 slotframes asn_start = sim_engine.getAsn() u.run_until_asn( sim_engine, sim_engine.getAsn() + mote.settings.tsch_slotframeLength * 20) # 3.5 RELOCATE should have happened logs = [ log for log in u.read_log_file(filter=['sixp.comp'], after_asn=asn_start) if ((log['_mote_id'] == mote.id) and ( log['cmd'] == d.SIXP_CMD_RELOCATE)) ] assert len(logs) == 1 # 4. test dedicated cell deallocation # 4.1 stop application packet transmission mote.settings.app_pkPeriod = 0 # 4.2 run for a while asn_start = sim_engine.getAsn() u.run_until_asn( sim_engine, sim_engine.getAsn() + mote.settings.tsch_slotframeLength * 20) # 4.3 DELETE should have happened logs = [ log for log in u.read_log_file(filter=['sixp.comp'], after_asn=asn_start) if ((log['_mote_id'] == mote.id) and ( log['cmd'] == d.SIXP_CMD_DELETE)) ] assert len(logs) > 0 # put the backup value to d.MSF_HOUSEKEEPINGCOLLISION_PERIOD d.MSF_HOUSEKEEPINGCOLLISION_PERIOD = msf_housekeeping_period_backup