#include "testHarness.h" #include "vf/Transport.h" #include #include #include using namespace vf; namespace { ByteBuffer messageOf(uint32_t index, std::size_t size) { ByteBuffer payload; payload.reserve(size); uint32_t state = index * 2654435761u + 12345u; for (std::size_t i = 0; i < size; ++i) { state = state * 1664525u + 1013904223u; payload.push_back(static_cast((state >> 16) & 0xffu)); } // The first four bytes carry the index so the receiver can prove ordering. for (int byte = 0; byte < 4 && static_cast(byte) < payload.size(); ++byte) payload[static_cast(byte)] = static_cast((index >> (8 * byte)) & 0xffu); return payload; } uint32_t indexOf(const ByteBuffer& payload) { uint32_t index = 0; for (int byte = 3; byte >= 0; --byte) index = (index << 8) | payload[static_cast(byte)]; return index; } // One tick of a two-endpoint exchange, always in the same order so a run is // reproducible from the link seed alone. void stepPair(Transport& a, Transport& b, uint64_t tick) { a.pump(tick); a.flush(tick); b.pump(tick); b.flush(tick); } } // namespace VF_TEST(loopbackLinkReplaysTheSameImpairmentsForTheSameSeed) { LinkQuality quality; quality.lossPercent = 30; quality.duplicatePercent = 15; quality.reorderPercent = 25; quality.latencyTicks = 2; quality.seed = 0xfeedfacecafebeefull; std::vector runs; for (int attempt = 0; attempt < 2; ++attempt) { LoopbackLink link(quality); std::string trace; for (uint64_t tick = 0; tick < 60; ++tick) { ByteBuffer payload = messageOf(static_cast(tick), 8); link.send(0, payload, tick); std::vector arrived; link.receive(1, tick, arrived); for (const ByteBuffer& one : arrived) { trace += std::to_string(tick); trace += ':'; trace += std::to_string(indexOf(one)); trace += ' '; } } trace += "d"; trace += std::to_string(link.droppedCount()); trace += "u"; trace += std::to_string(link.duplicatedCount()); runs.push_back(trace); } CHECK(runs[0] == runs[1]); CHECK(runs[0].size() > 40u); LoopbackLink probe(quality); for (uint64_t tick = 0; tick < 200; ++tick) probe.send(0, messageOf(1, 4), tick); CHECK(probe.droppedCount() > 30u); CHECK(probe.duplicatedCount() > 10u); CHECK(probe.delayedCount() > 20u); } VF_TEST(reliableChannelDeliversEveryMessageExactlyOnceInOrderOverALossyLink) { LinkQuality quality; quality.lossPercent = 30; quality.duplicatePercent = 12; quality.reorderPercent = 25; quality.reorderExtraTicks = 4; quality.latencyTicks = 2; quality.seed = 0x1234abcd5678ef01ull; LoopbackLink link(quality); TransportConfig config; config.mtu = 256; config.retransmitTimeoutTicks = 8; config.sendWindow = 16; Transport sender(link, 0, config); Transport receiver(link, 1, config); const uint32_t messageCount = 240; for (uint32_t i = 0; i < messageCount; ++i) { ByteBuffer payload = messageOf(i, 40 + (i % 90)); sender.sendReliable(payload); } std::vector order; for (uint64_t tick = 0; tick < 4000 && order.size() < messageCount; ++tick) { stepPair(sender, receiver, tick); ByteBuffer got; while (receiver.popReliable(got)) order.push_back(indexOf(got)); } CHECK_EQ(order.size(), static_cast(messageCount)); bool inOrder = true; for (std::size_t i = 0; i < order.size(); ++i) if (order[i] != static_cast(i)) inOrder = false; CHECK(inOrder); // Payloads survive intact, not just their headers. for (uint32_t i = 0; i < 5 && i < order.size(); ++i) CHECK_EQ(order[i], i); CHECK(link.droppedCount() > 20u); CHECK(link.duplicatedCount() > 5u); CHECK(sender.stats().retransmitted > 0u); CHECK(receiver.stats().duplicatesDropped + receiver.stats().staleDropped > 0u); CHECK_EQ(receiver.stats().messagesDelivered, static_cast(messageCount)); } VF_TEST(reliableMessagesLargerThanTheMtuAreFragmentedAndReassembledByteForByte) { LinkQuality quality; quality.lossPercent = 20; quality.reorderPercent = 30; quality.reorderExtraTicks = 3; quality.latencyTicks = 1; quality.seed = 0x77aabbccddee0011ull; LoopbackLink link(quality); TransportConfig config; config.mtu = 128; config.retransmitTimeoutTicks = 5; config.sendWindow = 8; Transport sender(link, 0, config); Transport receiver(link, 1, config); ByteBuffer huge = messageOf(9, 20000); CHECK(huge.size() > sender.maxFragmentPayload() * 100); sender.sendReliable(huge); CHECK(sender.unackedFragmentCount() > 100u); ByteBuffer got; bool arrived = false; uint64_t tick = 0; for (; tick < 6000 && !arrived; ++tick) { stepPair(sender, receiver, tick); if (receiver.popReliable(got)) arrived = true; } CHECK(arrived); // Let the last acks come back before asking whether anything is still unacked. for (uint64_t settle = 0; settle < 40; ++settle) stepPair(sender, receiver, tick + settle); CHECK_EQ(got.size(), huge.size()); CHECK(got == huge); CHECK_EQ(sender.unackedFragmentCount(), static_cast(0));