Skip to content

Commit 297fb50

Browse files
authored
[nexus] migrate anycast locator test to Nexus (openthread#12962)
This commit migrates the anycast locator test from the thread-cert functional tests to the Nexus simulation framework. The new Nexus test 'test_anycast_locator.cpp' covers: - Anycast Locator (ALOC) resolution for the Leader from all nodes. - Custom service ALOC resolution when only one node provides it. - Closest-node ALOC resolution when multiple nodes provide the same service in a line topology (LEADER-R1-R2-R3-R4). - Verification that nodes resolve to the nearest service instance. The original Python test 'test_anycast_locator.py' is removed as its functionality is now fully covered by the Nexus test.
1 parent db7fd23 commit 297fb50

3 files changed

Lines changed: 239 additions & 167 deletions

File tree

tests/nexus/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,7 @@ ot_nexus_test(1_4_CS_TC_3 "cert;nexus")
384384
ot_nexus_test(inform_previous_parent_on_reattach "cert;nexus")
385385

386386
# Misc tests
387+
ot_nexus_test(anycast_locator "core;nexus")
387388
ot_nexus_test(br_upgrade_router_role "core;nexus")
388389
ot_nexus_test(border_admitter "core;nexus")
389390
ot_nexus_test(border_agent "core;nexus")
Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
/*
2+
* Copyright (c) 2026, The OpenThread Authors.
3+
* All rights reserved.
4+
*
5+
* Redistribution and use in source and binary forms, with or without
6+
* modification, are permitted provided that the following conditions are met:
7+
* 1. Redistributions of source code must retain the above copyright
8+
* notice, this list of conditions and the following disclaimer.
9+
* 2. Redistributions in binary form must reproduce the above copyright
10+
* notice, this list of conditions and the following disclaimer in the
11+
* documentation and/or other materials provided with the distribution.
12+
* 3. Neither the name of the copyright holder nor the
13+
* names of its contributors may be used to endorse or promote products
14+
* derived from this software without specific prior written permission.
15+
*
16+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26+
* POSSIBILITY OF SUCH DAMAGE.
27+
*/
28+
29+
#include <initializer_list>
30+
#include <stdio.h>
31+
#include <string.h>
32+
33+
#include "platform/nexus_core.hpp"
34+
#include "platform/nexus_node.hpp"
35+
36+
namespace ot {
37+
namespace Nexus {
38+
39+
struct LocateResult
40+
{
41+
Error mError;
42+
Ip6::Address mMeshLocalAddress;
43+
uint16_t mRloc16;
44+
bool mCallbackInvoked;
45+
46+
void Clear(void)
47+
{
48+
mError = kErrorNone;
49+
mMeshLocalAddress.Clear();
50+
mRloc16 = 0;
51+
mCallbackInvoked = false;
52+
}
53+
};
54+
55+
static void HandleLocateResult(void *aContext, otError aError, const otIp6Address *aAddress, uint16_t aRloc16)
56+
{
57+
LocateResult *result = static_cast<LocateResult *>(aContext);
58+
59+
result->mError = aError;
60+
result->mCallbackInvoked = true;
61+
result->mRloc16 = aRloc16;
62+
63+
if (aAddress != nullptr)
64+
{
65+
result->mMeshLocalAddress = AsCoreType(aAddress);
66+
}
67+
}
68+
69+
static void Locate(Core &aNexus, Node &aNode, const Ip6::Address &aAnycastAddress, LocateResult &aResult)
70+
{
71+
aResult.Clear();
72+
SuccessOrQuit(aNode.Get<AnycastLocator>().Locate(aAnycastAddress, HandleLocateResult, &aResult));
73+
74+
for (uint32_t i = 0; i < 10000; i++)
75+
{
76+
aNexus.AdvanceTime(10);
77+
if (aResult.mCallbackInvoked)
78+
{
79+
break;
80+
}
81+
}
82+
83+
VerifyOrQuit(aResult.mCallbackInvoked);
84+
SuccessOrQuit(aResult.mError);
85+
}
86+
87+
void TestAnycastLocator(void)
88+
{
89+
// Topology:
90+
// LEADER -- ROUTER1 -- ROUTER2 -- ROUTER3 -- ROUTER4
91+
92+
Core nexus;
93+
94+
Node &leader = nexus.CreateNode();
95+
Node &router1 = nexus.CreateNode();
96+
Node &router2 = nexus.CreateNode();
97+
Node &router3 = nexus.CreateNode();
98+
Node &router4 = nexus.CreateNode();
99+
100+
leader.SetName("LEADER");
101+
router1.SetName("ROUTER1");
102+
router2.SetName("ROUTER2");
103+
router3.SetName("ROUTER3");
104+
router4.SetName("ROUTER4");
105+
106+
Node *nodes[] = {&leader, &router1, &router2, &router3, &router4};
107+
108+
nexus.AdvanceTime(0);
109+
110+
Log("---------------------------------------------------------------------------------------");
111+
Log("Form network with line topology");
112+
113+
AllowLinkBetween(leader, router1);
114+
AllowLinkBetween(router1, router2);
115+
AllowLinkBetween(router2, router3);
116+
AllowLinkBetween(router3, router4);
117+
118+
leader.Form();
119+
nexus.AdvanceTime(20 * 1000);
120+
121+
for (uint8_t i = 1; i < GetArrayLength(nodes); i++)
122+
{
123+
nodes[i]->Join(*nodes[i - 1]);
124+
nexus.AdvanceTime(20 * 1000);
125+
}
126+
127+
// Wait for all to become routers and network to stabilize
128+
nexus.AdvanceTime(300 * 1000);
129+
130+
for (Node *node : nodes)
131+
{
132+
VerifyOrQuit(node->Get<Mle::Mle>().IsRouter() || node->Get<Mle::Mle>().IsLeader());
133+
}
134+
135+
Log("---------------------------------------------------------------------------------------");
136+
Log("1. Locate leader ALOC from all nodes");
137+
138+
Ip6::Address leaderAloc;
139+
leader.Get<Mle::Mle>().GetLeaderAloc(leaderAloc);
140+
141+
LocateResult result;
142+
143+
for (Node *node : nodes)
144+
{
145+
Log("Node %s locating leader ALOC", node->GetName());
146+
Locate(nexus, *node, leaderAloc, result);
147+
VerifyOrQuit(result.mMeshLocalAddress == leader.Get<Mle::Mle>().GetMeshLocalEid());
148+
VerifyOrQuit(result.mRloc16 == leader.Get<Mle::Mle>().GetRloc16());
149+
}
150+
151+
Log("---------------------------------------------------------------------------------------");
152+
Log("2. Add a service on router4 and locate its ALOC");
153+
154+
uint32_t enterpriseNumber = 44970;
155+
NetworkData::ServiceData serviceData;
156+
NetworkData::ServerData serverData;
157+
uint8_t sData[] = {0x57, 0x12, 0x34};
158+
uint8_t rData[] = {0x00};
159+
160+
serviceData.Init(sData, sizeof(sData));
161+
serverData.Init(rData, sizeof(rData));
162+
163+
SuccessOrQuit(router4.Get<NetworkData::Local>().AddService(enterpriseNumber, serviceData, true, serverData));
164+
router4.Get<NetworkData::Notifier>().HandleServerDataUpdated();
165+
nexus.AdvanceTime(20 * 1000);
166+
167+
// Get the service ALOC from router4
168+
Ip6::Address serviceAloc;
169+
bool found = false;
170+
171+
for (const Ip6::Netif::UnicastAddress &addr : router4.Get<ThreadNetif>().GetUnicastAddresses())
172+
{
173+
if (router4.Get<Mle::Mle>().IsAnycastLocator(addr.GetAddress()) &&
174+
addr.GetAddress().GetIid().IsAnycastLocator())
175+
{
176+
// The leader ALOC is also an ALOC, so we need to make sure it's not the leader ALOC.
177+
if (addr.GetAddress() != leaderAloc)
178+
{
179+
serviceAloc = addr.GetAddress();
180+
found = true;
181+
break;
182+
}
183+
}
184+
}
185+
VerifyOrQuit(found);
186+
187+
for (Node *node : nodes)
188+
{
189+
Log("Node %s locating service ALOC", node->GetName());
190+
Locate(nexus, *node, serviceAloc, result);
191+
VerifyOrQuit(result.mMeshLocalAddress == router4.Get<Mle::Mle>().GetMeshLocalEid());
192+
VerifyOrQuit(result.mRloc16 == router4.Get<Mle::Mle>().GetRloc16());
193+
}
194+
195+
Log("---------------------------------------------------------------------------------------");
196+
Log("3. Add same service on leader and ensure we locate the closest ALOC destination");
197+
198+
SuccessOrQuit(leader.Get<NetworkData::Local>().AddService(enterpriseNumber, serviceData, true, serverData));
199+
leader.Get<NetworkData::Notifier>().HandleServerDataUpdated();
200+
nexus.AdvanceTime(20 * 1000);
201+
202+
// leader and router1 should locate leader as closest service ALOC
203+
for (Node *node : {&leader, &router1})
204+
{
205+
Log("Node %s locating service ALOC (expected leader)", node->GetName());
206+
Locate(nexus, *node, serviceAloc, result);
207+
VerifyOrQuit(result.mMeshLocalAddress == leader.Get<Mle::Mle>().GetMeshLocalEid());
208+
VerifyOrQuit(result.mRloc16 == leader.Get<Mle::Mle>().GetRloc16());
209+
}
210+
211+
// router3 and router4 should locate router4
212+
for (Node *node : {&router3, &router4})
213+
{
214+
Log("Node %s locating service ALOC (expected router4)", node->GetName());
215+
Locate(nexus, *node, serviceAloc, result);
216+
VerifyOrQuit(result.mMeshLocalAddress == router4.Get<Mle::Mle>().GetMeshLocalEid());
217+
VerifyOrQuit(result.mRloc16 == router4.Get<Mle::Mle>().GetRloc16());
218+
}
219+
220+
// router2 is in middle and can locate either one
221+
Log("Node %s locating service ALOC (expected either leader or router4)", router2.GetName());
222+
Locate(nexus, router2, serviceAloc, result);
223+
VerifyOrQuit((result.mMeshLocalAddress == leader.Get<Mle::Mle>().GetMeshLocalEid() &&
224+
result.mRloc16 == leader.Get<Mle::Mle>().GetRloc16()) ||
225+
(result.mMeshLocalAddress == router4.Get<Mle::Mle>().GetMeshLocalEid() &&
226+
result.mRloc16 == router4.Get<Mle::Mle>().GetRloc16()));
227+
228+
Log("All tests passed");
229+
}
230+
231+
} // namespace Nexus
232+
} // namespace ot
233+
234+
int main(void)
235+
{
236+
ot::Nexus::TestAnycastLocator();
237+
return 0;
238+
}

0 commit comments

Comments
 (0)