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
29from cli import verify
30from cli import verify_within
31import cli
32import time
33
34# -----------------------------------------------------------------------------------------------------------------------
35# Test description:
36#
37# Validate parent search on FTD child (FED/REED).
38#
39# Topology:
40#
41# The link between `r1` and `fed` is configured to be poor, ensuring
42# `fed` will search for a new parent. `r2` is configured with
43# `set_child_max(1)`, allowing only one child. `c2` is already
44# attached to `r2`, preventing `r2` from accepting `fed` as a child.
45# Later, `r3` is added to the network, and the test validates that
46# `fed` successfully switches to `r3` as its parent.
47#
48#
49#    r1 ---- r2             r3 -- r1 -- r2
50#     .      |                \         |
51#     .      |      ==>        \        |
52#   fed      c2                fed     c2
53
54test_name = __file__[:-3] if __file__.endswith('.py') else __file__
55print('-' * 120)
56print('Starting \'{}\''.format(test_name))
57
58# -----------------------------------------------------------------------------------------------------------------------
59# Creating `cli.Node` instances
60
61speedup = 40
62cli.Node.set_time_speedup_factor(speedup)
63
64fed = cli.Node()
65r1 = cli.Node()
66r2 = cli.Node()
67c2 = cli.Node()
68r3 = cli.Node()
69
70# -----------------------------------------------------------------------------------------------------------------------
71# Test Implementation
72
73r1.set_macfilter_lqi_to_node(fed, 1)
74fed.set_macfilter_lqi_to_node(r1, 1)
75
76r1.allowlist_node(fed)
77r1.allowlist_node(r2)
78r1.allowlist_node(r3)
79
80r2.allowlist_node(fed)
81r2.allowlist_node(r1)
82r2.allowlist_node(c2)
83r2.allowlist_node(r3)
84
85c2.allowlist_node(r2)
86
87# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
88# `r2` is allowed to have one child only (`c2`)
89
90r2.set_child_max(1)
91verify(int(r2.get_child_max()) == 1)
92
93# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
94# Form the network.
95
96r1.form('fed-prnt-srch')
97fed.join(r1, cli.JOIN_TYPE_REED)
98r2.join(r1)
99c2.join(r2, cli.JOIN_TYPE_END_DEVICE)
100
101verify(r1.get_state() == 'leader')
102verify(fed.get_state() == 'child')
103verify(r2.get_state() == 'router')
104verify(c2.get_state() == 'child')
105
106# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
107# Check that `c2` is attached to `r2`.
108
109info = c2.get_parent_info()
110verify(int(info['Rloc'], 16) == int(r2.get_rloc16(), 16))
111
112# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
113# Check that `fed` is attached to `r1` and has poor link quality.
114
115info = fed.get_parent_info()
116verify(int(info['Rloc'], 16) == int(r1.get_rloc16(), 16))
117verify(int(info['Link Quality In']) == 1)
118verify(int(info['Link Quality Out']) == 1)
119
120verify(int(cli.Node.parse_list(fed.get_mle_counter())['Better Parent Attach Attempts']) == 0)
121
122# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
123# The `toranj` config sets `PARENT_SEARCH_CHECK_INTERVAL` to 120 seconds.
124
125time.sleep(130 / speedup)
126
127# Verify that parent search mechanism did trigger an attach attempt on
128# `fed`.
129
130verify(int(cli.Node.parse_list(fed.get_mle_counter())['Better Parent Attach Attempts']) == 1)
131
132# Since `r2` can have one child only and already has `c2`, the attach
133# attempt should fail and `fed` should still have `r1` as its
134# parent.
135
136info = fed.get_parent_info()
137verify(int(info['Rloc'], 16) == int(r1.get_rloc16(), 16))
138
139# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
140# Ensure that `fed` does not initiate any further attach attempts, as
141# the only available parent (`r2`) should be in the "reselect timeout"
142# period. This should prevent `fed` from attempting to attach to `r2`.
143
144time.sleep(150 / speedup)
145
146verify(int(cli.Node.parse_list(fed.get_mle_counter())['Better Parent Attach Attempts']) == 1)
147
148# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
149# Add `r3` to the network and wait for `PARENT_SEARCH_CHECK_INTERVAL`.
150# This should trigger a parent search on `fed`. Validate that
151# `fed` successfully switches to `r3` as its new parent.
152
153r3.join(r1)
154verify(r3.get_state() == 'router')
155
156time.sleep(130 / speedup)
157
158counters = cli.Node.parse_list(fed.get_mle_counter())
159verify(int(counters['Better Parent Attach Attempts']) == 2)
160verify(int(counters['Parent Changes']) == 1)
161
162info = fed.get_parent_info()
163verify(int(info['Rloc'], 16) == int(r3.get_rloc16(), 16))
164verify(int(info['Link Quality In']) == 3)
165verify(int(info['Link Quality Out']) == 3)
166
167# -----------------------------------------------------------------------------------------------------------------------
168# Test finished
169
170cli.Node.finalize_all_nodes()
171
172print('\'{}\' passed.'.format(test_name))
173