From 6687792a35f19954dd34550cfef21ce05b6b069d Mon Sep 17 00:00:00 2001 From: Rye Date: Wed, 4 Nov 2026 03:06:02 -0500 Subject: [PATCH] A packet number past 24 bits no longer hangs the message system The wire carries 32 bits for a packet's number, and numbers wrap at 24. checkPacketInID works out how far ahead a number is modulo 24 bits, so 0x01000006 looks no distance from an expected 6. It then walks up from the number expected, wrapping at 24 bits, until it reaches the number, which it never does. mPotentialLostPackets fills with all 16.7 million numbers and the main thread spins for good. One datagram is enough, from the simulator or from anyone who can forge its source address. 5541ce25b5 brought this in when it made the distance modular, so that a gap could span the wrap. Before it, `(index < id) && ((id - index) < 16)` kept such a number out of the walk. It now takes the out-of-order path again, and a gap across the wrap still fills. The new case ran for 60 s of CPU holding 1 GB on the old code and was killed. With the fix it passes, and so does ctest -L llmessage, 29 of 29. Co-Authored-By: Claude Opus 5.5 --- indra/llmessage/llcircuit.cpp | 5 ++- indra/llmessage/tests/llcircuit_test.cpp | 55 ++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/indra/llmessage/llcircuit.cpp b/indra/llmessage/llcircuit.cpp index cac0529fb8..8be2425714 100644 --- a/indra/llmessage/llcircuit.cpp +++ b/indra/llmessage/llcircuit.cpp @@ -754,7 +754,10 @@ void LLCircuitData::checkPacketInID(TPACKETID id, bool receive_resent) // Modular forward distance, so a small gap that spans the 24-bit // packet-id wrap fills in normally; a genuinely old id yields a // huge forward distance and still takes the out-of-order path. - if (LLModularMath::subtract(id, index) < 16) + // So does an id past the 24 bits, which the wire can carry: the + // distance ignores its high bits, but the walk below wraps at 24 + // bits and would never reach it. + if (id < LL_MAX_OUT_PACKET_ID && LLModularMath::subtract(id, index) < 16) { while (index != id) { diff --git a/indra/llmessage/tests/llcircuit_test.cpp b/indra/llmessage/tests/llcircuit_test.cpp index ed8f6cb68d..d1835402c4 100644 --- a/indra/llmessage/tests/llcircuit_test.cpp +++ b/indra/llmessage/tests/llcircuit_test.cpp @@ -211,6 +211,23 @@ namespace ll_test { return circuit.checkDuplicate(id, resent); } + + // A packet's number was counted in, the packet not marked resent. + static void checkIn(LLCircuitData& circuit, TPACKETID id) + { + circuit.checkPacketInID(id, false); + } + + // The numbers a gap skipped that have not come, lowest first. + static std::vector missing(const LLCircuitData& circuit) + { + std::vector ids; + for (const auto& number : circuit.mPotentialLostPackets) + { + ids.push_back(number.first); + } + return ids; + } }; } @@ -452,4 +469,42 @@ namespace circuit.clearDuplicateList(8); CHECK_FALSE(CircuitProbe::checkDuplicate(circuit, 7, true)); } + + TEST_CASE("llcircuit: a number past the 24 bits opens no gap", "[llcircuit]") + { + StubMessageSystemScope message_system; + LLCircuitData circuit(LLHost(), 0, HEARTBEAT, TIMEOUT); + CircuitProbe::checkIn(circuit, 5); + + // The wire carries 32 bits and numbers wrap at 24. This number's low + // bits are the one expected next, so it looks no distance ahead, yet + // counting up from the number expected never reaches it. + CircuitProbe::checkIn(circuit, LL_MAX_OUT_PACKET_ID + 6); + CHECK(CircuitProbe::missing(circuit).empty()); + + // Nor does the highest number the wire can carry open one. + CircuitProbe::checkIn(circuit, 0xFFFFFFFF); + CHECK(CircuitProbe::missing(circuit).empty()); + + // Nor the first number past the top, from just below it. + CircuitProbe::checkIn(circuit, LL_MAX_OUT_PACKET_ID - 3); + CircuitProbe::checkIn(circuit, LL_MAX_OUT_PACKET_ID); + CHECK(CircuitProbe::missing(circuit).empty()); + + // Gaps are still counted after them. + CircuitProbe::checkIn(circuit, 20); + CircuitProbe::checkIn(circuit, 23); + CHECK(CircuitProbe::missing(circuit) == std::vector{ 21, 22 }); + } + + TEST_CASE("llcircuit: a gap across the wrap leaves its numbers missing", "[llcircuit]") + { + StubMessageSystemScope message_system; + LLCircuitData circuit(LLHost(), 0, HEARTBEAT, TIMEOUT); + CircuitProbe::checkIn(circuit, 0); + CircuitProbe::checkIn(circuit, LL_MAX_OUT_PACKET_ID - 3); + CircuitProbe::checkIn(circuit, 2); + CHECK(CircuitProbe::missing(circuit) + == std::vector{ 0, 1, LL_MAX_OUT_PACKET_ID - 2, LL_MAX_OUT_PACKET_ID - 1 }); + } }