1#!/usr/bin/env python3 2# 3# Copyright (c) 2021, The OpenThread Authors. 4# All rights reserved. 5# 6# Redistribution and use in source and binary forms, with or without 7# modification, are permitted provided that the following conditions are met: 8# 1. Redistributions of source code must retain the above copyright 9# notice, this list of conditions and the following disclaimer. 10# 2. Redistributions in binary form must reproduce the above copyright 11# notice, this list of conditions and the following disclaimer in the 12# documentation and/or other materials provided with the distribution. 13# 3. Neither the name of the copyright holder nor the 14# names of its contributors may be used to endorse or promote products 15# derived from this software without specific prior written permission. 16# 17# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 21# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27# POSSIBILITY OF SUCH DAMAGE. 28# 29 30import os 31import unittest 32import sys 33import thread_cert 34 35# Test description: 36# This test verifies History Tracker behavior. 37# 38# Topology: 39# 40# LEADER 41# | 42# | 43# CHILD 44# 45 46LEADER = 1 47CHILD = 2 48 49SHORT_WAIT = 5 50ONE_DAY = 24 * 60 * 60 51MAX_AGE_IN_DAYS = 49 52 53 54class TestHistoryTracker(thread_cert.TestCase): 55 USE_MESSAGE_FACTORY = False 56 SUPPORT_NCP = False 57 58 TOPOLOGY = { 59 LEADER: { 60 'name': 'Leader', 61 'mode': 'rdn', 62 }, 63 CHILD: { 64 'name': 'Child', 65 'mode': 'n', 66 }, 67 } 68 69 def test(self): 70 leader = self.nodes[LEADER] 71 child = self.nodes[CHILD] 72 73 # Start the leader and verify that 'netinfo' history 74 # is updated correctly. 75 76 leader.start() 77 self.simulator.go(SHORT_WAIT) 78 self.assertEqual(leader.get_state(), 'leader') 79 80 netinfo = leader.history_netinfo() 81 self.assertEqual(len(netinfo), 2) 82 self.assertEqual(netinfo[0]['role'], 'leader') 83 self.assertEqual(netinfo[0]['mode'], 'rdn') 84 self.assertEqual(int(netinfo[0]['rloc16'], 16), leader.get_addr16()) 85 self.assertEqual(netinfo[0]['partition-id'], leader.get_partition_id()) 86 self.assertEqual(netinfo[1]['role'], 'detached') 87 88 # Stop the leader 89 90 leader.thread_stop() 91 leader.interface_down() 92 self.simulator.go(SHORT_WAIT) 93 netinfo = leader.history_netinfo(2) 94 self.assertEqual(len(netinfo), 2) 95 self.assertEqual(netinfo[0]['role'], 'disabled') 96 self.assertEqual(netinfo[1]['role'], 'leader') 97 98 # Wait for one day, two days, then up to max age and verify that 99 # `netinfo` entry age is updated correctly. 100 # 101 # Since we want to wait for long duration (49 days), to speed up 102 # the simulation time, we disable leader to avoid the need to 103 # to simulate all the message/events (e.g. MLE adv) while thread 104 # is operational. 105 106 self.simulator.go(ONE_DAY) 107 netinfo = leader.history_netinfo(1) 108 self.assertTrue(netinfo[0]['age'].startswith('1 day')) 109 110 self.simulator.go(ONE_DAY) 111 netinfo = leader.history_netinfo(1) 112 self.assertTrue(netinfo[0]['age'].startswith('2 days')) 113 114 self.simulator.go((MAX_AGE_IN_DAYS - 3) * ONE_DAY) 115 netinfo = leader.history_netinfo(1) 116 self.assertTrue(netinfo[0]['age'].startswith('{} days'.format(MAX_AGE_IN_DAYS - 1))) 117 118 self.simulator.go(ONE_DAY) 119 netinfo = leader.history_netinfo(1) 120 self.assertTrue(netinfo[0]['age'].startswith('more than {} days'.format(MAX_AGE_IN_DAYS))) 121 122 self.simulator.go(2 * ONE_DAY) 123 netinfo = leader.history_netinfo(1) 124 self.assertTrue(netinfo[0]['age'].startswith('more than {} days'.format(MAX_AGE_IN_DAYS))) 125 126 # Start leader and child 127 128 leader.start() 129 self.simulator.go(SHORT_WAIT) 130 self.assertEqual(leader.get_state(), 'leader') 131 132 child.start() 133 self.simulator.go(SHORT_WAIT) 134 self.assertEqual(child.get_state(), 'child') 135 136 child_rloc16 = child.get_addr16() 137 leader_rloc16 = leader.get_addr16() 138 139 # Verify the `netinfo` history on child 140 141 netinfo = child.history_netinfo(2) 142 self.assertEqual(len(netinfo), 2) 143 self.assertEqual(netinfo[0]['role'], 'child') 144 self.assertEqual(netinfo[0]['mode'], 'n') 145 self.assertEqual(int(netinfo[0]['rloc16'], 16), child_rloc16) 146 self.assertEqual(netinfo[0]['partition-id'], leader.get_partition_id()) 147 self.assertEqual(netinfo[1]['role'], 'detached') 148 149 # Change the child mode and verify that `netinfo` history 150 # records this change. 151 152 child.set_mode('rn') 153 self.simulator.go(SHORT_WAIT) 154 netinfo = child.history_netinfo(1) 155 self.assertEqual(len(netinfo), 1) 156 self.assertEqual(netinfo[0]['mode'], 'rn') 157 158 # Ping from leader to child and check the RX and TX history 159 # on child and leader. 160 161 child_mleid = child.get_mleid() 162 leader_mleid = leader.get_mleid() 163 164 ping_sizes = [10, 100, 1000] 165 num_msgs = len(ping_sizes) 166 167 for size in ping_sizes: 168 leader.ping(child_mleid, size=size) 169 170 leader_tx = leader.history_tx(num_msgs) 171 leader_rx = leader.history_rx(num_msgs) 172 child_tx = child.history_tx(num_msgs) 173 child_rx = child.history_rx(num_msgs) 174 175 for index in range(num_msgs): 176 self.assertEqual(leader_tx[index]['type'], 'ICMP6(EchoReqst)') 177 self.assertEqual(leader_tx[index]['sec'], 'yes') 178 self.assertEqual(leader_tx[index]['prio'], 'norm') 179 self.assertEqual(leader_tx[index]['tx-success'], 'yes') 180 self.assertEqual(leader_tx[index]['radio'], '15.4') 181 self.assertEqual(int(leader_tx[index]['to'], 16), child_rloc16) 182 self.assertEqual(leader_tx[index]['src'][1:-3], leader_mleid) 183 self.assertEqual(leader_tx[index]['dst'][1:-3], child_mleid) 184 185 self.assertEqual(child_rx[index]['type'], 'ICMP6(EchoReqst)') 186 self.assertEqual(child_rx[index]['sec'], 'yes') 187 self.assertEqual(child_rx[index]['prio'], 'norm') 188 self.assertEqual(child_rx[index]['radio'], '15.4') 189 self.assertEqual(int(child_rx[index]['from'], 16), leader_rloc16) 190 self.assertEqual(child_rx[index]['src'][1:-3], leader_mleid) 191 self.assertEqual(child_rx[index]['dst'][1:-3], child_mleid) 192 193 self.assertEqual(leader_rx[index]['type'], 'ICMP6(EchoReply)') 194 self.assertEqual(child_tx[index]['type'], 'ICMP6(EchoReply)') 195 196 self.assertEqual(leader_tx[index]['len'], child_rx[index]['len']) 197 self.assertEqual(leader_rx[index]['len'], child_tx[index]['len']) 198 199 200if __name__ == '__main__': 201 # FIXME: We skip the test under distcheck build (the simulation 202 # under this build for some reason cannot seem to handle longer 203 # wait times - days up to 50 days in this test). We return error 204 # code 77 which indicates that this test case was skipped (in 205 # automake). 206 207 if os.getenv('DISTCHECK_BUILD') == '1': 208 sys.exit(77) 209 210 unittest.main() 211