1#!/usr/bin/env python3
2#
3#  Copyright (c) 2022, 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
29from cli import verify
30from cli import verify_within
31import cli
32import time
33
34# -----------------------------------------------------------------------------------------------------------------------
35# Test description:
36#
37# Verify device mode change on children.
38#
39
40test_name = __file__[:-3] if __file__.endswith('.py') else __file__
41print('-' * 120)
42print('Starting \'{}\''.format(test_name))
43
44# -----------------------------------------------------------------------------------------------------------------------
45# Creating `cli.Node` instances
46
47speedup = 10
48cli.Node.set_time_speedup_factor(speedup)
49
50parent = cli.Node()
51child1 = cli.Node()
52child2 = cli.Node()
53
54# -----------------------------------------------------------------------------------------------------------------------
55# Form topology
56
57parent.form('modechange')
58child1.join(parent, cli.JOIN_TYPE_END_DEVICE)
59child2.join(parent, cli.JOIN_TYPE_SLEEPY_END_DEVICE)
60
61verify(parent.get_state() == 'leader')
62verify(child1.get_state() == 'child')
63verify(child2.get_state() == 'child')
64
65# -----------------------------------------------------------------------------------------------------------------------
66# Test Implementation
67
68child1_rloc = int(child1.get_rloc16(), 16)
69child2_rloc = int(child2.get_rloc16(), 16)
70
71# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
72# Check the mode on children and also on parent child table
73
74verify(parent.get_mode() == 'rdn')
75verify(child1.get_mode() == 'rn')
76verify(child2.get_mode() == '-')
77
78child_table = parent.get_child_table()
79verify(len(child_table) == 2)
80for entry in child_table:
81    if int(entry['RLOC16'], 16) == child1_rloc:
82        verify(entry['R'] == '1')
83        verify(entry['D'] == '0')
84        verify(entry['N'] == '1')
85    elif int(entry['RLOC16'], 16) == child2_rloc:
86        verify(entry['R'] == '0')
87        verify(entry['D'] == '0')
88        verify(entry['N'] == '0')
89    else:
90        verify(False)
91
92# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
93# Change the network data flag on child 2 (sleepy) and verify that it
94# gets changed on parent's child table.
95
96child2.set_mode('n')
97
98
99def check_child2_n_flag_change():
100    verify(child2.get_mode() == 'n')
101    child_table = parent.get_child_table()
102    verify(len(child_table) == 2)
103    for entry in child_table:
104        if int(entry['RLOC16'], 16) == child1_rloc:
105            verify(entry['R'] == '1')
106            verify(entry['D'] == '0')
107            verify(entry['N'] == '1')
108        elif int(entry['RLOC16'], 16) == child2_rloc:
109            verify(entry['R'] == '0')
110            verify(entry['D'] == '0')
111            verify(entry['N'] == '1')
112        else:
113            verify(False)
114
115
116verify_within(check_child2_n_flag_change, 5)
117
118# Verify that mode change did not cause child2 to detach and re-attach
119
120verify(int(cli.Node.parse_list(child2.get_mle_counter())['Role Detached']) == 1)
121
122# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
123# Change child1 from rx-on to sleepy and verify the change on
124# parent's child table. This mode change should require child
125# to detach and attach again.
126
127child1.set_mode('n')
128
129
130def check_child1_become_sleepy():
131    verify(child1.get_mode() == 'n')
132    child_table = parent.get_child_table()
133    verify(len(child_table) == 2)
134    for entry in child_table:
135        if int(entry['RLOC16'], 16) == child1_rloc:
136            verify(entry['R'] == '0')
137            verify(entry['D'] == '0')
138            verify(entry['N'] == '1')
139        elif int(entry['RLOC16'], 16) == child2_rloc:
140            verify(entry['R'] == '0')
141            verify(entry['D'] == '0')
142            verify(entry['N'] == '1')
143        else:
144            verify(False)
145
146
147verify_within(check_child1_become_sleepy, 5)
148
149verify(int(cli.Node.parse_list(child1.get_mle_counter())['Role Detached']) == 2)
150
151# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
152# Change child2 from sleepy to rx-on and verify the change on
153# parent's child table. Verify that child2 did not detach and
154# used MLE "Child Update" exchange.
155
156child2.set_mode('rn')
157
158
159def check_child2_become_rx_on():
160    verify(child2.get_mode() == 'rn')
161    child_table = parent.get_child_table()
162    verify(len(child_table) == 2)
163    for entry in child_table:
164        if int(entry['RLOC16'], 16) == child1_rloc:
165            verify(entry['R'] == '0')
166            verify(entry['D'] == '0')
167            verify(entry['N'] == '1')
168        elif int(entry['RLOC16'], 16) == child2_rloc:
169            verify(entry['R'] == '1')
170            verify(entry['D'] == '0')
171            verify(entry['N'] == '1')
172        else:
173            verify(False)
174
175
176verify_within(check_child2_become_rx_on, 5)
177
178verify(int(cli.Node.parse_list(child2.get_mle_counter())['Role Detached']) == 1)
179
180# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
181# Now change child2 to become sleepy again. Since it was originally
182# attached as sleepy it should not detach and attach again and
183# can do this using MlE "Child Update" exchange with parent.
184
185child2.set_mode('n')
186
187
188def check_child2_become_sleepy_again():
189    verify(child2.get_mode() == 'n')
190    child_table = parent.get_child_table()
191    verify(len(child_table) == 2)
192    for entry in child_table:
193        if int(entry['RLOC16'], 16) == child1_rloc:
194            verify(entry['R'] == '0')
195            verify(entry['D'] == '0')
196            verify(entry['N'] == '1')
197        elif int(entry['RLOC16'], 16) == child2_rloc:
198            verify(entry['R'] == '0')
199            verify(entry['D'] == '0')
200            verify(entry['N'] == '1')
201        else:
202            verify(False)
203
204
205verify_within(check_child2_become_sleepy_again, 5)
206
207verify(int(cli.Node.parse_list(child2.get_mle_counter())['Role Detached']) == 1)
208
209# -----------------------------------------------------------------------------------------------------------------------
210# Test finished
211
212cli.Node.finalize_all_nodes()
213
214print('\'{}\' passed.'.format(test_name))
215