Skip to content

Commit fb274ef

Browse files
authored
[nexus] migrate test_history_tracker to Nexus (openthread#13023)
This commit migrates the test_history_tracker.py test from the thread-cert test suite to the Nexus test framework as a new C++ test. The new C++ test, test_history_tracker.cpp, covers: - Role changes (detached -> leader -> disabled) - NetInfo age up to 49 days - Child mode Rn changes - Ping between leader and child, verifying message types, checksums, priority, and success flags It directly uses HistoryTracker::Local methods instead of the C APIs.
1 parent d4a7f2d commit fb274ef

3 files changed

Lines changed: 236 additions & 212 deletions

File tree

tests/nexus/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,7 @@ ot_nexus_test(dnssd_name_with_special_chars "core;nexus")
402402
ot_nexus_test(dtls "core;nexus")
403403
ot_nexus_test(fed_rx_only_link_establishment "core;nexus")
404404
ot_nexus_test(form_join "core;nexus")
405+
ot_nexus_test(history_tracker "core;nexus")
405406
ot_nexus_test(ipv6_fragmentation "core;nexus")
406407
ot_nexus_test(ipv6_source_selection "core;nexus")
407408
ot_nexus_test(key_rotation_guard_time "core;nexus")
Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
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 "platform/nexus_core.hpp"
30+
#include "platform/nexus_node.hpp"
31+
32+
namespace ot {
33+
namespace Nexus {
34+
35+
void TestHistoryTracker(void)
36+
{
37+
static constexpr uint32_t kFormLeaderTimeMsec = 13 * 1000;
38+
static constexpr uint32_t kStopLeaderTimeMsec = 5 * 1000;
39+
static constexpr uint32_t kRestartLeaderTimeMsec = 25 * 1000;
40+
static constexpr uint32_t kJoinChildTimeMsec = 10 * 1000;
41+
static constexpr uint32_t kAgeVerificationWindowMsec = 10000u;
42+
43+
Core nexus;
44+
45+
Node &leader = nexus.CreateNode();
46+
Node &child = nexus.CreateNode();
47+
48+
nexus.AdvanceTime(0);
49+
50+
SuccessOrQuit(Instance::SetGlobalLogLevel(kLogLevelInfo));
51+
52+
Log("---------------------------------------------------------------------------------------");
53+
Log("Start leader and verify its netinfo history");
54+
55+
leader.Form();
56+
nexus.AdvanceTime(kFormLeaderTimeMsec);
57+
VerifyOrQuit(leader.Get<Mle::Mle>().IsLeader());
58+
59+
HistoryTracker::Iterator iter;
60+
uint32_t age;
61+
const HistoryTracker::NetworkInfo *netInfo;
62+
63+
iter.Init();
64+
netInfo = leader.Get<HistoryTracker::Local>().IterateNetInfoHistory(iter, age);
65+
VerifyOrQuit(netInfo != nullptr);
66+
VerifyOrQuit(MapEnum(netInfo->mRole) == Mle::kRoleLeader);
67+
VerifyOrQuit(netInfo->mMode.mRxOnWhenIdle);
68+
VerifyOrQuit(netInfo->mMode.mDeviceType);
69+
VerifyOrQuit(netInfo->mRloc16 == leader.Get<Mle::Mle>().GetRloc16());
70+
VerifyOrQuit(netInfo->mPartitionId == leader.Get<Mle::Mle>().GetLeaderData().GetPartitionId());
71+
72+
netInfo = leader.Get<HistoryTracker::Local>().IterateNetInfoHistory(iter, age);
73+
VerifyOrQuit(netInfo != nullptr);
74+
VerifyOrQuit(MapEnum(netInfo->mRole) == Mle::kRoleDetached);
75+
76+
Log("---------------------------------------------------------------------------------------");
77+
Log("Stop leader and verify its netinfo history contains 'disabled'");
78+
79+
leader.Get<ThreadNetif>().Down();
80+
leader.Get<Mle::Mle>().Stop();
81+
82+
nexus.AdvanceTime(kStopLeaderTimeMsec);
83+
84+
iter.Init();
85+
netInfo = leader.Get<HistoryTracker::Local>().IterateNetInfoHistory(iter, age);
86+
VerifyOrQuit(netInfo != nullptr);
87+
VerifyOrQuit(MapEnum(netInfo->mRole) == Mle::kRoleDisabled);
88+
89+
netInfo = leader.Get<HistoryTracker::Local>().IterateNetInfoHistory(iter, age);
90+
VerifyOrQuit(netInfo != nullptr);
91+
VerifyOrQuit(MapEnum(netInfo->mRole) == Mle::kRoleLeader);
92+
93+
Log("---------------------------------------------------------------------------------------");
94+
Log("Wait for 49 days and verify age calculations");
95+
96+
nexus.AdvanceTime(Time::kOneDayInMsec);
97+
98+
iter.Init();
99+
netInfo = leader.Get<HistoryTracker::Local>().IterateNetInfoHistory(iter, age);
100+
VerifyOrQuit(netInfo != nullptr);
101+
VerifyOrQuit(age >= Time::kOneDayInMsec && age < Time::kOneDayInMsec + kAgeVerificationWindowMsec);
102+
103+
nexus.AdvanceTime(Time::kOneDayInMsec);
104+
iter.Init();
105+
netInfo = leader.Get<HistoryTracker::Local>().IterateNetInfoHistory(iter, age);
106+
VerifyOrQuit(netInfo != nullptr);
107+
VerifyOrQuit(age >= 2 * Time::kOneDayInMsec && age < 2 * Time::kOneDayInMsec + kAgeVerificationWindowMsec);
108+
109+
nexus.AdvanceTime(47 * Time::kOneDayInMsec);
110+
iter.Init();
111+
netInfo = leader.Get<HistoryTracker::Local>().IterateNetInfoHistory(iter, age);
112+
VerifyOrQuit(netInfo != nullptr);
113+
VerifyOrQuit(age == HistoryTracker::kMaxAge);
114+
115+
Log("---------------------------------------------------------------------------------------");
116+
Log("Restart Leader, join Child");
117+
118+
leader.Form();
119+
nexus.AdvanceTime(kRestartLeaderTimeMsec);
120+
VerifyOrQuit(leader.Get<Mle::Mle>().IsLeader());
121+
122+
child.Join(leader, Node::kAsMed);
123+
nexus.AdvanceTime(kJoinChildTimeMsec);
124+
VerifyOrQuit(child.Get<Mle::Mle>().IsChild());
125+
126+
Log("---------------------------------------------------------------------------------------");
127+
Log("Verify child netinfo");
128+
129+
iter.Init();
130+
netInfo = child.Get<HistoryTracker::Local>().IterateNetInfoHistory(iter, age);
131+
VerifyOrQuit(netInfo != nullptr);
132+
VerifyOrQuit(MapEnum(netInfo->mRole) == Mle::kRoleChild);
133+
VerifyOrQuit(netInfo->mMode.mRxOnWhenIdle);
134+
VerifyOrQuit(!netInfo->mMode.mDeviceType);
135+
VerifyOrQuit(netInfo->mRloc16 == child.Get<Mle::Mle>().GetRloc16());
136+
VerifyOrQuit(netInfo->mPartitionId == leader.Get<Mle::Mle>().GetLeaderData().GetPartitionId());
137+
138+
netInfo = child.Get<HistoryTracker::Local>().IterateNetInfoHistory(iter, age);
139+
VerifyOrQuit(netInfo != nullptr);
140+
VerifyOrQuit(MapEnum(netInfo->mRole) == Mle::kRoleDetached);
141+
142+
Log("---------------------------------------------------------------------------------------");
143+
Log("Change child mode and verify netinfo");
144+
145+
Mle::DeviceMode mode(Mle::DeviceMode::kModeRxOnWhenIdle | Mle::DeviceMode::kModeFullThreadDevice |
146+
Mle::DeviceMode::kModeFullNetworkData);
147+
148+
SuccessOrQuit(child.Get<Mle::Mle>().SetDeviceMode(mode));
149+
nexus.AdvanceTime(kJoinChildTimeMsec);
150+
151+
iter.Init();
152+
netInfo = child.Get<HistoryTracker::Local>().IterateNetInfoHistory(iter, age);
153+
VerifyOrQuit(netInfo != nullptr);
154+
VerifyOrQuit(netInfo->mMode.mDeviceType);
155+
156+
Log("---------------------------------------------------------------------------------------");
157+
Log("Ping between leader and child and verify TX and RX message histories");
158+
159+
static constexpr uint16_t kPingSizes[] = {10, 100, 1000};
160+
for (uint16_t size : kPingSizes)
161+
{
162+
nexus.SendAndVerifyEchoRequest(leader, child.Get<Mle::Mle>().GetMeshLocalEid(), size);
163+
}
164+
165+
// Check the TX history of the leader for the 3 Echo Requests
166+
const HistoryTracker::MessageInfo *msgInfo;
167+
168+
iter.Init();
169+
for (int i = 2; i >= 0; --i)
170+
{
171+
uint16_t size = kPingSizes[i];
172+
msgInfo = leader.Get<HistoryTracker::Local>().IterateTxHistory(iter, age);
173+
VerifyOrQuit(msgInfo != nullptr);
174+
VerifyOrQuit(msgInfo->mIcmp6Type == OT_ICMP6_TYPE_ECHO_REQUEST);
175+
VerifyOrQuit(msgInfo->mIpProto == OT_IP6_PROTO_ICMP6);
176+
VerifyOrQuit(msgInfo->mPayloadLength == size + sizeof(Ip6::Icmp::Header));
177+
VerifyOrQuit(AsCoreType(&msgInfo->mSource.mAddress) == leader.Get<Mle::Mle>().GetMeshLocalEid());
178+
VerifyOrQuit(AsCoreType(&msgInfo->mDestination.mAddress) == child.Get<Mle::Mle>().GetMeshLocalEid());
179+
VerifyOrQuit(msgInfo->mLinkSecurity);
180+
VerifyOrQuit(msgInfo->mRadioIeee802154);
181+
VerifyOrQuit(msgInfo->mPriority == OT_HISTORY_TRACKER_MSG_PRIORITY_NORMAL);
182+
VerifyOrQuit(msgInfo->mNeighborRloc16 == child.Get<Mle::Mle>().GetRloc16());
183+
VerifyOrQuit(msgInfo->mChecksum != 0);
184+
VerifyOrQuit(msgInfo->mTxSuccess);
185+
}
186+
187+
// Now check the RX history of the child (should have received the 3 Echo Requests)
188+
iter.Init();
189+
for (int i = 2; i >= 0; --i)
190+
{
191+
uint16_t size = kPingSizes[i];
192+
msgInfo = child.Get<HistoryTracker::Local>().IterateRxHistory(iter, age);
193+
VerifyOrQuit(msgInfo != nullptr);
194+
VerifyOrQuit(msgInfo->mIcmp6Type == OT_ICMP6_TYPE_ECHO_REQUEST);
195+
VerifyOrQuit(msgInfo->mIpProto == OT_IP6_PROTO_ICMP6);
196+
VerifyOrQuit(msgInfo->mPayloadLength == size + sizeof(Ip6::Icmp::Header));
197+
VerifyOrQuit(AsCoreType(&msgInfo->mSource.mAddress) == leader.Get<Mle::Mle>().GetMeshLocalEid());
198+
VerifyOrQuit(AsCoreType(&msgInfo->mDestination.mAddress) == child.Get<Mle::Mle>().GetMeshLocalEid());
199+
VerifyOrQuit(msgInfo->mLinkSecurity);
200+
VerifyOrQuit(msgInfo->mRadioIeee802154);
201+
VerifyOrQuit(msgInfo->mPriority == OT_HISTORY_TRACKER_MSG_PRIORITY_NORMAL);
202+
VerifyOrQuit(msgInfo->mNeighborRloc16 == leader.Get<Mle::Mle>().GetRloc16());
203+
VerifyOrQuit(msgInfo->mChecksum != 0);
204+
}
205+
206+
// The child then replied, so let's check the RX history of the leader for the 3 Echo Replies
207+
iter.Init();
208+
uint8_t repliesFound = 0;
209+
while ((msgInfo = leader.Get<HistoryTracker::Local>().IterateRxHistory(iter, age)) != nullptr)
210+
{
211+
if (msgInfo->mIcmp6Type == OT_ICMP6_TYPE_ECHO_REPLY)
212+
{
213+
VerifyOrQuit(msgInfo->mIpProto == OT_IP6_PROTO_ICMP6);
214+
VerifyOrQuit(AsCoreType(&msgInfo->mSource.mAddress) == child.Get<Mle::Mle>().GetMeshLocalEid());
215+
VerifyOrQuit(AsCoreType(&msgInfo->mDestination.mAddress) == leader.Get<Mle::Mle>().GetMeshLocalEid());
216+
VerifyOrQuit(msgInfo->mLinkSecurity);
217+
VerifyOrQuit(msgInfo->mRadioIeee802154);
218+
VerifyOrQuit(msgInfo->mPriority == OT_HISTORY_TRACKER_MSG_PRIORITY_NORMAL);
219+
VerifyOrQuit(msgInfo->mNeighborRloc16 == child.Get<Mle::Mle>().GetRloc16());
220+
VerifyOrQuit(msgInfo->mChecksum != 0);
221+
repliesFound++;
222+
}
223+
}
224+
VerifyOrQuit(repliesFound == 3);
225+
}
226+
227+
} // namespace Nexus
228+
} // namespace ot
229+
230+
int main(void)
231+
{
232+
ot::Nexus::TestHistoryTracker();
233+
printf("All tests passed\n");
234+
return 0;
235+
}

0 commit comments

Comments
 (0)