1#!/usr/bin/env python3
2#
3#  Copyright (c) 2023, 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: Network Data Context ID assignment and reuse delay.
36#
37# Network topology
38#
39#      r1 ---- r2 ---- r3
40#
41
42test_name = __file__[:-3] if __file__.endswith('.py') else __file__
43print('-' * 120)
44print('Starting \'{}\''.format(test_name))
45
46# -----------------------------------------------------------------------------------------------------------------------
47# Creating `cli.Node` instances
48
49speedup = 40
50cli.Node.set_time_speedup_factor(speedup)
51
52r1 = cli.Node()
53r2 = cli.Node()
54r3 = cli.Node()
55
56# -----------------------------------------------------------------------------------------------------------------------
57# Form topology
58
59r1.allowlist_node(r2)
60
61r2.allowlist_node(r1)
62r2.allowlist_node(r3)
63
64r3.allowlist_node(r2)
65
66r1.form('netdata')
67r2.join(r1)
68r3.join(r1)
69
70verify(r1.get_state() == 'leader')
71verify(r2.get_state() == 'router')
72verify(r3.get_state() == 'router')
73
74# -----------------------------------------------------------------------------------------------------------------------
75# Test Implementation
76
77# Check the default reuse delay on `r1` to be 5 minutes.
78
79verify(int(r1.get_context_reuse_delay()) == 5 * 60)
80
81# Change the reuse delay on `r1` (leader) to 5 seconds.
82
83r1.set_context_reuse_delay(5)
84verify(int(r1.get_context_reuse_delay()) == 5)
85
86# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
87# Add an on-mesh prefix from each router `r1`, r2`, and `r3`.
88# Validate that netdata is updated and Context IDs are assigned.
89
90r1.add_prefix('fd00:1::/64', 'poas')
91r1.register_netdata()
92
93r2.add_prefix('fd00:2::/64', 'poas')
94r2.register_netdata()
95
96r3.add_prefix('fd00:3::/64', 'poas')
97r3.add_route('fd00:beef::/64', 's')
98r3.register_netdata()
99
100
101def check_netdata_1():
102    global netdata
103    netdata = r1.get_netdata()
104    verify(len(netdata['prefixes']) == 3)
105    verify(len(netdata['routes']) == 1)
106
107
108verify_within(check_netdata_1, 5)
109
110contexts = netdata['contexts']
111verify(len(contexts) == 3)
112verify(any([context.startswith('fd00:1:0:0::/64') for context in contexts]))
113verify(any([context.startswith('fd00:2:0:0::/64') for context in contexts]))
114verify(any([context.startswith('fd00:3:0:0::/64') for context in contexts]))
115for context in contexts:
116    verify(context.endswith('c'))
117
118# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
119# Remove prefix on `r3`. Validate that Context compress flag
120# is cleared for the removed prefix.
121
122r3.remove_prefix('fd00:3::/64')
123r3.register_netdata()
124
125
126def check_netdata_2():
127    global netdata, versions
128    versions = r1.get_netdata_versions()
129    netdata = r1.get_netdata()
130    verify(len(netdata['prefixes']) == 2)
131    verify(len(netdata['routes']) == 1)
132
133
134verify_within(check_netdata_2, 5)
135
136contexts = netdata['contexts']
137verify(len(contexts) == 3)
138verify(any([context.startswith('fd00:1:0:0::/64') for context in contexts]))
139verify(any([context.startswith('fd00:2:0:0::/64') for context in contexts]))
140verify(any([context.startswith('fd00:3:0:0::/64') for context in contexts]))
141for context in contexts:
142    if context.startswith('fd00:1:0:0::/64') or context.startswith('fd00:2:0:0::/64'):
143        verify(context.endswith('c'))
144    else:
145        verify(context.endswith('-'))
146
147# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
148# Validate that the prefix context is removed within reuse delay
149# time (which is set to 5 seconds on leader).
150
151
152def check_netdata_3():
153    global netdata
154    netdata = r1.get_netdata()
155    verify(len(netdata['prefixes']) == 2)
156    verify(len(netdata['routes']) == 1)
157    verify(len(netdata['contexts']) == 2)
158
159
160verify_within(check_netdata_3, 5)
161old_versions = versions
162versions = r1.get_netdata_versions()
163verify(versions != old_versions)
164
165# Make sure netdata does not change afterwards
166
167time.sleep(5 * 3 / speedup)
168
169verify(versions == r1.get_netdata_versions())
170netdata = r1.get_netdata()
171verify(len(netdata['prefixes']) == 2)
172verify(len(netdata['routes']) == 1)
173verify(len(netdata['contexts']) == 2)
174
175# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
176# Add two new on-mesh prefixes from `r3`. Validate that they get
177# assigned Context IDs.
178
179r3.add_prefix('fd00:4::/64', 'poas')
180r3.register_netdata()
181
182r3.add_prefix('fd00:3::/64', 'poas')
183r3.register_netdata()
184
185
186def check_netdata_4():
187    global netdata
188    netdata = r1.get_netdata()
189    verify(len(netdata['prefixes']) == 4)
190    verify(len(netdata['routes']) == 1)
191    verify(len(netdata['contexts']) == 4)
192
193
194verify_within(check_netdata_4, 5)
195
196contexts = netdata['contexts']
197verify(len(contexts) == 4)
198verify(any([context.startswith('fd00:1:0:0::/64') for context in contexts]))
199verify(any([context.startswith('fd00:2:0:0::/64') for context in contexts]))
200verify(any([context.startswith('fd00:3:0:0::/64') for context in contexts]))
201verify(any([context.startswith('fd00:4:0:0::/64') for context in contexts]))
202for context in contexts:
203    verify(context.endswith('c'))
204
205# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
206# Remove prefixes on `r1` and `r2` and re-add them back quickly both
207# from `r2`. Validate that they get the same context IDs as before.
208
209for context in contexts:
210    if context.startswith('fd00:1:0:0::/64'):
211        cid1 = int(context.split()[1])
212    elif context.startswith('fd00:2:0:0::/64'):
213        cid2 = int(context.split()[1])
214
215r1.remove_prefix('fd00:1:0:0::/64')
216r1.register_netdata()
217
218r2.remove_prefix('fd00:2:0:0::/64')
219r2.register_netdata()
220
221
222def check_netdata_5():
223    global netdata
224    netdata = r1.get_netdata()
225    verify(len(netdata['prefixes']) == 2)
226    verify(len(netdata['routes']) == 1)
227
228
229verify_within(check_netdata_5, 5)
230
231contexts = netdata['contexts']
232verify(len(contexts) == 4)
233verify(any([context.startswith('fd00:1:0:0::/64') for context in contexts]))
234verify(any([context.startswith('fd00:2:0:0::/64') for context in contexts]))
235verify(any([context.startswith('fd00:3:0:0::/64') for context in contexts]))
236verify(any([context.startswith('fd00:4:0:0::/64') for context in contexts]))
237for context in contexts:
238    if context.startswith('fd00:1:0:0::/64') or context.startswith('fd00:2:0:0::/64'):
239        verify(context.endswith('-'))
240    else:
241        verify(context.endswith('c'))
242
243# Re-add both prefixes (now from `r2`) before CID remove delay time
244# is expired.
245
246r2.add_prefix('fd00:1::/64', 'poas')
247r2.add_prefix('fd00:2::/64', 'poas')
248r2.register_netdata()
249
250
251def check_netdata_6():
252    global netdata
253    netdata = r1.get_netdata()
254    verify(len(netdata['prefixes']) == 4)
255    verify(len(netdata['routes']) == 1)
256
257
258verify_within(check_netdata_6, 5)
259
260contexts = netdata['contexts']
261verify(len(contexts) == 4)
262verify(any([context.startswith('fd00:1:0:0::/64') for context in contexts]))
263verify(any([context.startswith('fd00:2:0:0::/64') for context in contexts]))
264verify(any([context.startswith('fd00:3:0:0::/64') for context in contexts]))
265verify(any([context.startswith('fd00:4:0:0::/64') for context in contexts]))
266for context in contexts:
267    verify(context.endswith('c'))
268    if context.startswith('fd00:1:0:0::/64'):
269        verify(int(context.split()[1]) == cid1)
270    elif context.startswith('fd00:2:0:0::/64'):
271        verify(int(context.split()[1]) == cid2)
272
273# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
274# Remove two prefixes on `r3`. Add one back as an external route from
275# `r1`. Also add a new prefix from `r1`. Validate that the context IDs
276# for the removed on-mesh prefixes are removed after reuse delay time
277# and that the new prefix gets a different Context ID.
278
279# Remember the CID used.
280for context in contexts:
281    if context.startswith('fd00:3:0:0::/64'):
282        cid3 = int(context.split()[1])
283    elif context.startswith('fd00:4:0:0::/64'):
284        cid4 = int(context.split()[1])
285
286r3.remove_prefix('fd00:3:0:0::/64')
287r3.remove_prefix('fd00:4:0:0::/64')
288r3.register_netdata()
289
290
291def check_netdata_7():
292    global netdata
293    netdata = r1.get_netdata()
294    verify(len(netdata['prefixes']) == 2)
295    verify(len(netdata['routes']) == 1)
296
297
298verify_within(check_netdata_7, 5)
299
300contexts = netdata['contexts']
301verify(len(contexts) == 4)
302verify(any([context.startswith('fd00:1:0:0::/64') for context in contexts]))
303verify(any([context.startswith('fd00:2:0:0::/64') for context in contexts]))
304verify(any([context.startswith('fd00:3:0:0::/64') for context in contexts]))
305verify(any([context.startswith('fd00:4:0:0::/64') for context in contexts]))
306for context in contexts:
307    if context.startswith('fd00:3:0:0::/64') or context.startswith('fd00:4:0:0::/64'):
308        verify(context.endswith('-'))
309    else:
310        verify(context.endswith('c'))
311
312# Add first one removed as route and add a new prefix.
313
314r1.add_route('fd00:3:0:0::/64', 's')
315r1.add_prefix('fd00:5:0:0::/64', 'poas')
316r1.register_netdata()
317
318
319def check_netdata_8():
320    global netdata
321    netdata = r1.get_netdata()
322    verify(len(netdata['prefixes']) == 3)
323    verify(len(netdata['routes']) == 2)
324    verify(len(netdata['contexts']) == 3)
325
326
327verify_within(check_netdata_8, 5)
328
329contexts = netdata['contexts']
330verify(len(contexts) == 3)
331verify(any([context.startswith('fd00:1:0:0::/64') for context in contexts]))
332verify(any([context.startswith('fd00:2:0:0::/64') for context in contexts]))
333verify(any([context.startswith('fd00:5:0:0::/64') for context in contexts]))
334
335for context in contexts:
336    verify(context.endswith('c'))
337    if context.startswith('fd00:5:0:0::/64'):
338        verify(not int(context.split()[1]) in [cid3, cid4])
339
340# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
341# Remove a prefix on `r2`, wait one second and remove a second
342# prefix. Validate the Context IDs for both are removed.
343
344r2.remove_prefix('fd00:1::/64')
345r2.register_netdata()
346
347time.sleep(1 / speedup)
348
349r2.remove_prefix('fd00:2::/64')
350r2.register_netdata()
351
352
353def check_netdata_9():
354    global netdata
355    netdata = r1.get_netdata()
356    verify(len(netdata['prefixes']) == 1)
357    verify(len(netdata['routes']) == 2)
358    verify(len(netdata['contexts']) == 1)
359
360
361verify_within(check_netdata_9, 5)
362
363contexts = netdata['contexts']
364verify(len(contexts) == 1)
365verify(contexts[0].startswith('fd00:5:0:0::/64'))
366verify(contexts[0].endswith('c'))
367
368# -----------------------------------------------------------------------------------------------------------------------
369# Test finished
370
371cli.Node.finalize_all_nodes()
372
373print('\'{}\' passed.'.format(test_name))
374