1#!/usr/bin/env python3 2# 3# Copyright (c) 2019, 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 unittest 31 32import thread_cert 33import config 34import mle 35 36LEADER_1_2 = 1 37ROUTER_1_1 = 2 38REED_1_2 = 3 39ROUTER_1_2 = 4 40REED_1_1 = 5 41MED_1_1 = 6 42MED_1_2 = 7 43 44# Topology 45# (lq:2) (pp:1) 46# REED_1_2 ----- ROUTER_1_2 47# | \ / | \ 48# | \/ REED_1_1 \ 49# (lq:2) | / \ / `router` \ 50# | (lq:2) \ \ 51# | / / \ \ 52# LEADER_1_2 --- ROUTER_1_1 -- MED_1_2 53# \ | 54# \ | 55# \ | 56# MED_1_1 57# 58# 1) Bring up LEADER_1_2 and ROUTER_1_1, 59# 2) Config link quality (LEADER_1_2->REED_1_2) as 2, bring up REED_1_2 which would attach to ROUTER_1_1 60# due to higher two-way link quality, 61# 3) Config link quality(LEADER_1_2->ROUTER_1_2) and link quality(REED_1_2->ROUTER_1_2) as 2, bring up 62# ROUTER_1_2 which would attach to LEADER_1_2 due to active router is preferred, 63# 4) Config parent priority as 1 on ROUTER_1_2, bring up REED_1_1 which would attach to ROUTER_1_2 due to 64# higher parent priority, 65# 5) Upgrade REED_1_1 to `router` role, bring up MED_1_1 which would attach to LEADER_1_2 which has higher 66# link quality of 3, 67# 6) Config parent priority as 1 on ROUTER_1_1, bring up MED_1_2 which would attach to ROUTER_1_2 due to 68# higher version 69# 70 71 72class TestParentSelection(thread_cert.TestCase): 73 TOPOLOGY = { 74 LEADER_1_2: { 75 'version': '1.2', 76 'allowlist': [REED_1_2, ROUTER_1_2, REED_1_1, ROUTER_1_1, MED_1_1], 77 }, 78 ROUTER_1_1: { 79 'version': '1.1', 80 'allowlist': [LEADER_1_2, REED_1_2, MED_1_2, MED_1_1], 81 }, 82 REED_1_2: { 83 'version': '1.2', 84 'allowlist': [ROUTER_1_2, ROUTER_1_1, LEADER_1_2], 85 }, 86 ROUTER_1_2: { 87 'version': '1.2', 88 'allowlist': [REED_1_2, MED_1_2, REED_1_1, LEADER_1_2], 89 }, 90 REED_1_1: { 91 'version': '1.1', 92 'allowlist': [ROUTER_1_2, LEADER_1_2] 93 }, 94 MED_1_1: { 95 'mode': 'r', 96 'version': '1.1', 97 'allowlist': [LEADER_1_2, ROUTER_1_1], 98 }, 99 MED_1_2: { 100 'mode': 'r', 101 'version': '1.2', 102 'allowlist': [ROUTER_1_1, ROUTER_1_2], 103 }, 104 } 105 """All nodes are created with default configurations""" 106 107 def test(self): 108 109 self.nodes[LEADER_1_2].start() 110 self.simulator.go(config.LEADER_STARTUP_DELAY) 111 self.assertEqual(self.nodes[LEADER_1_2].get_state(), 'leader') 112 113 self.nodes[ROUTER_1_1].set_router_selection_jitter(1) 114 self.nodes[ROUTER_1_1].start() 115 self.simulator.go(config.ROUTER_STARTUP_DELAY) 116 self.assertEqual(self.nodes[ROUTER_1_1].get_state(), 'router') 117 118 # Mesh Impacting Criteria - Highest Two-way link quality 119 # REED_1_2 would attach to ROUTER_1_1 120 # Attach to ROUTER_1_1 which has highest two-way link quality 121 122 # Flush relative message queues 123 self.flush_nodes([LEADER_1_2, ROUTER_1_1]) 124 125 self.nodes[LEADER_1_2].set_link_quality(self.nodes[REED_1_2].get_addr64(), 2) 126 self.nodes[REED_1_2].set_router_selection_jitter(1) 127 self.nodes[REED_1_2].set_router_upgrade_threshold(1) 128 self.nodes[REED_1_2].start() 129 self.simulator.go(5) 130 self.assertEqual(self.nodes[REED_1_2].get_state(), 'child') 131 132 # Check Parent Response 133 messages = self.simulator.get_messages_sent_by(ROUTER_1_1) 134 parent_prefer = messages.next_mle_message(mle.CommandType.PARENT_RESPONSE) 135 assert (parent_prefer), "Error: Expected parent response not found" 136 137 messages = self.simulator.get_messages_sent_by(LEADER_1_2) 138 parent_cmp = messages.next_mle_message(mle.CommandType.PARENT_RESPONSE) 139 assert (parent_cmp), "Error: Expected parent response not found" 140 141 # Known that link margin for link quality 3 is 80 and link quality 2 is 15 142 assert ((parent_prefer.get_mle_message_tlv(mle.LinkMargin).link_margin - 143 parent_cmp.get_mle_message_tlv(mle.LinkMargin).link_margin) > 20) 144 145 # Check Child Id Request 146 messages = self.simulator.get_messages_sent_by(REED_1_2) 147 msg = messages.next_mle_message(mle.CommandType.CHILD_ID_REQUEST) 148 msg.assertSentToNode(self.nodes[ROUTER_1_1]) 149 150 # Mesh Impacting Criteria - Active Routers over REEDs 151 # ROUTER_1_2 would attach to LEADER_1_2 152 # Link quality configuration, so that REED_1_2 has the chance to respond 153 154 # Flush relative message queues 155 self.flush_nodes([LEADER_1_2, REED_1_2]) 156 157 self.nodes[LEADER_1_2].set_link_quality(self.nodes[ROUTER_1_2].get_addr64(), 2) 158 self.nodes[REED_1_2].set_link_quality(self.nodes[ROUTER_1_2].get_addr64(), 2) 159 self.nodes[ROUTER_1_2].set_router_selection_jitter(1) 160 self.nodes[ROUTER_1_2].start() 161 self.simulator.go(config.ROUTER_STARTUP_DELAY) 162 self.assertEqual(self.nodes[ROUTER_1_2].get_state(), 'router') 163 164 # Check Parent Response 165 messages = self.simulator.get_messages_sent_by(LEADER_1_2) 166 167 # Skip first response for first parent request 168 assert messages.next_mle_message(mle.CommandType.PARENT_RESPONSE) 169 170 parent_prefer = messages.next_mle_message(mle.CommandType.PARENT_RESPONSE) 171 assert (parent_prefer), "Error: Expected parent response not found" 172 173 messages = self.simulator.get_messages_sent_by(REED_1_2) 174 parent_cmp = messages.next_mle_message(mle.CommandType.PARENT_RESPONSE) 175 assert (parent_cmp), "Error: Expected parent response not found" 176 177 assert (parent_prefer.get_mle_message_tlv(mle.LinkMargin).link_margin == parent_cmp.get_mle_message_tlv( 178 mle.LinkMargin).link_margin) 179 180 # Check Child Id Request 181 messages = self.simulator.get_messages_sent_by(ROUTER_1_2) 182 msg = messages.next_mle_message(mle.CommandType.CHILD_ID_REQUEST) 183 msg.assertSentToNode(self.nodes[LEADER_1_2]) 184 185 # Mesh Impacting Criteria - Highest Parent Priority value in the Connectivity TLV 186 # REED_1_1 would attach to ROUTER_1_2 187 188 # Flush relative message queues 189 self.flush_nodes([LEADER_1_2, ROUTER_1_2]) 190 191 self.nodes[ROUTER_1_2].set_parent_priority(1) 192 self.nodes[REED_1_1].set_router_selection_jitter(1) 193 self.nodes[REED_1_1].set_router_upgrade_threshold(1) 194 self.nodes[REED_1_1].start() 195 self.simulator.go(5) 196 self.assertEqual(self.nodes[REED_1_1].get_state(), 'child') 197 198 # Check Parent Response 199 messages = self.simulator.get_messages_sent_by(ROUTER_1_2) 200 parent_prefer = messages.next_mle_message(mle.CommandType.PARENT_RESPONSE) 201 assert (parent_prefer), "Error: Expected parent response not found" 202 203 messages = self.simulator.get_messages_sent_by(LEADER_1_2) 204 parent_cmp = messages.next_mle_message(mle.CommandType.PARENT_RESPONSE) 205 assert (parent_cmp), "Error: Expected parent response not found" 206 207 assert (parent_prefer.get_mle_message_tlv(mle.LinkMargin).link_margin == parent_cmp.get_mle_message_tlv( 208 mle.LinkMargin).link_margin) 209 210 assert (parent_prefer.get_mle_message_tlv(mle.Connectivity).pp > parent_cmp.get_mle_message_tlv( 211 mle.Connectivity).pp) 212 213 # Check Child Id Request 214 messages = self.simulator.get_messages_sent_by(REED_1_1) 215 msg = messages.next_mle_message(mle.CommandType.CHILD_ID_REQUEST) 216 msg.assertSentToNode(self.nodes[ROUTER_1_2]) 217 218 # Mesh Impacting Criteria - Router with the most high-quality neighbors 219 # (Link Quality 3 field in the Connectivity TLV) 220 # MED_1_1 would attach to LEADER_1_2 221 222 self.nodes[REED_1_1].set_state('router') 223 self.simulator.go(config.ROUTER_STARTUP_DELAY) 224 self.assertEqual(self.nodes[REED_1_1].get_state(), 'router') 225 226 # Flush relative message queues 227 self.flush_nodes([LEADER_1_2, ROUTER_1_1]) 228 229 self.nodes[MED_1_1].start() 230 self.simulator.go(5) 231 self.assertEqual(self.nodes[MED_1_1].get_state(), 'child') 232 233 # Check Parent Response 234 messages = self.simulator.get_messages_sent_by(LEADER_1_2) 235 parent_prefer = messages.next_mle_message(mle.CommandType.PARENT_RESPONSE) 236 assert (parent_prefer), "Error: Expected parent response not found" 237 238 messages = self.simulator.get_messages_sent_by(ROUTER_1_1) 239 parent_cmp = messages.next_mle_message(mle.CommandType.PARENT_RESPONSE) 240 assert (parent_cmp), "Error: Expected parent response not found" 241 242 assert (parent_prefer.get_mle_message_tlv(mle.LinkMargin).link_margin == parent_cmp.get_mle_message_tlv( 243 mle.LinkMargin).link_margin) 244 assert (parent_prefer.get_mle_message_tlv(mle.Connectivity).pp == parent_cmp.get_mle_message_tlv( 245 mle.Connectivity).pp) 246 assert (parent_prefer.get_mle_message_tlv(mle.Connectivity).link_quality_3 > parent_cmp.get_mle_message_tlv( 247 mle.Connectivity).link_quality_3) 248 249 # Check Child Id Request 250 messages = self.simulator.get_messages_sent_by(MED_1_1) 251 msg = messages.next_mle_message(mle.CommandType.CHILD_ID_REQUEST) 252 msg.assertSentToNode(self.nodes[LEADER_1_2]) 253 254 # Child Impacting Criteria - A Version number in the Version TLV 255 # equal to or higher than the version that implements features 256 # desirable to the Child MED_1_2 would attach to ROUTER_1_2 257 258 # Flush relative message queues 259 self.flush_nodes([ROUTER_1_2, ROUTER_1_1]) 260 261 self.nodes[ROUTER_1_1].set_parent_priority(1) 262 self.nodes[MED_1_2].start() 263 self.simulator.go(15) 264 self.assertEqual(self.nodes[MED_1_2].get_state(), 'child') 265 266 # Check Parent Response 267 messages = self.simulator.get_messages_sent_by(ROUTER_1_2) 268 parent_prefer = messages.next_mle_message(mle.CommandType.PARENT_RESPONSE) 269 assert (parent_prefer), "Error: Expected parent response not found" 270 271 messages = self.simulator.get_messages_sent_by(ROUTER_1_1) 272 parent_cmp = messages.next_mle_message(mle.CommandType.PARENT_RESPONSE) 273 assert (parent_cmp), "Error: Expected parent response not found" 274 275 assert (parent_prefer.get_mle_message_tlv(mle.LinkMargin).link_margin == parent_cmp.get_mle_message_tlv( 276 mle.LinkMargin).link_margin) 277 assert (parent_prefer.get_mle_message_tlv(mle.Connectivity).pp == parent_cmp.get_mle_message_tlv( 278 mle.Connectivity).pp) 279 assert (parent_prefer.get_mle_message_tlv(mle.Connectivity).link_quality_3 == parent_cmp.get_mle_message_tlv( 280 mle.Connectivity).link_quality_3) 281 assert (parent_prefer.get_mle_message_tlv(mle.Version).version > parent_cmp.get_mle_message_tlv( 282 mle.Version).version) 283 284 # Check Child Id Request 285 messages = self.simulator.get_messages_sent_by(MED_1_2) 286 msg = messages.next_mle_message(mle.CommandType.CHILD_ID_REQUEST) 287 msg.assertSentToNode(self.nodes[ROUTER_1_2]) 288 289 290if __name__ == '__main__': 291 unittest.main() 292