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