Skip to content

Commit 81dc38a

Browse files
lte: (fixes #447) Skip CQI report on spectrum-model mismatch
During a handover the UE PHY is reconfigured to the target cell's EARFCN and bandwidth, which changes the spectrum model of the noise and interference PSDs. A downlink control reception associated with the previous configuration can complete after the reconfiguration, so the SpectrumValue division in LteUePhy::GenerateMixedCqiReport aborted on the spectrum-model equality assertion. Guard each division with a spectrum-model check: use the data interference denominator only when its model matches, fall back to the noise PSD, and otherwise skip the mixed CQI report for this subframe. Add a regression test (lte-handover-spectrum-model) that aborts before this change and runs to completion after.
1 parent 85864cb commit 81dc38a

3 files changed

Lines changed: 275 additions & 2 deletions

File tree

src/lte/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,7 @@ set(test_sources
342342
test/test-lte-epc-e2e-data.cc
343343
test/test-lte-handover-delay.cc
344344
test/test-lte-handover-failure.cc
345+
test/test-lte-handover-spectrum-model.cc
345346
test/test-lte-handover-target.cc
346347
test/test-lte-rlc-header.cc
347348
test/test-lte-rrc.cc

src/lte/model/lte-ue-phy.cc

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -721,21 +721,36 @@ LteUePhy::GenerateMixedCqiReport(const SpectrumValue& sinr)
721721
m_ctrlSinrForRlf = sinr;
722722

723723
SpectrumValue mixedSinr = (m_rsReceivedPower * m_paLinear);
724-
if (m_dataInterferencePowerUpdated)
724+
if (m_dataInterferencePowerUpdated &&
725+
mixedSinr.GetSpectrumModel() == m_dataInterferencePower.GetSpectrumModel())
725726
{
726727
// we have a measurement of interf + noise for the denominator
727728
// of SINR = S/(I+N)
728729
mixedSinr /= m_dataInterferencePower;
729730
m_dataInterferencePowerUpdated = false;
730731
NS_LOG_LOGIC("data interf measurement available, SINR = " << mixedSinr);
731732
}
732-
else
733+
else if (mixedSinr.GetSpectrumModel() == m_noisePsd->GetSpectrumModel())
733734
{
734735
// we did not see any interference on data, so interference is
735736
// there and we have only noise at the denominator of SINR
736737
mixedSinr /= (*m_noisePsd);
737738
NS_LOG_LOGIC("no data interf measurement available, SINR = " << mixedSinr);
738739
}
740+
else
741+
{
742+
// During a handover the UE PHY is reconfigured to the target cell's
743+
// EARFCN/bandwidth, which changes the spectrum model of m_noisePsd
744+
// (and m_dataInterferencePower). A control reception that started on
745+
// the previous cell can complete after this reconfiguration, leaving
746+
// m_rsReceivedPower on a different spectrum model than the denominator.
747+
// Dividing mismatched spectrum values would abort (@issueid{447}); the
748+
// resulting measurement is meaningless, so skip this CQI report.
749+
m_dataInterferencePowerUpdated = false;
750+
NS_LOG_LOGIC("spectrum model mismatch (handover reconfiguration), "
751+
"skipping mixed CQI report");
752+
return;
753+
}
739754

740755
/*
741756
* some RBs are not used in PDSCH and their SINR is very high
Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
/*
2+
* Copyright (c) 2026 Centre Tecnologic de Telecomunicacions de Catalunya (CTTC)
3+
*
4+
* SPDX-License-Identifier: GPL-2.0-only
5+
*
6+
* Author: Gabriel Ferreira <gabrielcarvfer@gmail.com>
7+
*/
8+
9+
#include "ns3/application-container.h"
10+
#include "ns3/boolean.h"
11+
#include "ns3/config.h"
12+
#include "ns3/constant-velocity-mobility-model.h"
13+
#include "ns3/data-rate.h"
14+
#include "ns3/double.h"
15+
#include "ns3/epc-tft.h"
16+
#include "ns3/eps-bearer.h"
17+
#include "ns3/inet-socket-address.h"
18+
#include "ns3/internet-stack-helper.h"
19+
#include "ns3/ipv4-address-helper.h"
20+
#include "ns3/ipv4-interface-container.h"
21+
#include "ns3/ipv4-static-routing-helper.h"
22+
#include "ns3/ipv4-static-routing.h"
23+
#include "ns3/lte-helper.h"
24+
#include "ns3/mobility-helper.h"
25+
#include "ns3/net-device-container.h"
26+
#include "ns3/node-container.h"
27+
#include "ns3/nstime.h"
28+
#include "ns3/packet-sink-helper.h"
29+
#include "ns3/point-to-point-epc-helper.h"
30+
#include "ns3/point-to-point-helper.h"
31+
#include "ns3/position-allocator.h"
32+
#include "ns3/rng-seed-manager.h"
33+
#include "ns3/simulator.h"
34+
#include "ns3/test.h"
35+
#include "ns3/udp-client-server-helper.h"
36+
#include "ns3/uinteger.h"
37+
38+
using namespace ns3;
39+
40+
NS_LOG_COMPONENT_DEFINE("LteHandoverSpectrumModelTest");
41+
42+
/**
43+
* @ingroup lte-test
44+
*
45+
* @brief Regression test for @issueid{447}: a UE handing over between cells must
46+
* not abort with the spectrum-model mismatch assertion
47+
* (m_spectrumModel == x.m_spectrumModel) in SpectrumValue.
48+
*
49+
* During a handover the UE PHY is reconfigured to the target cell, which
50+
* changes the spectrum model of its noise/interference PSDs. A downlink
51+
* control reception associated with the previous configuration can complete
52+
* after the reconfiguration, so LteUePhy::GenerateMixedCqiReport() must not
53+
* divide two SpectrumValue objects with different spectrum models.
54+
*
55+
* The test runs a compact mobile multi-UE A3-RSRP handover scenario with the
56+
* control and data error models enabled (which trigger the handover/RLF
57+
* activity that exposes the race) and verifies that the simulation runs to
58+
* completion.
59+
*/
60+
class LteHandoverSpectrumModelTestCase : public TestCase
61+
{
62+
public:
63+
LteHandoverSpectrumModelTestCase();
64+
~LteHandoverSpectrumModelTestCase() override;
65+
66+
private:
67+
void DoRun() override;
68+
};
69+
70+
LteHandoverSpectrumModelTestCase::LteHandoverSpectrumModelTestCase()
71+
: TestCase("Handover does not abort on spectrum-model mismatch (issue #447)")
72+
{
73+
}
74+
75+
LteHandoverSpectrumModelTestCase::~LteHandoverSpectrumModelTestCase()
76+
{
77+
}
78+
79+
void
80+
LteHandoverSpectrumModelTestCase::DoRun()
81+
{
82+
// Deterministic run.
83+
RngSeedManager::SetSeed(1);
84+
RngSeedManager::SetRun(1);
85+
86+
const double simTime = 5.0; // the pre-fix abort occurs at ~2.4 s
87+
const uint16_t numberOfUes = 30;
88+
const uint16_t numberOfCells = 5;
89+
const double speed = 16.0;
90+
91+
Config::SetDefault("ns3::LteRlcUm::MaxTxBufferSize", UintegerValue(60 * 1024));
92+
Config::SetDefault("ns3::LteHelper::UseIdealRrc", BooleanValue(false));
93+
Config::SetDefault("ns3::LteSpectrumPhy::CtrlErrorModelEnabled", BooleanValue(true));
94+
Config::SetDefault("ns3::LteSpectrumPhy::DataErrorModelEnabled", BooleanValue(true));
95+
// Radio link failure timers.
96+
Config::SetDefault("ns3::LteUeRrc::N310", UintegerValue(1));
97+
Config::SetDefault("ns3::LteUeRrc::N311", UintegerValue(1));
98+
Config::SetDefault("ns3::LteUeRrc::T310", TimeValue(Seconds(1)));
99+
100+
Ptr<LteHelper> lteHelper = CreateObject<LteHelper>();
101+
Ptr<PointToPointEpcHelper> epcHelper = CreateObject<PointToPointEpcHelper>();
102+
lteHelper->SetEpcHelper(epcHelper);
103+
lteHelper->SetPathlossModelType(TypeId::LookupByName("ns3::LogDistancePropagationLossModel"));
104+
lteHelper->SetSchedulerType("ns3::PfFfMacScheduler");
105+
lteHelper->SetHandoverAlgorithmType("ns3::A3RsrpHandoverAlgorithm");
106+
lteHelper->SetHandoverAlgorithmAttribute("Hysteresis", DoubleValue(3.0));
107+
lteHelper->SetHandoverAlgorithmAttribute("TimeToTrigger", TimeValue(MilliSeconds(256)));
108+
109+
// Internet / remote host.
110+
Ptr<Node> pgw = epcHelper->GetPgwNode();
111+
NodeContainer remoteHostContainer;
112+
remoteHostContainer.Create(1);
113+
Ptr<Node> remoteHost = remoteHostContainer.Get(0);
114+
InternetStackHelper internet;
115+
internet.Install(remoteHostContainer);
116+
PointToPointHelper p2ph;
117+
p2ph.SetDeviceAttribute("DataRate", DataRateValue(DataRate("100Gb/s")));
118+
p2ph.SetDeviceAttribute("Mtu", UintegerValue(1500));
119+
p2ph.SetChannelAttribute("Delay", TimeValue(Seconds(0.010)));
120+
NetDeviceContainer internetDevices = p2ph.Install(pgw, remoteHost);
121+
Ipv4AddressHelper ipv4h;
122+
ipv4h.SetBase("1.0.0.0", "255.0.0.0");
123+
Ipv4InterfaceContainer internetIpIfaces = ipv4h.Assign(internetDevices);
124+
Ipv4Address remoteHostAddr = internetIpIfaces.GetAddress(1);
125+
Ipv4StaticRoutingHelper ipv4RoutingHelper;
126+
Ptr<Ipv4StaticRouting> remoteHostStaticRouting =
127+
ipv4RoutingHelper.GetStaticRouting(remoteHost->GetObject<Ipv4>());
128+
remoteHostStaticRouting->AddNetworkRouteTo(Ipv4Address("7.0.0.0"), Ipv4Mask("255.0.0.0"), 1);
129+
130+
// eNodeBs: one macro plus several small cells along a line.
131+
NodeContainer enbNodes;
132+
enbNodes.Create(numberOfCells);
133+
Ptr<ListPositionAllocator> enbPosition = CreateObject<ListPositionAllocator>();
134+
enbPosition->Add(Vector(500, 0, 0)); // macro
135+
enbPosition->Add(Vector(10, 0, 0));
136+
enbPosition->Add(Vector(30, 0, 0));
137+
enbPosition->Add(Vector(50, 0, 0));
138+
enbPosition->Add(Vector(70, 0, 0));
139+
MobilityHelper enbMobility;
140+
enbMobility.SetMobilityModel("ns3::ConstantPositionMobilityModel");
141+
enbMobility.SetPositionAllocator(enbPosition);
142+
enbMobility.Install(enbNodes);
143+
144+
NetDeviceContainer enbDevs;
145+
Config::SetDefault("ns3::LteEnbPhy::TxPower", DoubleValue(43.0));
146+
enbDevs.Add(lteHelper->InstallEnbDevice(enbNodes.Get(0)));
147+
Config::SetDefault("ns3::LteEnbPhy::TxPower", DoubleValue(20.0));
148+
for (uint16_t j = 1; j < numberOfCells; j++)
149+
{
150+
enbDevs.Add(lteHelper->InstallEnbDevice(enbNodes.Get(j)));
151+
}
152+
153+
// UEs moving along the line of small cells.
154+
NodeContainer ueNodes;
155+
ueNodes.Create(numberOfUes);
156+
MobilityHelper ueMobility;
157+
ueMobility.SetMobilityModel("ns3::ConstantVelocityMobilityModel");
158+
ueMobility.Install(ueNodes);
159+
for (uint16_t i = 0; i < numberOfUes; i++)
160+
{
161+
ueNodes.Get(i)->GetObject<MobilityModel>()->SetPosition(Vector(0, 0, 0));
162+
ueNodes.Get(i)->GetObject<ConstantVelocityMobilityModel>()->SetVelocity(
163+
Vector(speed, 0, 0));
164+
}
165+
NetDeviceContainer ueDevs = lteHelper->InstallUeDevice(ueNodes);
166+
internet.Install(ueNodes);
167+
Ipv4InterfaceContainer ueIpIfaces = epcHelper->AssignUeIpv4Address(NetDeviceContainer(ueDevs));
168+
169+
for (uint16_t i = 0; i < numberOfUes; i++)
170+
{
171+
lteHelper->Attach(ueDevs.Get(i));
172+
}
173+
174+
// Downlink/uplink traffic and a dedicated bearer per UE. The scenario
175+
// mirrors the reporter's repro (sinks at both ends, a short packet
176+
// interval) which exposes the handover activity that triggers the bug.
177+
Ptr<UniformRandomVariable> startTime = CreateObject<UniformRandomVariable>();
178+
startTime->SetAttribute("Min", DoubleValue(0));
179+
startTime->SetAttribute("Max", DoubleValue(0.010));
180+
uint16_t dlPort = 10000;
181+
uint16_t ulPort = 20000;
182+
for (uint16_t u = 0; u < numberOfUes; ++u)
183+
{
184+
Ptr<Node> ue = ueNodes.Get(u);
185+
Ptr<Ipv4StaticRouting> ueStaticRouting =
186+
ipv4RoutingHelper.GetStaticRouting(ue->GetObject<Ipv4>());
187+
ueStaticRouting->SetDefaultRoute(epcHelper->GetUeDefaultGatewayAddress(), 1);
188+
189+
++dlPort;
190+
++ulPort;
191+
192+
UdpClientHelper dlClientHelper(ueIpIfaces.GetAddress(u), dlPort);
193+
dlClientHelper.SetAttribute("Interval", TimeValue(MilliSeconds(1)));
194+
dlClientHelper.SetAttribute("MaxPackets", UintegerValue(1000000));
195+
ApplicationContainer dlApps = dlClientHelper.Install(remoteHost);
196+
PacketSinkHelper dlSink("ns3::UdpSocketFactory",
197+
InetSocketAddress(Ipv4Address::GetAny(), dlPort));
198+
dlApps.Add(dlSink.Install(ue));
199+
200+
UdpClientHelper ulClientHelper(remoteHostAddr, ulPort);
201+
ulClientHelper.SetAttribute("Interval", TimeValue(MilliSeconds(1)));
202+
ulClientHelper.SetAttribute("MaxPackets", UintegerValue(1000000));
203+
ApplicationContainer ulApps = ulClientHelper.Install(ue);
204+
PacketSinkHelper ulSink("ns3::UdpSocketFactory",
205+
InetSocketAddress(Ipv4Address::GetAny(), ulPort));
206+
ulApps.Add(ulSink.Install(remoteHost));
207+
208+
Ptr<EpcTft> tft = Create<EpcTft>();
209+
EpcTft::PacketFilter dlpf;
210+
dlpf.localPortStart = dlPort;
211+
dlpf.localPortEnd = dlPort;
212+
tft->Add(dlpf);
213+
EpcTft::PacketFilter ulpf;
214+
ulpf.remotePortStart = ulPort;
215+
ulpf.remotePortEnd = ulPort;
216+
tft->Add(ulpf);
217+
EpsBearer bearer(EpsBearer::NGBR_IMS);
218+
lteHelper->ActivateDedicatedEpsBearer(ueDevs.Get(u), bearer, tft);
219+
220+
Time appStart = Seconds(startTime->GetValue());
221+
dlApps.Start(appStart);
222+
ulApps.Start(appStart);
223+
}
224+
225+
lteHelper->AddX2Interface(enbNodes);
226+
227+
Simulator::Stop(Seconds(simTime));
228+
Simulator::Run();
229+
230+
// Reaching this point means the simulation completed without the
231+
// spectrum-model mismatch assertion aborting it (@issueid{447}).
232+
NS_TEST_ASSERT_MSG_EQ(Simulator::Now().GetSeconds() >= simTime - 1e-6,
233+
true,
234+
"Simulation did not run to completion");
235+
236+
Simulator::Destroy();
237+
}
238+
239+
/**
240+
* @ingroup lte-test
241+
*
242+
* @brief Test suite for the handover spectrum-model regression test.
243+
*/
244+
class LteHandoverSpectrumModelTestSuite : public TestSuite
245+
{
246+
public:
247+
LteHandoverSpectrumModelTestSuite();
248+
};
249+
250+
LteHandoverSpectrumModelTestSuite::LteHandoverSpectrumModelTestSuite()
251+
: TestSuite("lte-handover-spectrum-model", Type::SYSTEM)
252+
{
253+
AddTestCase(new LteHandoverSpectrumModelTestCase(), TestCase::Duration::EXTENSIVE);
254+
}
255+
256+
/// Static variable for test initialization
257+
static LteHandoverSpectrumModelTestSuite g_lteHandoverSpectrumModelTestSuite;

0 commit comments

Comments
 (0)