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: Service ALOC destination route lookup
36#
37# Test topology:
38#
39#     r1---- r2 ---- r3 ---- r4
40#     |
41#     |
42#    fed1
43#
44# The same service is added on `r4` and `fed1`.
45
46test_name = __file__[:-3] if __file__.endswith('.py') else __file__
47print('-' * 120)
48print('Starting \'{}\''.format(test_name))
49
50# -----------------------------------------------------------------------------------------------------------------------
51# Creating `cli.Nodes` instances
52
53speedup = 40
54cli.Node.set_time_speedup_factor(speedup)
55
56r1 = cli.Node()
57r2 = cli.Node()
58r3 = cli.Node()
59r4 = cli.Node()
60fed1 = cli.Node()
61
62nodes = [r1, r2, r3, r4, fed1]
63
64# -----------------------------------------------------------------------------------------------------------------------
65# Form topology
66
67r1.allowlist_node(r2)
68r1.allowlist_node(fed1)
69
70r2.allowlist_node(r1)
71r2.allowlist_node(r3)
72
73r3.allowlist_node(r2)
74r3.allowlist_node(r4)
75
76r4.allowlist_node(r3)
77
78fed1.allowlist_node(r1)
79
80r1.form("srv-aloc")
81r2.join(r1)
82r3.join(r2)
83r4.join(r3)
84fed1.join(r1, cli.JOIN_TYPE_REED)
85
86verify(r1.get_state() == 'leader')
87verify(r2.get_state() == 'router')
88verify(r3.get_state() == 'router')
89verify(r4.get_state() == 'router')
90verify(fed1.get_state() == 'child')
91
92# -----------------------------------------------------------------------------------------------------------------------
93# Test Implementation
94
95# Wait till first router has either established a link or
96# has a valid "next hop" towards all other routers.
97
98r1_rloc16 = int(r1.get_rloc16(), 16)
99
100
101def check_r1_router_table():
102    table = r1.get_router_table()
103    verify(len(table) == 4)
104    for entry in table:
105        verify(int(entry['RLOC16'], 0) == r1_rloc16 or int(entry['Link']) == 1 or int(entry['Next Hop']) != 63)
106
107
108verify_within(check_r1_router_table, 120)
109
110# Add the same service on `r4` and `fed1`
111
112r4.cli('service add 44970 11 00')
113r4.register_netdata()
114
115fed1.cli('service add 44970 11 00')
116fed1.register_netdata()
117
118
119def check_netdata_services(expected_num_services):
120    # Check that all nodes see the `expected_num_services` service
121    # entries in network data.
122    for node in nodes:
123        verify(len(node.get_netdata_services()) == expected_num_services)
124
125
126verify_within(check_netdata_services, 100, 2)
127
128# Determine the ALOC address of the added service.
129
130aloc = r4.get_mesh_local_prefix().split('/')[0] + 'ff:fe00:fc10'
131
132# Ping ALOC address from `r3` and verify that `r4` responds.
133# `r4` should be chosen due to its shorter path cost from `r3`.
134
135old_counters = r4.get_ip_counters()
136r3.ping(aloc)
137new_counters = r4.get_ip_counters()
138
139verify(int(new_counters['RxSuccess']) > int(old_counters['RxSuccess']))
140verify(int(new_counters['TxSuccess']) > int(old_counters['TxSuccess']))
141
142# Ping ALOC address from `r1` and verify that `fed1` responds.
143# `fed1` should be chosen due to its shorter path cost from `r1`.
144
145old_counters = fed1.get_ip_counters()
146r1.ping(aloc)
147new_counters = fed1.get_ip_counters()
148
149verify(int(new_counters['RxSuccess']) > int(old_counters['RxSuccess']))
150verify(int(new_counters['TxSuccess']) > int(old_counters['TxSuccess']))
151
152# Ping ALOC address from `r2` and verify that `r4` responds.
153# Both `r4` and `fed1` have the same path cost, but `r4` is
154# preferred because it is acting as a router.
155
156old_counters = r4.get_ip_counters()
157r2.ping(aloc)
158new_counters = r4.get_ip_counters()
159
160verify(int(new_counters['RxSuccess']) > int(old_counters['RxSuccess']))
161verify(int(new_counters['TxSuccess']) > int(old_counters['TxSuccess']))
162
163# -----------------------------------------------------------------------------------------------------------------------
164# Test finished
165
166cli.Node.finalize_all_nodes()
167
168print('\'{}\' passed.'.format(test_name))
169