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#
29
30import unittest
31
32import command
33import config
34import thread_cert
35
36# Test description:
37#
38#   This test verifies behavior child supervision.
39#
40#
41# Topology:
42#
43#  Parent (leader)
44#   |
45#   |
46#  Child (sleepy).
47
48PARENT = 1
49CHILD = 2
50
51
52class ChildSupervision(thread_cert.TestCase):
53    USE_MESSAGE_FACTORY = False
54    SUPPORT_NCP = False
55
56    TOPOLOGY = {
57        PARENT: {
58            'name': 'PARENT',
59            'mode': 'rdn',
60        },
61        CHILD: {
62            'name': 'CHILD',
63            'is_mtd': True,
64            'mode': 'n',
65        },
66    }
67
68    def test(self):
69        parent = self.nodes[PARENT]
70        child = self.nodes[CHILD]
71
72        # Form the network.
73
74        parent.start()
75        self.simulator.go(config.LEADER_STARTUP_DELAY)
76        self.assertEqual(parent.get_state(), 'leader')
77
78        child.start()
79        self.simulator.go(5)
80        self.assertEqual(child.get_state(), 'child')
81        child.set_pollperiod(500)
82
83        self.assertEqual(int(child.get_child_supervision_check_failure_counter()), 0)
84
85        # Check the parent's child table.
86
87        table = parent.get_child_table()
88        self.assertEqual(len(table), 1)
89        self.assertEqual(table[1]['suprvsn'], int(child.get_child_supervision_interval()))
90
91        # Change the supervision interval on child. This should trigger an
92        # MLE Child Update exchange from child to parent so to inform parent
93        # about the change. Verify that parent is notified by checking the
94        # parent's child table.
95
96        child.set_child_supervision_interval(20)
97
98        self.simulator.go(2)
99
100        self.assertEqual(int(child.get_child_supervision_interval()), 20)
101        table = parent.get_child_table()
102        self.assertEqual(len(table), 1)
103        self.assertEqual(table[1]['suprvsn'], int(child.get_child_supervision_interval()))
104
105        # Change supervision check timeout on the child.
106
107        child.set_child_supervision_check_timeout(25)
108        self.assertEqual(int(child.get_child_supervision_check_timeout()), 25)
109
110        # Wait for multiple supervision intervals and ensure that child
111        # stays attached (child supervision working as expected).
112
113        self.simulator.go(110)
114
115        self.assertEqual(child.get_state(), 'child')
116        table = parent.get_child_table()
117        self.assertEqual(len(table), 1)
118        self.assertEqual(int(child.get_child_supervision_check_failure_counter()), 0)
119
120        # Disable supervision check on child.
121
122        child.set_child_supervision_check_timeout(0)
123
124        # Enable allowlist on parent without adding the child. After child
125        # timeout expires, the parent should remove the child from its child
126        # table.
127
128        parent.clear_allowlist()
129        parent.enable_allowlist()
130
131        table = parent.get_child_table()
132        child_timeout = table[1]['timeout']
133
134        self.simulator.go(child_timeout + 1)
135        table = parent.get_child_table()
136        self.assertEqual(len(table), 0)
137
138        # Since supervision check is disabled on the child, it should
139        # continue to stay attached to parent (since data polls are acked by
140        # radio driver).
141
142        self.assertEqual(child.get_state(), 'child')
143        self.assertEqual(int(child.get_child_supervision_check_failure_counter()), 0)
144
145        # Re-enable supervision check on child. After the check timeout the
146        # child must try to exchange "Child Update" messages with parent and
147        # then detect that parent is not responding and detach.
148
149        child.set_child_supervision_check_timeout(25)
150
151        self.simulator.go(35)
152        self.assertEqual(child.get_state(), 'detached')
153        self.assertTrue(int(child.get_child_supervision_check_failure_counter()) > 0)
154
155        # Disable allowlist on parent. Child should be able to attach again.
156
157        parent.disable_allowlist()
158        self.simulator.go(30)
159        self.assertEqual(child.get_state(), 'child')
160        child.reset_child_supervision_check_failure_counter()
161        self.assertEqual(int(child.get_child_supervision_check_failure_counter()), 0)
162
163        # Set the supervision interval to zero on child (child is asking
164        # parent not to supervise it anymore). This practically behaves
165        # the same as if parent does not support child supervision
166        # feature.
167
168        child.set_child_supervision_interval(0)
169        child.set_child_supervision_check_timeout(25)
170        self.simulator.go(2)
171
172        self.assertEqual(int(child.get_child_supervision_interval()), 0)
173        self.assertEqual(int(child.get_child_supervision_check_timeout()), 25)
174
175        table = parent.get_child_table()
176        self.assertEqual(len(table), 1)
177        self.assertEqual(table[2]['suprvsn'], int(child.get_child_supervision_interval()))
178
179        # Wait for multiple check timeouts. The child should still stay
180        # attached to parent.
181
182        self.simulator.go(100)
183        self.assertEqual(child.get_state(), 'child')
184        self.assertEqual(len(parent.get_child_table()), 1)
185        self.assertTrue(int(child.get_child_supervision_check_failure_counter()) > 0)
186
187
188if __name__ == '__main__':
189    unittest.main()
190