1#!/usr/bin/env python3
2#
3#  Copyright (c) 2024, 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 ipaddress
31import unittest
32import math
33
34import command
35import config
36import thread_cert
37
38# Test description:
39#
40#   This test verifies key rotation and key guard time mechanisms.
41#
42#
43# Topology:
44#
45#   leader ---  router
46#    |    \
47#    |     \
48#  child   reed
49#
50
51LEADER = 1
52CHILD = 2
53REED = 3
54ROUTER = 4
55
56
57class MleMsgKeySeqJump(thread_cert.TestCase):
58    USE_MESSAGE_FACTORY = False
59    SUPPORT_NCP = False
60
61    TOPOLOGY = {
62        LEADER: {
63            'name': 'LEADER',
64            'mode': 'rdn',
65        },
66        CHILD: {
67            'name': 'CHILD',
68            'is_mtd': True,
69            'mode': 'rn',
70        },
71        REED: {
72            'name': 'REED',
73            'mode': 'rn'
74        },
75        ROUTER: {
76            'name': 'ROUTER',
77            'mode': 'rdn',
78        },
79    }
80
81    def test(self):
82        leader = self.nodes[LEADER]
83        child = self.nodes[CHILD]
84        reed = self.nodes[REED]
85        router = self.nodes[ROUTER]
86
87        nodes = [leader, child, reed, router]
88
89        #-------------------------------------------------------------------
90        # Form the network.
91
92        for node in nodes:
93            node.set_key_sequence_counter(0)
94
95        leader.start()
96        self.simulator.go(config.LEADER_STARTUP_DELAY)
97        self.assertEqual(leader.get_state(), 'leader')
98
99        child.start()
100        reed.start()
101        self.simulator.go(5)
102        self.assertEqual(child.get_state(), 'child')
103        self.assertEqual(reed.get_state(), 'child')
104
105        router.start()
106        self.simulator.go(config.ROUTER_STARTUP_DELAY)
107        self.assertEqual(router.get_state(), 'router')
108
109        #-------------------------------------------------------------------
110        # Validate the initial key seq counter and key switch guard time
111
112        for node in nodes:
113            self.assertEqual(node.get_key_sequence_counter(), 0)
114            self.assertEqual(node.get_key_switch_guardtime(), 624)
115
116        #-------------------------------------------------------------------
117        # Change the key rotation time a bunch of times and make sure that
118        # the key switch guard time is properly changed (should be set
119        # to 93% of the rotation time).
120
121        for rotation_time in [100, 1, 10, 888, 2]:
122            reed.start_dataset_updater(security_policy=[rotation_time, 'onrc'])
123            guardtime = math.floor(rotation_time * 93 / 100) if rotation_time >= 2 else 1
124            self.simulator.go(100)
125            for node in nodes:
126                self.assertEqual(node.get_key_switch_guardtime(), guardtime)
127
128        #-------------------------------------------------------------------
129        # Wait for key rotation time (2 hours) and check that all nodes
130        # moved to the next key seq counter
131
132        self.simulator.go(2 * 60 * 60)
133        for node in nodes:
134            self.assertEqual(node.get_key_sequence_counter(), 1)
135
136        #-------------------------------------------------------------------
137        # Manually increment the key sequence counter on leader and make
138        # sure other nodes are not updated due to key guard time.
139
140        router.set_key_sequence_counter(2)
141
142        self.simulator.go(50 * 60)
143
144        self.assertEqual(router.get_key_sequence_counter(), 2)
145
146        for node in [leader, reed, child]:
147            self.assertEqual(node.get_key_sequence_counter(), 1)
148
149        #-------------------------------------------------------------------
150        # Make sure nodes can communicate with each other.
151
152        self.assertTrue(leader.ping(router.get_mleid()))
153        self.assertTrue(router.ping(child.get_mleid()))
154
155        #-------------------------------------------------------------------
156        # Wait for rotation time to expire. Validate that the `router`
157        # has moved to key seq `3` and all other nodes also followed.
158
159        self.simulator.go(75 * 60)
160
161        self.assertEqual(router.get_key_sequence_counter(), 3)
162
163        for node in nodes:
164            self.assertEqual(node.get_key_sequence_counter(), 3)
165
166        self.assertTrue(leader.ping(router.get_mleid()))
167        self.assertTrue(router.ping(child.get_mleid()))
168
169
170if __name__ == '__main__':
171    unittest.main()
172