Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions indra/llmessage/llpacketbuffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,18 @@ class LLPacketBuffer
bool getPacketIDChecked() const { return mPacketIDChecked; }
void setPacketIDChecked(bool checked) { mPacketIDChecked = checked; }

// Whether this packet, a reliable one, was acked as it was read off the
// socket, before it was sorted into the inbound queue.
bool getAcked() const { return mAcked; }
void setAcked(bool acked) { mAcked = acked; }

protected:
char mData[NET_BUFFER_SIZE]; // packet data /* Flawfinder : ignore */
S32 mSize; // size of buffer in bytes
LLHost mHost; // source/dest IP and port
LLHost mReceivingIF; // source/dest IP and port
bool mPacketIDChecked = false;
bool mAcked = false;
};

#endif
Expand Down
3 changes: 2 additions & 1 deletion indra/llmessage/llsdmessagereader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,8 @@ S32 getElementSize(const LLSD& llsd)
case LLSD::TypeDate:
return sizeof(LLDate);
case LLSD::TypeURI:
return sizeof(LLURI);
// its text, as getString() reads it
return static_cast<S32>(llsd.asString().size());
case LLSD::TypeBinary:
{
std::vector<U8> data = llsd;
Expand Down
28 changes: 21 additions & 7 deletions indra/llmessage/message.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -521,12 +521,13 @@ bool LLMessageSystem::checkMessages(LockMessageChecker&, S64 frame_count,
S32 num_acks = 0;
S32 true_rcv_size = 0;
bool recv_packet_id_checked = false;
bool recv_acked = false;

U8* buffer = mTrueReceiveBuffer;

if (!faked_message)
{
mTrueReceiveSize = receivePacketOrDrop((char *)mTrueReceiveBuffer, recv_packet_id_checked);
mTrueReceiveSize = receivePacketOrDrop((char *)mTrueReceiveBuffer, recv_packet_id_checked, recv_acked);
receive_size = mTrueReceiveSize;
}
else
Expand Down Expand Up @@ -649,8 +650,12 @@ bool LLMessageSystem::checkMessages(LockMessageChecker&, S64 frame_count,
{
// We need to ACK here to suppress
// further resends of packets we've
// already seen.
cdp->collectRAck(mCurrentRecvPacketID);
// already seen, unless this copy was
// acked as it came in.
if (!recv_acked)
{
cdp->collectRAck(mCurrentRecvPacketID);
}

LL_DEBUGS("Messaging") << "Discarding duplicate from " << host << LL_ENDL;
if(mVerboseLog)
Expand Down Expand Up @@ -731,8 +736,12 @@ bool LLMessageSystem::checkMessages(LockMessageChecker&, S64 frame_count,
// Add to the recently received list for duplicate suppression
cdp->mRecentlyReceivedReliablePackets[mCurrentRecvPacketID] = getMessageTimeUsecs();

// Put it onto the list of packets to be acked
cdp->collectRAck(mCurrentRecvPacketID);
// Put it onto the list of packets to be acked, unless
// it was acked as it came in
if (!recv_acked)
{
cdp->collectRAck(mCurrentRecvPacketID);
}
mReliablePacketsIn++;
}
}
Expand Down Expand Up @@ -965,9 +974,10 @@ void LLMessageSystem::setNetImpairment(F32 loss_percent, F32 burst_packets, F32
}
#endif

S32 LLMessageSystem::receivePacketOrDrop(char* datap, bool& packet_id_already_checked)
S32 LLMessageSystem::receivePacketOrDrop(char* datap, bool& packet_id_already_checked, bool& already_acked)
{
packet_id_already_checked = false;
already_acked = false;

#if AL_NET_IMPAIRMENT
// Steady-state traffic arrives here, one direct socket read per call. The drain that feeds the
Expand Down Expand Up @@ -995,6 +1005,7 @@ S32 LLMessageSystem::receivePacketOrDrop(char* datap, bool& packet_id_already_ch
mLastSender = pkt.getHost();
mLastReceivingIF = pkt.getReceivingInterface();
packet_id_already_checked = pkt.getPacketIDChecked();
already_acked = pkt.getAcked();

if (packet_size > 0)
{
Expand Down Expand Up @@ -1150,10 +1161,13 @@ S32 LLMessageSystem::bufferInboundPacket()

if (cdp)
{
// ACK inbound reliable packet ASAP
// ACK inbound reliable packet ASAP, so its sender stops resending
// it while it waits in the queue, whatever checkMessages() makes
// of it, which does not ack it again.
if ((data[0] & LL_RELIABLE_FLAG))
{
cdp->collectRAck(recv_packet_id);
pkt.setAcked(true);
}

// Check packet sequencing here, in true socket-arrival order, before
Expand Down
5 changes: 3 additions & 2 deletions indra/llmessage/message.h
Original file line number Diff line number Diff line change
Expand Up @@ -983,9 +983,10 @@ class LLMessageSystem : public LLMessageSenderInterface
// Receive one packet: pop from ring if buffered, else read from mSocket.
// Sets mLastSender and mLastReceivingIF.
// Sets packet_id_already_checked to whether checkPacketInID() was already
// run for this packet back when it was buffered (see bufferInboundPacket()).
// run for this packet back when it was buffered (see bufferInboundPacket()),
// and already_acked to whether it was acked then.
// Returns packet_size, or 0 if no packet or packet was dropped.
S32 receivePacketOrDrop(char* datap, bool& packet_id_already_checked);
S32 receivePacketOrDrop(char* datap, bool& packet_id_already_checked, bool& already_acked);

public:
#if AL_NET_IMPAIRMENT
Expand Down
15 changes: 15 additions & 0 deletions indra/llmessage/tests/llsdmessagereader_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,21 @@ namespace
outValue = buffer;
CHECK(outValue == inValue);
}
// A handler reads a variable field by asking its size and reading as
// many bytes.
TEST_CASE_METHOD(LLSDMessageReaderTestData, "LLSDMessageReader: a URI in a variable field is sized and read as its text", "[LLSDMessageReader]")
{
const std::string text = "https://example.com/cap/1234?x=1";
LLSDMessageReader msg = testType(LLURI(text));

const S32 size = msg.getSize("block", "var");
CHECK(size == (S32)text.size());
CHECK(msg.getSize("block", 0, "var") == size);

std::vector<char> buffer(size + 1, 'x');
msg.getString("block", "var", size + 1, buffer.data());
CHECK(std::string(buffer.data()) == text);
}
TEST_CASE_METHOD(LLSDMessageReaderTestData, "LLSDMessageReader: getVector3 reads back an LLVector3", "[LLSDMessageReader]")
{
LLVector3 outValue, inValue = LLVector3(1,2,3);
Expand Down
130 changes: 130 additions & 0 deletions indra/llmessage/tests/message_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
#include "message_prehash.h"
#include "net.h"

#include <algorithm>

#if !LL_WINDOWS
#include <fcntl.h>
#include <netinet/in.h>
Expand All @@ -59,6 +61,12 @@ namespace ll_test
{
return circuit.mAcks.size();
}

// How many of the acks waiting are for packet `id`.
static size_t acksWaitingFor(const LLCircuitData& circuit, TPACKETID id)
{
return std::count(circuit.mAcks.begin(), circuit.mAcks.end(), id);
}
};
}

Expand Down Expand Up @@ -622,5 +630,127 @@ namespace
CHECK(sDuplicateTestsHandled == 2);
}
}

// Takes what has come in into the inbound queue, as the viewer does when
// a frame's messages run over, until `packets` wait there or a second
// has passed.
void queue_until(LLMessageSystem& system, S32 packets)
{
const F64 give_up = LLTimer::getTotalSeconds() + 1.0;
while (system.drainUdpSocket() < packets && LLTimer::getTotalSeconds() < give_up)
{
ms_sleep(1);
}
}

// Reads every packet waiting in the inbound queue.
void read_queued(LLMessageSystem& system)
{
LockMessageChecker lmc(&system);
while (system.getNumBufferedPackets() > 0)
{
lmc.checkMessages();
}
}

// Reads what has come in until the system has refused `packets` packets
// in all, or a second has passed.
void read_until_refused(LLMessageSystem& system, U32 packets)
{
LockMessageChecker lmc(&system);
const F64 give_up = LLTimer::getTotalSeconds() + 1.0;
while (system.mInvalidOnCircuitPackets < packets && LLTimer::getTotalSeconds() < give_up)
{
if (!lmc.checkMessages())
{
ms_sleep(1);
}
}
}

// A packet taken into the inbound queue is acked as it comes in, before
// it is read, so that its sender stops resending it while it waits; one
// read straight from the socket is acked as it is read, unless it is
// refused.
TEST_CASE_METHOD(LLMessageSystemTestData, "LLMessageSystem: a reliable packet is acked once whether or not it waited in the queue", "[LLMessageSystem]")
{
const std::string template_path = mTestConfigDir + mSep + "message_template.msg";
const TemplateFile message_template(template_path, DUPLICATE_TEST_TEMPLATE);
LLMessageSystem system(template_path, NET_USE_OS_ASSIGNED_PORT, 1, 0, 0, false, 5.f, 100.f);
REQUIRE(system.isOK());
const UseMessageSystem use(system);
system.setHandlerFunc("DuplicateTest", handle_duplicate_test);
sDuplicateTestsHandled = 0;

const SenderSocket sender;
REQUIRE(sender.mResult == 0);
const LLHost sender_host("127.0.0.1", sender.mPort);
system.enableCircuit(sender_host, true);
const LLCircuitData* circuit = system.mCircuitInfo.findCircuit(sender_host);
REQUIRE(circuit != nullptr);
const LLHost receiver("127.0.0.1", system.getListenPort());

// Low message `message` numbered `id`: 1 is DuplicateTest, and 2 is
// no message the template has, which the system refuses.
auto send = [&](U8 id, U8 message)
{
const U8 packet[] = { LL_RELIABLE_FLAG, 0, 0, 0, id, 0, 0xff, 0xff, 0x00, message, 1, 2, 3, 4 };
REQUIRE(send_packet(sender.mSocket, (const char*)packet, (int)sizeof(packet), receiver.getAddress(), receiver.getPort()));
};

{
INFO("read straight from the socket");
send(1, 1);
read_until(system, 1);
REQUIRE(system.mPacketsIn == 1);
CHECK(ll_test::CircuitProbe::acksWaitingFor(*circuit, 1) == 1);
}
{
INFO("a copy of it read straight from the socket is acked as it is read");
send(1, 1);
read_until(system, 2);
REQUIRE(system.mPacketsIn == 2);
CHECK(sDuplicateTestsHandled == 1);
CHECK(ll_test::CircuitProbe::acksWaitingFor(*circuit, 1) == 2);
}
{
INFO("taken into the queue and then read");
send(2, 1);
queue_until(system, 1);
REQUIRE(system.getNumBufferedPackets() == 1);
CHECK(ll_test::CircuitProbe::acksWaitingFor(*circuit, 2) == 1);
read_queued(system);
REQUIRE(system.mPacketsIn == 3);
CHECK(sDuplicateTestsHandled == 2);
CHECK(ll_test::CircuitProbe::acksWaitingFor(*circuit, 2) == 1);
}
{
INFO("a copy of it taken into the queue: each copy acked once");
send(2, 1);
queue_until(system, 1);
REQUIRE(system.getNumBufferedPackets() == 1);
read_queued(system);
REQUIRE(system.mPacketsIn == 4);
CHECK(sDuplicateTestsHandled == 2);
CHECK(ll_test::CircuitProbe::acksWaitingFor(*circuit, 2) == 2);
}
{
INFO("a refused packet that waited in the queue was acked as it came in");
send(3, 2);
queue_until(system, 1);
REQUIRE(system.getNumBufferedPackets() == 1);
read_queued(system);
REQUIRE(system.mInvalidOnCircuitPackets == 1);
CHECK(ll_test::CircuitProbe::acksWaitingFor(*circuit, 3) == 1);
}
{
INFO("one read straight from the socket is not acked");
send(4, 2);
read_until_refused(system, 2);
REQUIRE(system.mInvalidOnCircuitPackets == 2);
CHECK(ll_test::CircuitProbe::acksWaitingFor(*circuit, 4) == 0);
}
CHECK(ll_test::CircuitProbe::acksWaiting(*circuit) == 5);
}
}

3 changes: 3 additions & 0 deletions indra/llprimitive/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ add_library(llprimitive STATIC)
target_sources(
llprimitive
PRIVATE
alcompressedupdate.cpp
alobjectmotion.cpp
lldaeloader.cpp
llgltfmaterial.cpp
Expand All @@ -24,6 +25,7 @@ target_sources(
PUBLIC
FILE_SET HEADERS
FILES
alcompressedupdate.h
alobjectmotion.h
lldaeloader.h
llgltfmaterial.h
Expand Down Expand Up @@ -65,6 +67,7 @@ if(AL_BUILD_TESTS)
llprimitive_tests
PROJECT llprimitive
SOURCES
tests/alcompressedupdate_test.cpp
tests/alobjectmotion_test.cpp
tests/llgltfmaterial_test.cpp
tests/llmaterial_test.cpp
Expand Down
78 changes: 78 additions & 0 deletions indra/llprimitive/alcompressedupdate.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/**
* @file alcompressedupdate.cpp
* @brief The fields of a compressed object update after its extra parameters
*
* $LicenseInfo:firstyear=2026&license=viewerlgpl$
* Alchemy Viewer Source Code
* Copyright (C) 2026, Rye <rye@alchemyviewer.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
* $/LicenseInfo$
*/

#include "linden_common.h"

#include "alcompressedupdate.h"

#include "lldatapacker.h"
#include "llvolumemessage.h"

bool ALCompressedSoundAndNames::unpack(LLDataPacker& dp, U32 flags)
{
*this = ALCompressedSoundAndNames();
if (flags & 0x10)
{
if (!dp.unpackUUID(mSoundID, "SoundUUID")
|| !dp.unpackF32(mSoundGain, "SoundGain")
|| !dp.unpackU8(mSoundFlags, "SoundFlags")
|| !dp.unpackF32(mSoundRadius, "SoundRadius"))
{
return false;
}
}
if (flags & 0x100)
{
if (!dp.unpackString(mNameValues, "NV"))
{
return false;
}
}
return true;
}

ALCompressedVolume::ECut ALCompressedVolume::unpack(LLDataPacker& dp, U32 flags)
{
// A volume's parameters take a run of bytes of one size: it is read
// whole, and the parameters unpacked from what was read.
U8 params[LLVolumeMessage::PACKED_VOLUME_PARAMS_SIZE];
if (!dp.unpackBinaryDataFixed(params, sizeof(params), "VolumeParams"))
{
return ECut::VOLUME_PARAMS;
}
LLDataPackerBinaryBuffer params_dp(params, sizeof(params));
mVolumeParamsInRange = LLVolumeMessage::unpackVolumeParams(&mVolumeParams, params_dp);

if (!dp.unpackBinaryData(mTextureEntry, "TextureEntry"))
{
return ECut::TEXTURE_ENTRY;
}

mHasTextureAnim = (flags & 0x40) != 0;
if (mHasTextureAnim && !mTextureAnim.unpackTAMessage(dp))
{
return ECut::TEXTURE_ANIM;
}
return ECut::NONE;
}
Loading
Loading